Nâng cấp đầu nối MCP "Incomex VPS": bộ fs_* 3 gốc + vps_status + ui_screenshot + trợ lý host (2026-09-17)
Nâng cấp đầu nối MCP "Incomex VPS" — báo cáo nghiệm thu (2026-09-17)
Người làm: Claude Code (Fable 5.1). Mọi dòng "ĐẠT" dưới đây đều đo bằng lời gọi MCP thật qua đầu nối claude.ai → nginx → container, làm trên bản sao, không đụng bản gốc.
1. Bảng kết quả (công cụ × gốc)
| Công cụ | gh (GitHub) |
ui (xưởng vẽ) |
docs (chỉ đọc) |
|---|---|---|---|
| fs_list | ✅ ĐẠT (kèm git HEAD, tự pull) | ✅ ĐẠT | ✅ ĐẠT |
| fs_read theo đoạn | ✅ ĐẠT — file 314KB, có dòng dài 238.270 ký tự, cắt theo ký tự + con trỏ đọc tiếp | ✅ ĐẠT — đọc JS dòng 1500 (sau byte 51.200) | ✅ ĐẠT |
| fs_search | ✅ ĐẠT | ✅ ĐẠT | ✅ ĐẠT |
| fs_edit | ✅ ĐẠT — commit 97f71b3 hiện trên GitHub, diff đúng 1 dòng; old_str trùng 6 chỗ → từ chối kèm số dòng; Mac chen ngang + phiên bản cũ → từ chối, thay đổi của Mac còn nguyên |
✅ ĐẠT — bản cũ commit 08bf162 rồi bản sửa 819f6e5 |
✅ bị từ chối đúng |
| fs_write | ✅ ĐẠT (tạo mới + push) | (dùng chung mã, không thử riêng) | ✅ bị từ chối đúng |
| fs_log / fs_diff | ✅ ĐẠT | ✅ ĐẠT | — (không có sổ git: báo rõ) |
| ui_screenshot | — | ✅ ĐẠT — ảnh trước/sau khác rõ (nền xám → vàng) | — |
| vps_status | ✅ 5/5 mục trả kết quả (containers · systemd · resources · logs · git); muc="containers; id" và service="incomex-nginx; cat /etc/shadow" → bị từ chối |
||
| 8 công cụ cũ | read_file ✅ · query_pg ✅ · write_file ✅ · pg_schema ❌ và directus_ ❌ — HỎNG TỪ TRƯỚC, không do đợt này* (xem ghi nợ N1, N2) |
Thử phá script root: 8 ca docker-logs (x; id, $(id), --since, tên ngoài danh sách, n=0/99999/10; id, lệnh con lạ) + 6 ca ui-commit (ngoài gốc, tuyệt đối, .git/config, symlink ra ngoài, qua symlink ui-preview, thừa trường) → 14/14 bị từ chối, mỗi ca có 1 dòng journald. sudo lệnh khác bằng user trợ lý → "a password is required". Message commit có xuống dòng + $(id) vào commit nguyên văn như chữ.
2. Địa chỉ
- Repo: https://github.com/Huyen1974/incomex-workspace (PRIVATE, nhánh
main). Deploy key ghi163589117, gắn duy nhất repo này. - Gốc
gh=/opt/incomex/mcp-roots/gh(chỉ user trợ lý ghi; container mount chỉ đọc). - Gốc
ui=/opt/incomex/docs/mcp-writes/— đây là thư mục thật phục vụ/ui-preview/mcp-writes/.mcp-writes/ui-previewchỉ là symlink trỏ về.(từ 27/07) nên hai tài liệu cũ không lệch nhau. Đã có sổ git (/var/lib/incomex/ui-preview-notebook.git, snapshot 5 phút). - Gốc
docs=/opt/incomex/docs+/opt/incomex/dot/specs(path bắt đầu bằngdocs/hoặcspecs/). - Nhật ký ghi duy nhất:
/var/log/mcp-audit/fs-writes.jsonl. Mã:/opt/incomex/claude-mcp/app/{fsroots,hostq,hosttools}.py+helper/. Git VPS: commit72a3f0d.
3. Kiến trúc đã chọn (chủ duyệt 3 lần trong phiên)
Container claude-mcp nhận lệnh từ AI nên giữ nguyên bị khoá: không internet, không git, không docker.sock, không khoá GitHub, không quyền GSM. Phần cần máy chủ do đúng 1 unit incomex-mcp-helper.service làm: user thường (uid 999), nhận việc qua thư mục hàng đợi (không mở cổng), thực đơn cố định 8 việc, không bao giờ chạy chuỗi lấy từ yêu cầu. Đường lên root duy nhất: 1 file sudoers → 1 script /usr/local/sbin/incomex-mcp-helper-priv (3 lệnh con: docker-ps · docker-logs · ui-commit).
Đánh đổi: vì sudo cần setuid nên unit không bật được NoNewPrivileges. Lớp bù: ProtectSystem=strict, ProtectHome, PrivateTmp, ReadWritePaths hẹp (4 đường dẫn), script root tự kiểm mọi tham số, sudo -l chỉ hiện đúng 1 dòng. Cách gỡ sạch (chỉ mô tả, chưa làm): xoá /etc/sudoers.d/incomex-mcp-helper + /usr/local/sbin/incomex-mcp-helper-priv.
Chụp ảnh: dùng lại Chromium headless Playwright đã có sẵn trên host (chép 260MB sang /usr/local/lib/incomex-mcp-helper/), chỉ cho vps.incomexsaigoncorp.vn, chặn mọi tên miền khác ở tầng DNS của trình duyệt, PNG ≤1,5MB, ≤30s.
4. Khoá bí mật
- Tên secret:
MCP_WORKSPACE_GH_DEPLOY_KEY(GSM, projectgithub-chatgpt-ggcloud). Sinh trong tmpfs trên VPS, lưu thẳng GSM, khoá riêng chưa từng rời VPS/GSM, không có bản trên Mac. - Nơi dùng:
/usr/local/sbin/incomex-mcp-helper-key-fetch(quyền root,ExecStartPre=+— cùng cơ chếhermes-key-fetch) → tmpfs/run/incomex-mcp-helper/gh_deploy_key(root:incomex-mcp-helper 0640). User trợ lý không có credential gcloud nào. - Xoay khoá (3 dòng): sinh cặp khoá mới →
gcloud secrets versions add MCP_WORKSPACE_GH_DEPLOY_KEY+gh repo deploy-key add/delete→systemctl restart incomex-mcp-helper. - Tự kiểm cuối ĐẠT: xoá bản nạp tạm (shred) → quét đĩa không còn bản sao → restart → nạp lại từ GSM (hash khớp) → push
d56a6a2thành công. - Token nhịp tim Kuma: SSOT là
kuma.db(đúng luật sẵn có), cũng nạp vào tmpfs. Đã thêm dòng sổ kê vàoBAN-DO-HE-THONG-20260729.md§12.2.
5. Giám sát · đĩa · drift
- Uptime-Kuma: giữ "MCP Endpoint"; thêm monitor push "MCP Host Helper" (id 18, nhịp 60s, ngưỡng 180s). Đo thật: dừng trợ lý → Kuma ghi DOWN 10:49:03 UTC → chạy lại → UP.
- Đĩa: trước 15.444MB trống → sau 14.727MB (build image chỉ ~1MB; phần lớn là 260MB Chromium + hoạt động khác của máy). Không prune gì. Ứng viên dọn (chỉ liệt kê): 41GB image Docker "reclaimable", image treo
d8ed7813(bản build đầu),/root/mcp-helper-stage/, các tệp yêu cầu JSON trongqueue/done|out(rất nhỏ), image cũphase1a/rollback*của claude-mcp (KHÔNG đụngrollback-pre-fs-20260917). incomex-config-drift-check: đã DRIFT từ 08/08/2026 vì 2 mụcnginx-default-confvàenv-docker-stack. Sau đợt này thêm mục thứ 3mcp-compose(do sửa compose claude-mcp — đúng dự kiến). Không tìm thấy quy trình chính thức cập nhật mốc so sánh ⇒ không sửa, không tắt, chỉ báo cáo.
6. Cách quay lui
- Container: sửa
image:trong/opt/incomex/claude-mcp/docker-compose.claude-mcp.ymlvềclaude-mcp-local:rollback-pre-fs-20260917(hoặc chép lại…yml.bak-fsroots-20260917T103553Z) →docker compose -f … up -d. Biến môi trường/mount mới vô hại với bản cũ. - Trợ lý:
systemctl disable --now incomex-mcp-helper.service(8 công cụ cũ + fs_list/read/search vẫn chạy).
7. Hiện trạng các máy chủ MCP (Bước 0)
| Máy chủ | Công cụ | Giới hạn | Trùng nhau |
|---|---|---|---|
| claude-mcp ("Incomex VPS") | 8 cũ + 9 mới | 50KB/lần, 10s (ghi: 120s) | — |
| claude-kb ("Incomex KB") | 6, chỉ đọc | proxy của agent-data; các công cụ ghi bị khoá có chủ đích 26/07 (path secret cũ bị lộ, chờ xoay) | Trùng 100% phần đọc với agent-data |
| agent-data | 10 (đọc + ghi KB) | MCP tự viết, không SSE nên Claude chat không gắn thẳng được | như trên |
| cowork-mcp + runner | 9 (shell hộp cát, preview, patch) | 1 job/lần | — |
| Lark gateway | ~41 | systemd, quyền root | — |
Đề xuất (KHÔNG làm): giữ cặp KB/agent-data như hiện tại cho tới khi xoay path secret của claude-kb; sau đó mở lại công cụ ghi ở claude-kb thay vì thêm đầu nối thứ ba.
8. Danh sách ghi nợ
- N1 —
pg_schemachưa từng chạy được: 205 lần gọi, 0 thành công (lỗiAmbiguousParametertrong mã cũ). Không sửa vì luật "không đổi 8 công cụ cũ". Sửa = ép kiểu%(schema)s::text. - N2 —
directus_*hỏng từ ≤11/09: container Directus không còn trên mạngclaude_mcp_net(lần thành công cuối 18/05). Sửa = chạyscripts/ensure-claude-mcp-network.sh— ngoài phạm vi đợt này. - N3 — ĐÃ ĐÓNG 17/09: chủ quyết chuyển repo PUBLIC (trufflehog + regex quét 15 commit/toàn lịch sử: 0 secret) → bật secret scanning + push protection, bảo vệ
mainchặn force-push + chặn xoá (enforce_admins); deploy key trợ lý host push6be0053sau đổi visibility: ĐẠT. - N4 —
full_pagechỉ chụp cao tối đa 5000px (giới hạn của trình duyệt dòng lệnh). - N5 — drift-check thêm mục
mcp-compose(xem §5). - N6 —
claude-mcp/README.mdcòn ghi "3 tools" (tài liệu cũ, không sửa vì luật không tạo/sửa tài liệu khác). - N7 — trong lúc làm có bên khác khởi động lại
incomex-nuxtvàincomex-agent-data(không phải đợt này).
9. Ba câu tuyên ngôn
- Vĩnh viễn? Gốc/repo/danh sách cho phép đều ở biến môi trường + file cấu hình root; thêm gốc mới = thêm 1 mục JSON, không sửa mã. Khoá mất thì nạp lại từ GSM.
- Nhầm được không? Hạ tầng chặn: container không có mạng/khoá/git; trợ lý chỉ có thực đơn; sudoers 1 dòng; edit đòi khớp đúng 1 chỗ + mã phiên bản; không có công cụ xoá.
- 100% tự động? Unit tự khởi động cùng máy, tự nạp khoá, Kuma kêu khi chết. Bước tay duy nhất còn lại: người dùng bấm "làm mới công cụ" ở đầu nối claude.ai nếu chưa thấy 9 công cụ mới.
10. ĐỢT 2 — 2026-09-18: gỡ mọi trần tự đặt (phase0.7 "nolimit")
Người làm: Claude Code (Fable 5.1). Chỉ đụng claude-mcp + trợ lý host của nó; không đụng agent-data / workspace GPT. Git VPS: commit e289226. Image claude-mcp-local:phase0.7-nolimit (Dockerfile.phase07 = app/ chồng lên phase0.5a, không cài gì). Lùi: image claude-mcp-local:rollback-pre-nolimit-20260918 (= phase0.6-fs) + docker-compose.claude-mcp.yml.bak-nolimit-20260917T233011Z → compose up -d; trợ lý: bản cũ của 4 file (helper.env, priv.conf, incomex-mcp-helper-priv, mcp_host_helper.py) ở /root/mcp-helper-rollback-20260918/ → chép lại + systemctl restart incomex-mcp-helper. Đĩa: 86% trước và sau (build ~1MB; test để lại 1 file 20MB — xem §10.5).
10.1 Nguyên tắc đã áp dụng
- Không còn con số trần "tiện tay" trong mã. Mỗi trần là (a) tham số người gọi chọn, mặc định vừa phải để tiết kiệm token, hoặc (b) trần cứng lấy từ biến môi trường (compose cho container,
helper.env/priv.confcho trợ lý), lý do ghi ngay cạnh biến (app/limits.py, compose,helper.env). - Bị cắt thì đầu ra luôn nói cắt ở đâu + gọi tiếp thế nào (
ĐỌC TIẾP: start_line=…,CÒN NỮA: offset=…,"next": "…offset=5000",chụp tiếp bằng offset_y=…). - Giữ nguyên hàng rào: không công cụ xoá (fs_move =
git mv, từ chối nếu đích tồn tại), không shell tự do (vps_status vẫn thực đơn cố định;sincesang trợ lý dưới dạng số epoch,containsmã hoá hex và so khớp chữ nguyên văn trong Python, không bao giờ thành đối số grep/regex), không force-push, che/chặn bí mật (bộ lọcstrictmới cho gốccode), khoá đường dẫn trong gốc, ảnh chỉvps.incomexsaigoncorp.vn, DB chỉ đọc (READ ONLY + AST +context_pack_readonly), khoá vẫn từ GSM.
10.2 Bảng trần cũ → trần mới → lý do trần mới
| Công cụ | Trần cũ (gõ cứng) | Trần mới | Lý do trần mới |
|---|---|---|---|
fs_read |
40KB/lần, max_lines≤2000, file ≤5MB |
max_bytes tham số (mặc định 40KB) — trần env MCP_TEXT_MAX_BYTES_CAP=1MB; max_lines mặc định 0 = không giới hạn dòng; file nạp vào bộ nhớ FS_FILE_LOAD_MAX_BYTES=64MB |
1MB chữ ≈ 300k token — không ngữ cảnh mô hình nào chứa hơn trong 1 câu trả lời. 64MB file ×3 bản (bytes/str/dòng) ≈ 200MB trong container 512MB; GitHub tự giới hạn 100MB/file |
fs_diff |
~36KB, dòng cắt 300 ký tự | max_bytes (mặc định 36KB, trần 1MB) + offset (dòng của bản vá) + line_chars (0 = không cắt) |
như trên |
fs_search |
max_results≤200, context ≤3, bỏ qua file >2MB, 7s cứng, 5 khớp/dòng |
max_results trần FS_SEARCH_MAX_RESULTS_CAP=10000 + offset; context tới 50; quét file tới 64MB (cùng trần nạp); time_budget_s tham số (mặc định 7) trần 60s; mọi khớp trên 1 dòng đều báo (file nén); line_chars; max_bytes |
60s < proxy_read_timeout 300s của nginx và < wall-clock công cụ; cây /opt/incomex 80k file quét không hết 30s (báo dừng sớm đúng chỗ) |
fs_edit |
≤20 chỗ sửa | không giới hạn số chỗ sửa — chỉ còn kích thước yêu cầu | đo thật 50 chỗ/1 lần, 1 commit |
fs_write |
1MiB; từ chối ghi đè file >50KB; gốc ui đòi thư mục cha có sẵn | FS_WRITE_MAX_BYTES=20MB; ghi đè file >FS_OVERWRITE_GUARD_BYTES(50KB) được khi có ĐỦ expected_version + overwrite_large=true; tự tạo thư mục cha ở mọi gốc |
nội dung đi trong JSON, chữ có dấu bị escape ×2 phải lọt client_max_body_size 50m của nginx ⇒ 20MB; trợ lý nhận payload 25MB (MCP_HELPER_PAYLOAD_MAX_BYTES); mem_limit container 256m→512m (host 12GB) |
fs_list |
depth 1–3, 300 mục, quét tối đa 5000 | depth tự do (0 = cả cây), limit trần FS_LIST_MAX_ENTRIES_CAP=5000 + offset, quét lười theo FS_LIST_TIME_S=20s |
quét theo ngân sách thời gian thay vì đếm mục |
fs_log |
n≤50 | n trần FS_LOG_MAX_N_CAP=1000 + offset + follow (theo file qua đổi tên) + max_bytes |
trợ lý cắt theo hàng, offset luôn chính xác |
fs_move (MỚI) |
— | git mv gốc gh (pull→mv→commit→push, push bị từ chối thì khôi phục clone) và ui (commit bản hiện tại → đổi tên → ghi nhận vào sổ); từ chối nếu đích tồn tại |
không phải xoá; lịch sử đi theo file |
gốc code (MỚI) |
— | /opt/incomex mount chỉ đọc tại /mnt/code; redact: strict; exclude = backups, lark-backups, secrets, data, lark-snapshots (kho dump DB/Lark chứa băm mật khẩu + toàn bộ bản ghi; kho bí mật; dữ liệu cá nhân học viên) — sửa 1 dòng JSON trong compose là đổi |
uid 10001 chỉ thấy file o+r; xem §10.3 về bộ lọc |
ui_screenshot |
rộng ≤1920, cao ≤2000, full_page ≤5000px, ảnh ≤1,5MB, 30s | rộng ≤3840, cao ≤4320, full_page ≤30000px css, ảnh ≤8MB (env SCREENSHOT_* cả 2 phía), 90s; thêm selector, wait_ms, device_scale_factor 0.5–4, offset_y, allow_oversize |
trình duyệt điều khiển bằng CDP qua --remote-debugging-pipe (không mở cổng, không thư viện). 🔴 Giới hạn thật của hạ tầng là Anthropic API: ≤8000px/cạnh, ≤5MB/ảnh — vượt là ứng dụng chat có thể từ chối cả tin nhắn ⇒ mặc định cắt lát ≤8000px và trả offset_y để chụp tiếp; allow_oversize=true mới chụp 1 ảnh tới 30000px (đo thật 2560×15046px OK) |
vps_status logs |
n≤200 | n trần STATUS_LOG_MAX_N_CAP=5000; thêm since ('90s'/'15m'/'2h'/'3d' hoặc mốc UTC), contains (chữ nguyên văn); max_bytes (cắt đầu, giữ cuối) |
script root: docker-logs <tên> <n> [<epoch> <hex>]; có contains thì cửa sổ quét chặn ở log_scan_lines=200000 dòng để 1 lần gọi không stream cả log nhiều GB; đầu ra script root 2MB (out_cap_bytes, priv.conf root sở hữu) |
query_pg |
LIMIT 500 cứng, 5s cứng, 50KB | limit (mặc định 500) trần QUERY_PG_MAX_ROWS_CAP=50000 + offset; timeout_s (mặc định 5) trần 60 (SET LOCAL statement_timeout); max_bytes (mặc định 50KB) trần 8MB; con trỏ phía server → dừng đúng ranh giới hàng, JSON luôn hợp lệ, mỗi hàng 1 dòng (~30% ít token hơn indent) |
60s < nginx 300s; role chỉ-đọc dùng chung không giữ snapshot hàng phút; 8MB = 1 thông điệp MCP ×4 bản trong RAM |
pg_schema |
chưa từng chạy (AmbiguousParameter) | ép kiểu %(schema)s::text — chạy; max_bytes cắt theo cả bảng, liệt kê tên bảng bị bỏ |
N1 đóng |
directus_read |
limit ≤500 | limit trần DIRECTUS_MAX_ITEMS_CAP=50000 + offset + fields; trả total (meta=filter_count) + next; timeout HTTP 60s |
chưa đo được — Directus vẫn ngoài mạng (§10.4) |
read_file cũ |
50KB / 10.000 dòng | bỏ trần dòng; max_bytes cùng trần 1MB của fs_read; cắt trên ranh giới dòng (hoặc start_char nếu 1 dòng dài) |
dùng chung MCP_TEXT_MAX_BYTES_CAP |
write_file cũ |
1MiB | cùng FS_WRITE_MAX_BYTES=20MB |
|
| message commit | 500/600 ký tự | FS_MESSAGE_MAX_CHARS=5000 (cả 3 lớp) |
file yêu cầu hàng đợi 64KB |
| trần ngoài cùng mọi câu trả lời | 50KB | MCP_RESPONSE_HARD_MAX_BYTES=8MB (lưới an toàn; mỗi công cụ tự cắt sạch trước) |
1 thông điệp JSON-RPC ×~4 bản trong RAM của container 512MB |
Wall-clock từng công cụ (TOOL_TIMEOUTS) đặt theo tham số lớn nhất người gọi có thể xin + biên (query_pg 80s, fs_search 80s, fs_edit/write/move 180s, screenshot 120s).
10.3 Gốc code — phát hiện ngoài dự kiến và cách xử lý
Khi đo bộ lọc trên giá trị bí mật thật (kịch bản /root/mcp-nolimit-stage/leakscan2.py: gom giá trị từ các file .env/secrets/ thật, đẩy mọi file mà gốc code sẽ phục vụ qua đúng hàm che, chỉ báo số đếm + vân tay băm, không in giá trị):
- 🔴 3 bí mật đang sống nằm gõ cứng trong file ai-cũng-đọc-được dưới
/opt/incomex: AGENT_DATA_API_KEY (UUID) nằm ngay trongdocker/nginx/conf.d/default.conf(+13 bản.bakcủa nó,scripts/reconcile-*.py,docker/nuxt-repo/automation/config.js,docker/agent-data-repo/scripts/*); INCOMEX_PG_PASSWORD trongdocker/agent-data-repo/scripts/import_firestore_to_pg.py+.github/workflows/s109-pg-setup.yml; MYSQL_ROOT_PASSWORD trong 2 báo cáoW205_fix_db_auth_safe_password.md. Bộ lọc cũ (sanitize.redact) không che được chúng (đo: 32 cặp file×bí-mật sống sót). - Đã thêm chế độ
redact_strict(chỉ gốccode): che URL path-secret, mọi phép gán có tên*pass|secret|token|key|auth|credential*(trừ tham chiếuos.environ/${VAR}— là mã, không phải bí mật), UUID, mọi token ≥16 ký tự trông ngẫu nhiên (entropy ≥3,7; loại trừ tên file có dấu thời gian, đường dẫn), và dạng văn xuôipassword "…". Đo lại: 3 cặp sống sót → sau vá dạng văn xuôi còn 1 và đó là dương tính giả (tên file trong một nhãn thư mục). ⚠️ Lần đo cuối cùng sau vá cuối chưa chạy lại được vì hộp cát của Claude Code chặn lệnh đọc file bí mật; chủ chạycd /root/mcp-nolimit-stage && python3 leakscan2.py backups,lark-backups,secrets,data,lark-snapshotsđể có con số cuối (kỳ vọng 0 cặp thật). - 🔴 Đề nghị (chưa làm, ngoài phạm vi): xoay 3 bí mật trên và gỡ chúng khỏi mã/nginx conf (nginx có thể
includetừsecrets/như các route MCP đã làm). Đây là vấn đề vệ sinh có sẵn, gốccodechỉ làm nó lộ ra; gốcghlà repo PUBLIC nên đọc-từ-code-rồi-ghi-sang-gh là đường rò rỉ cần nhớ khi cho AI làm việc tự do. - Đo qua MCP thật:
claude-mcp/.env→ từ chối (denylist),backups/→ "nằm NGOÀI gốc",docker/nginx/conf.d/default.confđọc được với giá trị khoá hiện[REDACTED], ghi/sửa/chuyển ở gốccode→root_read_only.
10.4 Khảo sát directus_* (chỉ khảo sát, CHỜ DUYỆT)
- Hiện trạng: container
incomex-directus(tạo lại 2026-07-23) chỉ ở mạngdocker_incomex;claude_mcp_netcònpostgres, incomex-nginx, incomex-claude-kb, incomex-cowork-mcp, incomex-claude-mcp. Container claude-mcp không phân giải được têndirectus⇒ mọidirectus_*trảupstream_error. Lần thành công cuối 2026-05-18 (2 lần trong toàn bộ audit). - Vì sao rời mạng: compose chính
docker/docker-compose.ymlkhaiclaude_mcp_netlà mạng ngoài nhưng không service nào dùng nó (directus.networks = [incomex]); việc gắn Directus vào mạng hồi tháng 5 làm bằng tay (scripts/ensure-claude-mcp-network.sh→docker network connect) và mất khi container được tạo lại 23/07. Không có tài liệu/commit nào ghi quyết định tách ⇒ không phải quyết định an toàn cố ý, là trôi cấu hình (bước tay chưa được ghi vào compose). Thiết kế gốc (docs/design-mcp-remote-server.md§3H) vẫn coi Directus thuộc mạng này. - Đề xuất nối lại (2 mức, đều chưa làm): (1) tức thời: chạy
bash /opt/incomex/claude-mcp/scripts/ensure-claude-mcp-network.sh(idempotent, chỉdocker network connect, không tạo lại container) — quay lui:docker network disconnect claude_mcp_net incomex-directus; (2) bền: thêmclaude_mcp_netvàoservices.directus.networkstrong compose chính — đây là file của stack GPT/Codex đang sửa, nên chờ Codex xong. Lưu ý mạnginternal: truenên Directus nối vào vẫn không có thêm đường ra internet. - Cần chủ cân nhắc trước khi duyệt:
directus_create/update/deletelà công cụ ghi (Directus API, allowlist 7 collection) — nối mạng lại là mở lại cả phần ghi, khác với "DB chỉ đọc" củaquery_pg.
10.5 Nghiệm thu (gọi MCP thật qua nginx → container, trên bản sao)
| Ca | Kết quả |
|---|---|
đọc trọn file 314KB 1 lần (max_bytes=400000) |
✅ 256/256 dòng, 316.660 byte, 1,9s, HẾT FILE |
| tìm trong file 2,2MB nén (trước bị bỏ qua) | ✅ 75 khớp trên 1 dòng, 0,1s |
1 fs_edit 50 chỗ sửa |
✅ commit 95f3e31, 0,7s |
fs_move gốc ui + fs_log follow |
✅ commit 40ae08b, lịch sử 3 commit đi theo tên mới; đích đã tồn tại → từ chối |
fs_move gốc gh |
✅ 5a6792c push main, follow thấy commit trước đổi tên |
| ghi 19,8MB rồi đọc đuôi, sửa 1 chỗ, tìm trong đó | ✅ 3,0s / 0,7s / 7,3s / 1,9s; 21,8MB → từ chối đúng trần 20MB; ghi đè sai version → từ chối |
| chụp full_page trang dài | ✅ 1280×4988 (815KB) và 1280×7513 (1,15MB); selector=body @x2 → 2560×8000 + "chụp tiếp offset_y=4000"; allow_oversize → 2560×15046; 3840 rộng OK; host lạ / selector không có → từ chối; 15360×8000 mặc định → từ chối kèm hướng dẫn |
query_pg 5000 dòng |
✅ 5000 hàng, 59KB, 0,2s, next: offset=5000; offset 6998 → 2 hàng cuối; timeout_s=30 OK; UPDATE → DENIED |
pg_schema |
✅ chạy (1 bảng và cả DB 1041 bảng: trả 68 bảng trong 50KB + tên 973 bảng còn lại) |
gốc code |
✅ đọc claude-mcp/app/server.py; .env từ chối; backups/ ngoài gốc; nginx conf che khoá |
vps_status logs n=1000 since=2h contains=INFO (docker) + journald |
✅; service="…; cat /etc/shadow" và since="1h; id" → từ chối |
| thử phá script root | ✅ 35/35 bị từ chối/đúng (helper/host-install/thu-pha-priv.sh: 8 docker-logs cũ + 9 mới cho since/contains/n + 6 ui-commit cũ + 11 ui-move + sudo lệnh khác), mỗi ca 1 dòng journald (32 REFUSED) |
Bản sao/chứng tích để lại (không xoá vì không có công cụ xoá, chủ quyết): ui nolimit-test-20260918/ (moved/edit50-renamed.html, big20mb.txt 20MB — 2 bản trong sổ git), gh acceptance/moved/gpt-concurrency-note.txt, ảnh test /root/mcp-nolimit-stage/out/*.png, thư mục stage /root/mcp-nolimit-stage/.
10.6 Nợ mới / lưu ý
- N8 ⚠️ ứng dụng chat: kết quả 1MB chữ hay ảnh >8000px có thể bị client cắt/từ chối (Claude Code tự cắt kết quả MCP >25k token) — trần đã là của hạ tầng phía nhận, không còn của đầu nối.
- N9 regex
fs_searchdo người gọi viết có thể "nổ" trên 1 dòng dài (Pythonrekhông ngắt được giữa chừng); ngân sách thời gian kiểm giữa các dòng, cpu container 0.5 chặn phần còn lại. - N10
drift-checksẽ thêm khác biệtmcp-compose(như N5). - N1 ĐÓNG, N4 ĐÓNG (full_page 30000px), N2 còn (chờ duyệt §10.4). Bí mật gõ cứng §10.3 chưa xoay.
11. ĐỢT 1e — 2026-09-18: hết lỗi "file nhị phân" giả · trần tài nguyên · đo token · dọn file thử · khảo sát bí mật
Người làm: Claude Code (Fable 5.1). Chỉ đụng claude-mcp (trợ lý host không đổi). Git VPS 2b77591. Image claude-mcp-local:phase0.8-1e (Dockerfile.phase08 = app/ chồng phase0.5a, không cài gì). Đĩa 86% trước và sau (build ~1MB; clone 6 repo để quét: 52MB trong /root/mcp-nolimit-stage/ghscan/). Mọi "ĐẠT" đo bằng lời gọi MCP thật qua nginx.
11.1 Bảng việc
| Việc | Kết quả | Bằng chứng |
|---|---|---|
| 0 — Lỗi "file nhị phân" giả | ✅ ĐẠT | _CONTEXT.md (9.095B, có ô cắt đúng byte 8192): trước = fs_read/read_file từ chối, fs_search "Không khớp" im lặng (scanned=0); sau = đọc trọn 144 dòng, tìm thấy 4 khớp. Quét 4 gốc: logic cũ kết luận nhầm 911 file (ui 20 · docs 21 · code 870 — toàn .md/.csv/.js/.html tiếng Việt), logic mới 0 (mọi file đó decode trọn UTF-8). 7 ca kiểm thử tests/test_is_binary.py (ký tự 2/3/4 byte vắt qua byte 8192; nhị phân thật vẫn bị chặn). fs_search nay đếm file bỏ qua: skipped_binary=, skipped_too_large= |
| 1 — Trần tài nguyên | ⚠️ NỬA | cpus 0.5→2, mem 512m→1g (đo docker inspect), time_budget_s mặc định 7→20, chú thích "512 MB" đã sửa. Nhưng gốc code KHÔNG quét hết được — xem 11.2 và CHỦ CẦN QUYẾT #1 |
| 2 — Nối lại Directus | ✅ ĐẠT (có điều kiện) | Đã nối incomex-directus vào claude_mcp_net (alias directus, IP 172.19.0.7). directus_read(tac_publication) trả total=4, tac_change_set total=0. 🔑 Token của đầu nối là người dùng Directus tac-agent (policy tac-agent-policy) — chỉ đọc/ghi được các collection tac_*; information_unit, species… trả 403 (quyền, không phải mạng). Không chạy nguyên script ensure-claude-mcp-network.sh vì nó nối luôn cả incomex-agent-data (đang không ở mạng này) — vi phạm luật "không đụng agent-data"; chỉ chạy đúng 1 lệnh docker network connect --alias directus mà script dùng cho Directus. Compose chính chưa sửa (ghi nợ N11) |
| 3 — Đo token | ✅ ĐẠT | vps_status(muc='usage'): đọc audit.jsonl trong container, không thêm lệnh host. Bảng theo công cụ: lần gọi · bytes · ms · cut · deny · err cho 24h và 7 ngày. Số 24h lúc đo: 153 lần gọi, 9,1MB trả về (ui_screenshot 7,5MB = 82%), 241s chạy |
| 4 — Gom file thử | ✅ ĐẠT | fs_move (lịch sử đi theo, fs_log follow=true thấy 4 commit của big20mb.txt). ui: _luu-tru-chung-tich-claude/ {nolimit-test-20260918/big20mb.txt, …/moved/edit50-renamed.html, mow-unified-canvas-v2-mcp-test.html, INDEX.md} — commit 1ec1f75 · a965b76 · ac708e2 · 90a25bd. gh: _luu-tru-chung-tich-claude/ {acceptance/gsm-reload-proof.md, INDEX.md} — c6d5053 · a22d267, push main. README.md gh thêm 1 đoạn "file thử của GPT — agent đừng đọc cả file" (0142f57). File GPT không di chuyển. Còn lại 3 thư mục rỗng nolimit-test-20260918/{,moved,sub} (không có công cụ xoá) |
| 5 — Bộ che strict | ✅ ĐẠT | Thêm miễn che cho tên "giống chữ" (Dockerfile.phase07, phase0.7-nolimit, pwd-2026-abc). Đo lại leakscan2 trước và sau đều: 30 giá trị bí mật · 80.360 file · 53 file chứa bí mật · 1 cặp sống sót = dương tính giả đã biết (tên file trong nhãn thư mục nginx) ⇒ 0 bí mật thật lọt thêm. Che nhầm trên file thật: compose 8→3, 00-NHAN-THU-MUC.md 3→0; khoá trong nginx conf vẫn che |
| 6 — Khảo sát bí mật | ✅ XONG, CHỜ DUYỆT | xem 11.3 — KHÔNG KHẨN: 6 repo công khai 0 commit nào chứa |
| 7 — Luật hoá | ✅ ĐẠT | Ghi vào 00-NHAN-THU-MUC.md (mục "LUAT VE TRAN / HAN CHE") + docstring đầu app/limits.py + chú thích compose |
CHỦ CẦN QUYẾT
- Gốc
codequét hết hay không? Gốc có 91.334 file / 5,4GB nhìn từ container:deploys/60.620 file (bản dựng Nuxt cũ, 2,9GB) +context-pack.tmp/9.597 file (snapshot tự sinh, 1,5GB) +context-pack-staging/590 +venv-xlsx/3.016. Quét toàn bộ mất 110–137s — vượt cả trần 60s. Loại 4 thư mục đó khỏi gốc ⇒ 13.358 file, quét hết 5–8s (đo thật). Đây là hạn chế mới nên tôi không tự làm. Đề xuất: đồng ý loại 4 thư mục (là bản dựng/snapshot/venv, không phải nguồn) — sửa 1 dòngexcludetrong compose; hoặc phương án B: nângFS_SEARCH_TIME_CAP_S60→240 (nginx 300s) và người gọi truyềntime_budget_s=150. - Quyền Directus cho đầu nối: token hiện là
tac-agent(chỉtac_*). Danh sách ghi củadirectus_create/update(information_unit, species, knowledge_node…) hầu hết ngoài quyền token ⇒ hiện tại ghi cũng 403. Đề xuất: giữ nguyên (an toàn hơn), chỉ mở thêm quyền đọc nếu Claude Chat thật sự cần đọc Directus thay vìquery_pg. - Xoay 3 bí mật — xem 11.3, chờ duyệt thứ tự.
mow-danh-tu-v2-copy.html(gh/acceptance, chủ tạo từ Mac cho nghiệm thu 17/09) tôi không gom vì không phải Claude tạo — chủ muốn gom thì 1 lệnhfs_move.
11.2 Số đo trước / sau
| Phép đo | Trước (phase0.7, cpus 0.5) | Sau (phase0.8-1e, cpus 2) |
|---|---|---|
fs_read _CONTEXT.md |
từ chối "nhị phân" | 144/144 dòng, HẾT FILE |
fs_search công cụ trong _CONTEXT.md |
0 khớp, scanned=0, im lặng | 4 khớp, scanned=1 |
fs_search 1 chuỗi toàn gốc code, ngân sách mặc định |
7,0s · 2.515 file · dừng sớm | 20,0s · 7.248 file · dừng sớm (tiền kiểm cả file bằng regex C trước khi lặp dòng: +25% file/giây) |
fs_search toàn gốc code, time_budget_s=300 |
— | 110–137s · 80.227 file quét hết, 11.108 nhị phân, 2 file >64MB |
| … nếu loại deploys/context-pack.tmp/context-pack-staging/venv-xlsx | — | 5–8s · 13.358 file quét hết |
Đọc trọn 3 tài liệu tiếng Việt thật (max_bytes=1000000) |
— | mow-unified-canvas-v2.html 159KB/2.428 dòng 0,1s · BAN-DO-HE-THONG-20260729.md 129KB/1.998 dòng 0,1s · AGENTS.md 18KB/364 dòng — không lỗi, không mất dấu; recovery/directus-backup-restore-runbook.md (trước bị coi nhị phân) 291 dòng OK |
Che nhầm (số chỗ [REDACTED]) |
compose 8 · nhãn thư mục 3 | compose 3 · nhãn 0 · nginx key vẫn 2 |
| Thử phá script root | 35/35 | 35/35 (chạy 2 lần) |
11.3 Khảo sát 3 bí mật gõ cứng (CHỈ KHẢO SÁT — chưa xoay)
Kết luận đầu tiên: KHÔNG KHẨN. Clone bare 6 repo công khai của Huyen1974 (agent-data 516 commit/62 nhánh · agent-data-production 14 · agent_data 1 · mpc_back_end_for_agent 0 · LarkBase_OAuth_Policy 1 · incomex-workspace 31) và git log --all -S<giá trị> cho cả 3 giá trị: 0 commit từng chứa, HEAD 0 file. Hai repo có chứa trong lịch sử là agent-data-test (API key: 7 commit, 4 file đang tracked; mật khẩu PG: 3 commit, 2 file) và web-test (API key: 10 commit, 5 file; MySQL root: 1 commit, 1 file) — cả hai PRIVATE (xác nhận bằng gh api đã đăng nhập). Giá trị không bao giờ được in; kịch bản /root/mcp-nolimit-stage/{secret-survey,gh-history-scan,local-repo-scan}.py chỉ in số đếm/đường dẫn.
| Bí mật | Ai đang dùng (xoay sót thì chết) | Nằm ở đâu (file sống, ngoài .bak) | Lưu ý |
|---|---|---|---|
| AGENT_DATA_API_KEY (UUID) | container incomex-agent-data (env API_KEY — bên kiểm), incomex-nuxt (NUXT_AGENT_DATA_API_KEY), incomex-claude-kb (AGENT_DATA_API_KEY), incomex-directus (AGENT_DATA_API_KEY, flow gọi agent-data); nginx docker/nginx/conf.d/default.conf (gõ cứng trong proxy_set_header, cả secrets/gpt-mcp-route.conf); DOT dot/bin/dot-knowledge-ingest-batch, dot-vector-audit; scripts/reconcile-*.py, test-agent-connections.sh; nuxt-repo/automation/config.js |
186 file trên đĩa (56 dưới docker/, 33 /root/agent-runs worktree, 23 evidence/, 22 /root/incomex-config-changes…); nguồn: docker/.env, secrets/.env.production, /etc/incomex/agent-data.env, claude-kb/.env |
Xoay = đổi đồng thời ≥4 container + nginx + 4 file DOT/script; lỡ 1 chỗ là Nuxt/KB/Directus-flow gọi agent-data 401 |
| INCOMEX_PG_PASSWORD (30 ký tự; ≠ POSTGRES_PASSWORD ≠ PG_PASSWORD ≠ DB_PASSWORD) | container incomex-agent-data (env PG_PASSWORD) — duy nhất |
docker/.env, /etc/incomex/agent-data.env, agent-data-repo/scripts/import_firestore_to_pg.py, agent-data-repo/.github/workflows/s109-pg-setup.yml, 1 file evidence luật Điều 33 |
Xoay dễ nhất: ALTER ROLE + 2 file env + restart agent-data; ~30s mất kết nối agent-data |
| MYSQL_ROOT_PASSWORD | không container nào — MySQL đã nghỉ hưu 13/03/2026 (S115, compose ghi rõ) | docker/.env, 2 báo cáo W205_fix_db_auth_safe_password.md (nuxt-repo + web-rp-current) |
Không còn dịch vụ ⇒ "xoay" = xoá dòng khỏi .env + che trong 2 báo cáo; rủi ro 0 |
⚠️ Đang có việc song song: /root/incomex-config-changes/hardening-20260918-env-docker-stack/ (hôm nay, không phải tôi) đang sờ vào docker/.env — xoay phải phối hợp với bên đó. incomex-agent-data bị tạo lại 2 lần trong đợt (03:19Z và 03:47Z, die → destroy → start; không do đợt này — compose overlay claude-mcp chỉ có 1 service).
Đề xuất thứ tự xoay (chờ duyệt): (1) MYSQL_ROOT_PASSWORD — xoá, 0 phút chết; (2) INCOMEX_PG_PASSWORD — ALTER ROLE trong PG → sửa docker/.env + /etc/incomex/agent-data.env → docker restart incomex-agent-data → xoá giá trị khỏi 2 file trong agent-data-repo (đọc từ env); chết ~30s; lùi = ALTER ROLE về giá trị cũ; (3) AGENT_DATA_API_KEY — sinh UUID mới → sửa .env của 4 container + nginx default.conf/gpt-mcp-route.conf (nên include từ secrets/) + 4 file DOT/script → restart agent-data, nuxt, claude-kb, directus, nginx -s reload; chết 1–2 phút; lùi = trả .env + conf từ bản sao .bak-<ts> rồi restart. Sau xoay: chạy lại secret-survey.py phải ra 0 file sống chứa giá trị cũ; các bản .bak/worktree /root/agent-runs vẫn chứa giá trị cũ (vô hại sau xoay, nhưng nên dọn — chủ quyết).
11.4 Cách quay lui đợt 1e
- Container:
docker tagsẵnclaude-mcp-local:rollback-pre-1e-20260918(= phase0.7-nolimit) +docker-compose.claude-mcp.yml.bak-1e-20260918T030949Z→ chép đè compose →docker compose -f docker-compose.claude-mcp.yml up -d claude-mcp(về cpus 0.5/512m, mã cũ — lỗi "nhị phân" giả quay lại). - Directus:
docker network disconnect claude_mcp_net incomex-directus. - File đã gom:
fs_movengược lại từng file (lịch sử vẫn theo). Trợ lý host không đổi, không cần lùi.
11.5 Nợ mới
- N11 compose chính
docker/docker-compose.ymlchưa cóclaude_mcp_netcho Directus — Directus tạo lại là rơi mạng lần nữa (file của Codex, chờ Codex xong). - N12 3 thư mục rỗng trong ui
nolimit-test-20260918/— không có công cụ xoá. - N13
ensure-claude-mcp-network.shnối cả agent-data — muốn dùng script phải bỏ mục đó hoặc chủ duyệt riêng. - N14
patch_documentcủa đầu nối agent-data qua Claude Code gửiold_strrỗng (lỗi cầu nối MCP, không phải KB) — §11 này ghi bằng RESTPATCH /documents/…trực tiếp. - N2 ĐÓNG (mạng), nhưng quyền Directus của token chỉ
tac_*(CHỦ CẦN QUYẾT #2).
12. ĐỢT KẾT NỐI CHUNG GPT + CLAUDE — 2026-09-18 (Claude Code) — VÒNG 1 + VÒNG 2
Đọc nhanh: vòng 1 ở 12.0–12.9 (giữ nguyên làm hồ sơ). VÒNG 2 — đóng nợ môi trường — ở 12.10–12.15, kèm bảng trước/sau, OWNER CẦN QUYẾT mới và đường lùi từng lớp.
Prompt: "Hoàn thiện kết nối GPT + Claude vào GitHub/VPS" (rev 2, Fible). Mục này được ghi lại sau MỖI nhóm việc để một phiên mới nối tiếp được. Trạng thái: 🟢 PASS · 🟡 cảnh báo · 🔴 FAIL/BLOCKED · ⚪ OPEN.
12.0 Bảng kết quả (cập nhật liên tục)
| Năng lực | GPT backend | GPT Chat/Work/Codex | Claude backend | Claude Chat/Cowork | Shared test | OPEN + lý do + bên xử lý |
|---|---|---|---|---|---|---|
| Giai đoạn 0 (định vị, khoá phối hợp) | 🟢 | — | 🟢 | — | — | — |
| C5 KB continuation (>30KB tới EOF, budget, quyền result) | 🟢 không đổi | — | 🟢 19/19 qua nginx (claude-kb-local:c5-20260918) |
⚪ | — | Claude Chat/Cowork chưa chấm — Fible: đọc báo cáo này bằng get_document_for_rewrite + kb_result_read |
| C6 restart/readiness | 🟢 không đổi | — | 🟢 phiên cũ → 404 đúng chuẩn; trợ lý chết → helper_unavailable 0,09s; readiness |
🟡 Claude Code (cầu nối Anthropic): sau restart bị 502 ~10 phút rồi tự nối lại (nginx: 404 → initialize 200 → list 200); Claude Chat chưa đo | — | 502 nằm ở proxy Anthropic, không phải VPS — chỉ ghi nhận |
S1 khoá ui chung |
🟢 không đổi mã | — | 🟢 cùng inode 1313457 | — | 🟢 hai chiều (đo lại có đồng bộ: Claude giữ → GPT WORKSPACE_BUSY; GPT giữ → host & Claude bị chặn) |
— |
| S2 phiên bản + HEAD | 🟢 sẵn có | — | 🟢 expected_head + expected_version |
🟢 (Claude Code) | ⚪ ca A/B GitHub hai chiều cần GPT ghi thật | Fible + GPT |
| S3 idempotency + mất phản hồi | ⚪ chỉ upload/commit idempotent | — | 🟢 operation_id, OUTCOME_UNKNOWN → 1 commit (đo thật) |
— | — | GPT operation_id cho edit/transaction — Codex |
| C1 fs_stat / fs_copy / fs_transaction | 🟢 sẵn có | — | 🟢 33/33 (vòng 2 đã sửa bộ đếm: ca "FAIL" là BÁO ĐỎ GIẢ do git trích dẫn tên có dấu, không phải sản phẩm — xem 12.10) | 🟢 fs_stat/fs_copy/fs_transaction qua cầu nối Claude Code; Claude Chat ⚪ | — | — |
| C2 search continuation | 🟢 sẵn có | — | 🟢 cursor tới EOF; cả gốc code complete; include/exclude; generated; regex lồng từ chối |
— | — | — |
| C3 ui_inspect / ui_act | 🟢 sẵn có (ui_inspect) | — | 🟢 19/19 trên trang fixture (chữ có dấu, modal, gửi form, che token/password, 404, chặn chuyển hướng) | 🟢 ui_inspect qua Claude Code | — | profile đăng nhập: OPEN (chưa có account test qua GSM) |
| C4 chuyển file | 🟢 sẵn có | ⚪ Work attachment | ⚪ khảo sát: fs_write 20MB text; nhị phân chỉ fs_copy trong VPS | ⚪ claude.ai không chuyển attachment tới MCP từ xa | — | chiều download/export: OPEN; upload nhị phân: OPEN (đề xuất fs_upload_* nếu chủ cần ảnh vào ui) |
| Vá GPT | 🟢 lock: không cần vá; docs: đã thêm mục hợp đồng (e8e7715, không restart) |
⚪ Codex cache/reconnect | — | — | — | idempotency GPT: OPEN |
| Tải phối hợp (mục 7) | 🟢 | — | 🟢 | — | 🟢 4 Claude + 3 GPT đồng thời đều OK, tải đỉnh 2,83/6 CPU, claude-mcp 79MiB/1G, agent-data 2,4G/6G, RAM trống không đổi, health OK | — |
| README kho chung (Shared Workspace Contract v1.0) | — | — | 🟢 ghi qua chính fs_edit (commit 8ba6a79) |
— | — | — |
OWNER CẦN QUYẾT (mỗi câu có sẵn đề xuất — gật/lắc)
- ✅ ĐÃ LÀM Ở VÒNG 2 (chủ đã duyệt) — xoay khoá nội bộ
MCP_UPSTREAM_KEY(nginx → container claude-mcp; không phải path-secret): tôi in nhầm file route nginx ra terminal CLI (path-secret đã che, dòngX-API-Keythì không). Rủi ro thấp (chỉ dùng được sau path-secret, trong mạng nội bộ). Đề xuất: gật → tôi xoay (.envclaude-mcp + route nginx trongsecrets/+nginx -s reload+ restart claude-mcp; ~1 phút) — cần duyệt vì đụng nginx. - Chứng tích thử ở gh
_luu-tru-chung-tich-claude/p09-*/(9 commit test trên main) và ui_luu-tru-chung-tich-claude/{fixture-p09,p09-*}— không có công cụ xoá. Đề xuất: giữ. - GPT idempotency (
operation_idchoworkspace_edit/workspace_transaction): Đề xuất: để Codex làm khi có quota; tôi KHÔNG đụng agent-data đợt này. - Upload nhị phân vào
uitừ Claude Chat (ảnh cho trang web): hiện chưa có đường; Đề xuất: chưa làm — chỉ làm nếu chủ thật sự cần (sẽ làfs_upload_begin/append/commitbase64, model phải chép từng mảnh = tốn token, không phải native). - Tài khoản test cho trang cần đăng nhập (ui_inspect/ui_act với profile): chưa có nguồn bí mật hợp lệ. Đề xuất: để OPEN cho tới khi có nhu cầu thật.
12.1 Giai đoạn 0 — đo live 05:40–06:00 UTC
- Không có việc dở của ai đang chạy: không tiến trình build/rsync/deploy; Codex hết quota; commit cuối của Codex
a483d09(03:56 UTC, "Deploy bounded MCP workers…");hardening-20260918-*ở/root/incomex-config-changes/sửa lúc 03:25–03:27 UTC (chỉreviewed.confcủa config-guard, không phải deploy đang chạy).incomex-mcp-helper.servicebị ai đó restart lúc 05:01 UTC (journald: Stopping→Started; file trợ lý không đổi, hash y nguyên bản 1e) — không phải tôi; ghi nhận, không kết luận. - Repo VPS
/opt/incomex: nhánhfeat/s177-sprint1-round-a, 170 mục dirty của bên khác (data/phai-cu/*,docs/mcp-writes/*do sổ ui,docs/mcp-writes-notebook/*) — không đụng; tôi chỉgit addđường dẫn cụ thể dướiclaude-kb/,claude-mcp/. Không có remote.agent-data-repo: nhánhcodex/mcp-hardening-20260918=bf64bbb, sạch; image chạyagent-data-hardening:20260918-final(tạo 03:47 UTC, healthy, 37 tool). Claude:claude-mcp-local:phase0.8-1e,claude-kb-local:v1(→ c5). 12 container, 0 lỗi. Đĩa 14G trống (86%), RAM 12G, tải ~0.4–0.7. - S1 đã có câu trả lời (đo bằng
docker inspectmount +realpath):
| resource_id | GPT root | Claude root | host realpath | remote/branch | chế độ commit/push | khoá |
|---|---|---|---|---|---|---|
ui |
ui = /workspace/ui rw, execution=true |
ui rw, vcs=git-local |
CÙNG THƯ MỤC /opt/incomex/docs/mcp-writes |
— (GPT: filesystem, không git) / Claude: sổ /var/lib/incomex/ui-preview-notebook.git + snapshot 5 phút |
GPT: atomic replace, không commit · Claude: commit trước+sau mỗi sửa | GPT: flock EX/SH trên /opt/incomex/data/workspace-tools/<sha256("ui")>.lock · Claude: flock trên /tmp/fsroots-ui.lock trong container ⇒ CHƯA CHUNG |
gh / workspace |
workspace = /workspace/state/github-workspace (git, main, push) |
gh (git-push, main) |
HAI CLONE RIÊNG: /opt/incomex/data/workspace-tools/github-workspace (GPT, 7fcff90) và /opt/incomex/mcp-roots/gh (Claude, 0142f57 — chậm hơn origin 1 commit, tự pull trước khi ghi) |
cùng git@github.com:Huyen1974/incomex-workspace.git main |
cả hai: pull/fetch → commit → push không force; conflict qua HEAD remote | không cần khoá chung (mỗi clone khoá riêng); bảo vệ = push non-force |
docs |
ro | ro | cùng /opt/incomex/docs |
— | — | không cần |
agent-data |
rw + exec | (không có; code chỉ đọc nhìn thấy nó qua /mnt/code/docker/agent-data-repo) |
/opt/incomex/docker/agent-data-repo |
git nhánh codex/…, không push | GPT only | GPT |
code |
— | ro | /opt/incomex |
— | — | không cần |
Hệ quả: khoá chung CHỈ cần cho ui. Cách rẻ nhất, không đụng agent-data: mount đúng file khoá của GPT vào container Claude và cho _RootLock('ui') dùng nó (cùng inode ⇒ flock chung); GPT ghi giữ EX, đọc giữ SH; Claude sẽ giữ EX khi ghi và SH khi đọc ui. Snapshot 5 phút của Claude gom cả thay đổi GPT ghi thẳng — hợp đồng: coi đó là lịch sử chung, không rollback.
- Nguồn tham chiếu đã đọc:
claude-mcp/00-NHAN-THU-MUC.md,app/*.py,helper/mcp_host_helper.py,claude-kb/app/server.py,agent-data-repo/docs/WORKSPACE_TOOLS.md,agent_data/workspace_tools.py(lock, page_result),workspace_transfer.py(result_read),agent_data/server.py(dispatcher)..claude/skills/incomex-rules.mdkhông tồn tại trên VPS (/opt/incomex/.claude/skills/không có) — ghi nhận. Audit GPTquy trình/ket-noi-gpt/audit.mdchỉ có trên Mac (chưa đọc — không cần cho phần đã làm).
12.2 C5 — Claude KB hết ngõ cụt — 🟢 LIVE 05:58 UTC
- Nguyên nhân đo được: agent-data gói MỌI kết quả >6000 ký tự (
response_page_chars) thành{result_id, content=6000 ký tự đầu, continuation_tool:"workspace_result_read"}; proxy Claude KB chặnworkspace_result_read(allowlist chỉ-đọc) ⇒ Claude Chat thấy đúng 6000 ký tự và hết đường. Ví dụ:get_document_chunk(max_chars=20000)của chính báo cáo này → 6000. - Sửa (chỉ
claude-kb/app/server.py, agent-data KHÔNG đổi): (1) proxy nhận gói → tự gọiworkspace_result_readnội bộ, lắp lại trọn kết quả, kiểm SHA256 agent-data khai; (2) trả cho mô hình theo cửa sổpage_chars(tham số thêm vào schema mọi tool KB; mặc định envKB_PAGE_CHARS=40000, trầnKB_PAGE_CHARS_CAP=1M) — vừa thì trả nguyên JSON gốc, dài thì{result_id, content, offset, total_chars, sha256, next_cursor, continuation_tool:"kb_result_read"}; (3) tool mớikb_result_read(result_id, offset, max_chars)chỉ đọc result trong sổ đăng ký của proxy (result do tool KB chỉ-đọc tạo qua chính proxy) — result của tool GPT (exec stdout, upload…) hay UUID đoán →RESULT_NOT_OWNED;workspace_result_readvẫn bị chặn/không quảng cáo; (4) annotationsreadOnlyHintcho 7 tool; (5)/readyz+ lỗiUPSTREAM_UNAVAILABLEkèmretry_after_secondskhi agent-data chưa sẵn sàng. Sổ đăng ký trong RAM (KB_RESULT_CACHE_BYTES48MB, TTLKB_RESULT_TTL_S7 ngày = TTL của agent-data; hết thân thì tự tải lại và kiểm lại SHA). Phạm vi quyền = principal đầu nối (mọi phiên Claude Chat dùng chung path secret này đều đọc được result của nhau — cùng chủ; không tách theo Mcp-Session-Id để reconnect giữa chừng không mất con trỏ). - Nghiệm thu 19/19 qua nginx → route thật (
/root/mcp-c5-stage/kb-c5-test-nginx.py, chỉ in số): tools/list 7 tool; chunk 20000 về trọn (20.647B, 0,1s);get_document_for_rewritebáo cáo này 33.489 ký tự vớipage_chars=10000→ 2 trang tới EOF, SHA khớp, JSON parse, body 32.765 ký tự;max_chars=1234đúng; offset quá EOF/result_idsai/UUID lạ/result do GPT tạo (page_resultgọi trong container agent-data) → từ chối rõ; gọi thẳngworkspace_result_read→ "Unknown tool"; mặc định 40k: 1 trang;get_document/search_knowledge/list_documents/batch_read(full=true, 2 tài liệu, 33.616 ký tự)đều tới EOF. - Triển khai: image
claude-kb-local:c5-20260918(Dockerfile.c5 = app/ chồng v1, không cài gì; build ~0 byte thêm), compose thêm 6 envKB_*có lý do; container healthy, hash/app/app/server.pytrong container = nguồn (39828457…). Lùi:claude-kb-local:rollback-pre-c5-20260918(= v1) +docker-compose.claude-kb.yml.bak-c5-20260918T055718Z+app/server.py.bak-c5-20260918T055718Z→compose up -d. Git VPS818c5ed. - Còn OPEN: Claude Chat/Cowork chưa chấm (chỉ CLI qua endpoint = PASS backend) — Fible đọc thử báo cáo này bằng
get_document_for_rewrite+kb_result_readtới EOF. Client cũ có thể cần "làm mới công cụ" để thấykb_result_read+page_chars.
12.3 ĐỢT 0.9 — S1–S3 + C1 + C2 + C6 — 🟢 LIVE 07:15 UTC (git VPS 195b7c8)
- Image
claude-mcp-local:phase0.9(Dockerfile.phase09 = app/ chồng phase0.5a, không cài gì); compose thêm mount file khoá của GPT/opt/incomex/data/workspace-tools/<sha256("ui")>.lock → /shared-locks/ui.lock:ro(flock không cần quyền ghi) + envFS_OPS_JOURNAL,FS_SEARCH_GENERATED,FS_TRANSACTION_MAX_OPS,HELPER_*,FS_WAIT_*(lý do cạnh từng biến). Trợ lý host:mcp_host_helper.pythêmgh_apply_commit_push(giao dịch nhiều file = 1 commit, hoàn tác cả file mới tạo),expected_head, dấu nhịp.helper-alive; script root + sudoers KHÔNG đổi (35/35 thử phá). Lùi:claude-mcp-local:rollback-pre-09-20260918(= phase0.8-1e) +docker-compose.claude-mcp.yml.bak-09-20260918T070751Z→compose up -d; trợ lý:/root/mcp-helper-rollback-09-20260918/mcp_host_helper.py+systemctl restart; tar toàn bộ trước sửa/root/mcp-c5-stage/claude-mcp-pre09-20260918T070751Z.tgz. Hash/app/app/fsroots.pytrong container = nguồn (6d98187f…). - 23 tool (18 cũ +
fs_stat,fs_copy,fs_transaction,ui_inspect,ui_act— đếm lại live 18/09 bằngtools/listcó bắt tay MCP đầy đủ; con số "21" ở các bản trước là SAI vì bỏ sót hai toolui_*), mọi tool cóannotations(readOnlyHint/destructiveHint/idempotentHint); lỗi trả[DENIED:<mã>] …để client rẽ nhánh (busy,version_mismatch,head_mismatch,outcome_unknown,helper_unavailable,secret_in_content,regex_unsupported…). - Hợp đồng đã cài (tóm tắt để ghi README kho chung): (1)
ui= một thư mục vật lý, một khoá flock chung — ghi EX, đọc SH, chờ ngắn rồi BUSY+retry; snapshot 5 phút của sổ ui gom cả thay đổi GPT = lịch sử chung, không rollback; (2)gh/workspace= hai clone, cùng main: pull/fetch → kiểmexpected_head/expected_version→ commit đúng file → push non-force; remote đổi → conflict có mã, đọc lại; (3) mọi ghi cóoperation_id: cùng id+cùng payload = replay, khác payload = từ chối, mất phản hồi = OUTCOME_UNKNOWN → gọi lại cùng id để giải quyết; (4) nội dung vào gốc công khai (gh PUBLIC, ui web) qua mọi đường ghi đều qua bộ quét bí mật (JWT/bearer/api-key/PEM/DSN/password/path-secret), gốccodekhông bao giờ là nguồn copy; (5) thư mục sinh tự động bị bỏ khi tìm,include_generated=trueđể quét hết. - Nghiệm thu (kịch bản
/root/mcp-c5-stage/p09-test.py, chạy trong container trênclaude_mcp_netqua nginx, chỉ in số/đầu dòng): 32/33 PASS — ca "FAIL" duy nhất là kịch bản đếm+++ b/trong khi git trích dẫn đường dẫn có dấu ("…/ghi-ch\303\272.md");git show --stat 359c0fdxác nhận 3 files changed, 1 commit. Unit 7/7 (tests/test_fsroots_phase09.py, chạy trên host). Khoá chung đo hai chiều (mục S1). Mất phản hồi: container thửFS_WAIT_PUSH_S=0.3→ lần 1–4 trợ lý từ chốiexpired(chưa nhận trước hạn ⇒ KHÔNG làm), lần 5outcome_unknown(trợ lý đang push), lần 6 resolvedcommit=358a3dd, lần 7 REPLAY;git logfile = 1 commit. Trợ lý dừng 35s → readiness DOWN,fs_list ghtrả sau 0,09s kèmhelper_unavailable,fs_read docsbình thường; bật lại → UP. - Số đo: fs_stat gh (kèm last_commit) 2,25s (pull throttle + 2 lượt trợ lý); fs_copy ui 0,35s; docs→gh push 3,5s; tx gh 3 file 4,9s; tx ui 3 op 1,2s; search cursor 7 cửa sổ cho 173 khớp; cả gốc
code13.421 file 23,3s (time_budget_s=60; mặc định 20s sẽ trả partial + cursor). - Chứng tích để lại: gh
_luu-tru-chung-tich-claude/p09-*/(8 commit trên main, kể cả 2 lần chạy), ui_luu-tru-chung-tich-claude/{fixture-p09/pixel.png, p09-*/}; nhật ký/var/log/mcp-audit/fs-ops.jsonl(+fs-ops-test.jsonlcủa container thử).
12.4 C3 — ui_inspect + ui_act — 🟢 LIVE 07:29 UTC (git 05a8613)
- Trợ lý host thêm hành động
browse(CDP qua pipe, hồ sơ trình duyệt tạm mỗi lần, không cookie): mở 1 trang thuộcvps.incomexsaigoncorp.vn→ (tuỳ chọn) chạy danh sách bước từ vựng cố địnhclick | hover | type | press | scroll_to | wait(không nhận JavaScript; mỗi bước có trace ok/ms/url_after; selector không khớp → dừng rõ; chuyển hướng ra host khác → dừngurl_not_allowed) → thuoutline(title, headings, landmarks/form, links, controls — password che***, ảnh, đếm),text(innerText theomax_text, báo tổng/cắt),styles(computed + box),console(error/warning/exception),network(request hỏng / HTTP ≥400; URL có tham số bí mật bị che, không header/body) → (tuỳ chọn) ảnh PNG selector / viewport / full_page ≤8000px. Container:ui_inspect(readOnlyHint, KHÔNG có tham số actions) vàui_act(destructiveHint — có thể gửi form; chỉ dùng trang thử). - Nghiệm thu 19/19 (
/root/mcp-c5-stage/c3-test.py) trên fixtureui/_luu-tru-chung-tich-claude/fixture-p09/test-modal.html(ghi bằngfs_write): h1 có dấu đúng; modal ẩn không lọt vào text; console error + 2×404 (json, png) bắt được; click mở modal + ảnh selector 1264×149px;type+press Entergửi form cùng origin (?name=Huy%E1%BB%81n) và text "Xin chào Huyền"; selector sai →[DENIED:selector_not_found]; click link ngoài →[DENIED:url_not_allowed]; host lạ → từ chối. Cũng gọi được qua cầu nối Claude Code (client thật, không phải Claude Chat). Trần mới:BROWSE_MAX_ACTIONS=30,BROWSE_MAX_TEXT=200000,BROWSE_MAX_ITEMS=500 (env cả 2 phía, lý do ghi cạnh) — là trần kỹ thuật của một lần gọi, có đường đọc tiếp qua nhiều lần gọi/selector.
12.5 C4 — chuyển file (khảo sát, chưa dựng thêm)
- Claude Chat / Cowork → MCP từ xa: claude.ai KHÔNG truyền attachment/resource handle tới tool của remote MCP (chỉ tham số JSON do mô hình điền) ⇒ không có "native transfer" tương đương
openai/fileParamscủa GPT. Cowork đọc được file cục bộ nhưng vẫn phải chép nội dung vào tham số = model-relayed. Kết luận: native = NOT_APPLICABLE cho Claude; đường có sẵn:fs_write(UTF-8 tới 20MB/lần, đo 19,8MB ở đợt 0.7),fs_copyserver-side giữ byte (kể cả nhị phân) trong VPS; chưa có đường đưa file nhị phân từ máy người dùng vào ui/gh qua Claude (OPEN #4). - Chiều download/export ra client: MCP có thể trả blob (EmbeddedResource) nhưng claude.ai chỉ hiển thị ảnh; text thì
fs_read. OPEN, không dựng kho lưu trữ mới.
12.6 Tải phối hợp (mục 7) — 🟢
Chạy đồng thời: Claude fs_search cả gốc code (3.000 khớp, 3,8s) + ui_act click + ảnh full_page (3,3s) + fs_read ui (0,15s) + fs_search ui (0,17s); GPT (đường mã thật trong container) workspace_search (4,3s), workspace_read ui (0,0s — khoá SH chung không cản), ui_inspect Playwright (8,8s). Đỉnh: tải 2,83/6 CPU; claude-mcp 79MiB/1G, cpu 9%; agent-data 2,38G/6G, cpu 74% (Playwright); claude-kb 49MiB; RAM trống 7,3G trước/sau; đĩa 14G; 12 container healthy. Không BUSY, không lỗi. Không giả lập cạn đĩa.
12.7 Tài liệu
- README kho chung: mục Shared Workspace Contract — v1.0, 2026-09-18 (7 điều: tài nguyên, khoá, phiên bản, commit/push, retry & mất phản hồi, xuất công khai, chi phí) — commit
8ba6a79ghi bằng chínhfs_edit(expected_version). GPT:docs/WORKSPACE_TOOLS.mdthêm mục trỏ + ghi chú khoáuichung (e8e7715trên nhánh codex, chỉ tài liệu, không restart). Claude:claude-mcp/00-NHAN-THU-MUC.md(2cb27bc). Audit gốc của GPT (quy trình/ket-noi-gpt/audit.mdtrên Mac) chưa cập nhật — không truy cập từ VPS; phần thay đổi phía GPT chỉ là tài liệu.
12.8 Cách quay lui cả đợt (thứ tự ngược)
- claude-mcp:
docker-compose.claude-mcp.yml.bak-09-20260918T070751Z→ chép đè →compose up -d(imagerollback-pre-09-20260918= phase0.8-1e; mount khoá chung/env mới thành trơ). 2. Trợ lý:/root/mcp-helper-rollback-09-20260918/mcp_host_helper.py→systemctl restart incomex-mcp-helper(container cũ không dùng menu mới). 3. claude-kb:.bak-c5-20260918T055718Z+ imagerollback-pre-c5-20260918. 4. README/WORKSPACE_TOOLS/00-NHAN: revert commit tương ứng. Không có gì đợt này chạm agent-data image/compose/.env, nginx, Directus, PG.
12.9 Nợ / OPEN tổng hợp
- N15 GPT
operation_id(Codex). N16 ca cross A/B GitHub (GPT ghi thật) + ca "cùng thư mục ui hai đường ghi thật" từ client thật — Fible/GPT. N17 Claude Chat/Cowork chưa chấm C5/C1/C2/C3 (chỉ Claude Code + endpoint). N18MCP_UPSTREAM_KEY(quyết định #1). N19 upload nhị phân từ Claude / download ra client (quyết định #4). N20 profile đăng nhập (quyết định #5). N21 ghi chú cho Fible: sau restart container, cầu nối Anthropic có thể trả 502 vài phút — đợi rồi gọi lại, không phải lỗi VPS. N22client_max_body_size 0trên route claude-mcp (ai đó đã đổi) ⇒ lý do trầnFS_WRITE_MAX_BYTES=20MB ("nginx 50m") không còn đúng — có thể nới khi cần (xin phép chủ theo luật).
12.10 VÒNG 2 — đóng nợ môi trường phối hợp — 2026-09-18 (Claude Code, Opus 5)
Prompt chốt "VÒNG 2: ĐÓNG NỢ MÔI TRƯỜNG PHỐI HỢP GPT + CLAUDE" + 2 hiệu chỉnh của chủ (agent-data: chỉ được sửa docs/WORKSPACE_TOOLS.md; staging upload: đo, không xoá). Mọi dòng 🟢 dưới đây đều gọi thật, không suy từ mã.
| Hạng mục | Trước | Sau | Test thật (backend / Claude Chat / GPT client) | |
|---|---|---|---|---|
| Nguồn luật của kho chung | HAI nguồn: README v1.0 + AGENTS.md (tự nhận "ưu tiên cập nhật chính file này") | MỘT: README v1.1 là hợp đồng; AGENTS.md rút còn cửa vào 17 dòng | backend: ghi bằng chính fs_transaction (f21132a), đọc lại bằng fs_search/fs_stat / ⚪ / ⚪ |
🟢 |
| Ba đường ghi | không được mô tả ở đâu | §0: bảng 3 đường + 5 luật riêng cho GitHub native (SHA hiện tại, biết HEAD trước + diff sau, hoà giải không ghi đè, không thay giao dịch nguyên tử, tự kiểm bí mật) | backend 🟢 / ⚪ / ⚪ | 🟢 |
| Quyền xoá | không có điều khoản (GitHub native có delete_file) |
§7: delete_file chỉ khi chủ duyệt TỪNG LẦN; dọn dẹp = rename vào _luu-tru-chung-tich-*/ |
backend 🟢 / ⚪ / ⚪ | 🟢 |
Bảo vệ nhánh main |
GPT báo ruleset rỗng + 403 ⇒ chưa rõ có thật không | CÓ THẬT (đo bằng GitHub API quyền admin): allow_force_pushes=false, allow_deletions=false, enforce_admins=true, secret scanning + push protection BẬT. Ruleset rỗng là bình thường — chốt chặn nằm ở branch protection cổ điển |
backend 🟢 / — / — | 🟢 |
| Giới hạn body route claude-mcp | client_max_body_size 0 = KHÔNG giới hạn (nợ N22) |
50m — đúng trần thiết kế ghi trong app/limits.py, bằng mức server-level của conf.d/default.conf |
backend 🟢: 1 KB và 29,5 MiB và 42,9 MiB LỌT · 55,3 MiB → HTTP 413 | 🟢 |
| — cơ sở chọn 50m (đo thật, không ước lượng) | — | yêu cầu ghi 20 MB tiếng Việt sau JSON-escape ensure_ascii = 32,34 MB |
backend 🟢 — 32m mà tôi đặt lúc đầu là QUÁ THẤP, đã sửa lên 50m trước khi chốt | 🟢 |
MCP_UPSTREAM_KEY |
khoá cũ (tôi in nhầm ra terminal 18/09 — nợ N18) | đã xoay. Rà 2 cách (tên biến + giá trị): 11 file chứa, 3 nơi sống đã cập nhật (claude-mcp/.env, route nginx ×2, .deploy-secrets), 8 nơi còn lại là bản lưu/lịch sử |
backend 🟢: khoá cũ → 401, khoá mới → 200, qua nginx initialize → 200 / Claude Code 🟢 nối lại được ngay / — |
🟢 |
| Snapshot 5 phút (người ghi THỨ BA) | chỉ khoá riêng, chờ 60s, hết hạn = unit FAILED | thêm khoá CHUNG vùng ui (chia sẻ, cùng inode 1313457), chờ 15s rồi BỎ LƯỢT, thoát mã 0 |
backend 🟢 đo thật: bên kia giữ khoá → bỏ lượt sau 15s, HEAD không đổi (303 commit), log SKIP ban-khoa; nhả khoá → commit đủ (304); c7b7aa6 vẫn là tổ tiên của f2cee6f |
🟢 |
| config-guard | FAILED liên tục, MISMATCH=3 (kb-server-py, kb-compose, mcp-compose) |
MISMATCH=0 · STATUS=CLEAN, dịch vụ hết FAILED |
backend 🟢. Nguồn gốc 3 chỗ lệch đã truy: đúng 818c5ed (C5) + 195b7c8 (0.9) của vòng 1, không ai chen vào. Chuông vẫn kêu: giả lệch mode → MISMATCH MODE, hoàn nguyên → CLEAN |
🟢 |
| Bộ đếm test | 32/33 — 1 ca "FAIL" | 33/33 — ca đó là BÁO ĐỎ GIẢ của bộ đếm, sản phẩm luôn đúng | backend 🟢: trên commit thật 359c0fd, bộ đếm cũ +++ b/ = 2, bộ đếm mới +++ "?b/ = 3 + nhận ra tên có dấu. Chạy lại thật qua client: 43d3f84 = 3 file / 1 commit |
🟢 |
| Idempotency phía GPT | OPEN, chưa có bản bàn giao | bản bàn giao kỹ thuật đủ cho Codex, viết vào đúng một tài liệu docs/WORKSPACE_TOOLS.md (23214cb) |
— (chỉ tài liệu; agent-data không restart, vẫn Up 6 hours (healthy)) |
⚪ Codex |
| Luật chi phí | 1 dòng chung | §9 thêm 2 dòng đo thật: soi bằng CHỮ trước (ảnh 479 KB/lần vs ui_inspect 2 KB/lần, rẻ hơn ~240 lần); ưu tiên thao tác server-side |
backend 🟢 (không đo lại — dùng số Fible đã đo) | 🟢 |
| Khoá chung GPT↔Claude | cùng inode 1313457 | vẫn cùng inode sau khi tạo lại container để nạp khoá mới | backend 🟢 (host 1313457 = trong container 1313457, mount ro còn nguyên) | 🟢 |
| Hồi quy Claude MCP | — | fs_stat · fs_copy (tên đích có dấu) · fs_transaction · fs_search cursor · ui_inspect · ui_act · readiness |
🟢 qua cầu nối Claude Code (client thật) / Claude Chat ⚪ / — | 🟢 backend |
| KB continuation | PASS vòng 1 | đọc 52.859 ký tự / 2 trang tới EOF, SHA khớp, has_more=false, next_offset=null |
🟢 qua Claude Code / Claude Chat ⚪ | 🟢 backend |
| Dung lượng staging (yêu cầu bổ sung của chủ) | chưa đo | 62 MB tổng (uploads 56 KB/13 file · results 3,2 MB/58 file · github-workspace 17 MB) — 0 file quá 7 ngày | backend 🟢. Đĩa 86% là do backups 5,9G + deploys 2,9G + docker 1,7G + context-pack.tmp 1,5G, không phải cơ chế mới |
🟡 xem OPEN #3 |
[CHỈNH 1 — sửa một dòng sai của bản GPT.] 5 công cụ fs_stat/fs_copy/fs_transaction/ui_inspect/ui_act KHÔNG phải "đã được Claude Code + Claude Chat kiểm". Đúng là: PASS backend + PASS qua cầu nối Claude Code; client Claude Chat/Cowork = ⚪ OPEN. Phiên Claude Chat của Fible mở trước lúc container restart nên giữ bộ schema cũ 18 tool — đây là bộ nhớ đệm schema phía client, không phải lỗi.
12.11 OWNER CẦN QUYẾT (gật hoặc lắc)
- Hai route khác vẫn
client_max_body_size 0:claude-kb-route.conf(3 chỗ) vàgpt-mcp-route.conf(2 chỗ) — cùng lỗ hổng vừa vá cho claude-mcp, nhưng nằm ngoài phạm vi vòng này nên tôi không tự đụng. Đề xuất: gật cho tôi đặtclaude-kb= 50m (cùng lý do, cùng cổng config-guard); còngpt-mcplà route của đầu nối GPT → hỏi Codex/GPT trước khi đổi. - Chứng tích thử của vòng 2:
/root/v2-bodytest/63 MB (chỉ là file toàn chữ 'a' để thử 413) +/root/v2-keyrotate/(khoá mới, phải giữ 600) + các bản.bak-*. Tôi không xoá gì (luật vòng này). Đề xuất: gật cho xoá riêng/root/v2-bodytest/(rác thuần), giữ toàn bộ.bak-*và/root/v2-keyrotate/. - GC staging chưa được CHỨNG MINH: đo hôm nay 0 file quá 7 ngày, nên không thể nói TTL/GC chạy đúng — chỉ nói được là chưa âm thầm làm đầy đĩa. Đề xuất: đo lại sau 25/09/2026 (khi lô file hôm nay quá 7 ngày); nếu lúc đó
uploads/resultsvẫn giữ file cũ thì mới là nợ thật. - Idempotency phía GPT: Đề xuất: để Codex làm theo bản bàn giao ở
docs/WORKSPACE_TOOLS.md; tôi tiếp tục không đụng agent-data. - Chứng tích thử tiếp tục tích tụ trên
main(_luu-tru-chung-tich-claude/v2-20260918/, 2 commit vòng 2). Đề xuất: giữ; hợp đồng §7 nay đã ghi rõ dọn dẹp = rename, không xoá.
12.12 Đã đổi những gì
- GitHub
Huyen1974/incomex-workspace(main):e11c2a1(cờ đang-chỉnh trên AGENTS.md, đóng cửa sổ tranh chấp) →f21132a(hợp đồng v1.1 + AGENTS.md cửa vào, mộtfs_transaction= một commit) →43d3f84(chạy lại ca giao dịch 3 file để chấm bộ đếm). - Repo VPS
/opt/incomex(nhánhfeat/s177-sprint1-round-a, không remote):5d821b8—evidence/v2-snapshot-lock-20260918/(bản TRƯỚC/SAU của script + số đo). - Repo agent-data (nhánh
codex/mcp-hardening-20260918, không push):23214cb— chỉdocs/WORKSPACE_TOOLS.md. - File hệ thống:
/opt/incomex/docker/nginx/secrets/claude-mcp-route.conf(3 lần qua cổng config-guard: 32m → xoay khoá → 50m),/opt/incomex/claude-mcp/.env(qua cổng),/opt/incomex/claude-mcp/.deploy-secrets(ngoài registry, sửa tay + backup),/usr/local/bin/mcp-writes-git(ngoài registry — đã đếm = 0 trongregistry.tsv, nên không sinh drift mới),/root/mcp-c5-stage/p09-test.py. - Bản chuẩn config-guard khai lại cho
kb-server-py,kb-compose,mcp-compose(nội dung candidate y hệt bản sống — khai lại, không sửa nội dung). Ghi chú kỹ thuật: validatorDOCKER_COMPOSE_CONFIGkhông chạy được ở thư mục candidate vì thiếu.envcạnh file; tôi đặt một.envrỗng trong thư mục thay đổi để validator chạy được — không nới luật: bước kiểm bản SỐNG (docker compose config -qtrên đúng thư mục) vẫn chạy đủ và PASS, và hai compose không dùng nội suy${VAR}nào. - Dịch vụ:
nginx -s reload×3 (do chính config-guard chạy,nginx -tsạch trước và sau mỗi lần);incomex-claude-mcptạo lại 1 lần (bắt buộc —env_filechỉ đọc lúc tạo container,docker restartsẽ giữ khoá cũ), healthy sau 9s;incomex-claude-kbđược config-guard chạm qua post-action allowlist (không đổi nội dung). Không đụng: agent-data, Directus, PostgreSQL, Qdrant, Nuxt, cowork-*.
12.13 Đường lùi từng lớp (thứ tự ngược)
- Tài liệu kho chung:
git revert f21132a(vàe11c2a1) trên main. - Bàn giao Codex:
docs/WORKSPACE_TOOLS.md.bak-20260918T100241Zhoặcgit revert 23214cb. - Bộ đếm test:
/root/mcp-c5-stage/p09-test.py.bak-20260918T095922Z. - Snapshot:
cp -p /usr/local/bin/mcp-writes-git.bak-20260918T095258Z /usr/local/bin/mcp-writes-git(không cần restart gì — oneshot theo timer). - Bản chuẩn config-guard:
/var/lib/incomex-config-guard-v0/rollback/<target>.<mutation-id>.pre(guard tự lưu trước mỗi lần áp). - Khoá + giới hạn body: chép
claude-mcp-route.conf.bak-20260918T094608Z(bản khoá CŨ +0) và.env.bak-20260918T094608Z+.deploy-secrets.bak-20260918T094919Zvề chỗ cũ →nginx -s reload→docker compose -f /opt/incomex/claude-mcp/docker-compose.claude-mcp.yml up -d --no-build --no-deps --force-recreate claude-mcp. Lưu ý: lùi bằng cách chép tay sẽ sinh drift cho config-guard — lùi xong phải khai lại bản chuẩn quaincomex-config-apply-v0. - Khoá mới nằm ở
/root/v2-keyrotate/newkey(chmod 600). Chưa bao giờ in ra terminal, log, báo cáo hay commit.
12.14 Còn OPEN thật sự
N15 / GPT idempotency— ĐÓNG 18/09/2026 (vòng 3, xem §12.16):operation_idđã chạy thật cho cả 5 tool ghi phía GPT.- N17 / Claude Chat + Cowork chưa chấm độc lập: C5 KB, C1 (
fs_stat/fs_copy/fs_transaction), C2 search cursor, C3 (ui_inspect/ui_act), readiness. Tất cả đều PASS backend + PASS qua cầu nối Claude Code. Phiên mới của Fible cần "làm mới công cụ" để thấy đủ 23 tool — đây là OPEN phía client, KHÔNG phải backend FAIL. - N16 / ca cross A/B GitHub hai chiều cần GPT ghi thật — Fible + GPT.
- N19 upload nhị phân từ Claude Chat / download ra client; N20 profile đăng nhập cho
ui_act— đều chờ nhu cầu thật. Hai route còn— vòng 3: route KB đã đặt 50m (đóng); route GPT rà xong, có kết luận + đề xuất, chờ Codex/GPT xác nhận (§12.16, §12.18). GC staging chưa chứng minh — hẹn đo lại sau 2026-09-25 (§12.16).client_max_body_size 0cloud-init.service và systemd-networkd-wait-online.service— ĐÓNG 18/09/2026 với kết luận VÔ HẠI / HOÃN kèm bằng chứng (§12.17).- N18 ĐÓNG (khoá đã xoay). N22 ĐÓNG (
client_max_body_size= 50m). Drift 3 mục ĐÓNG (MISMATCH=0).
12.15 Chờ ai chấm độc lập
- Fible / Claude Chat (phiên MỚI): gọi thật
fs_stat,fs_copy,fs_transaction,fs_searchcó cursor,ui_inspect,ui_act,vps_status(muc='readiness'), và đọc chính báo cáo này tới EOF bằngget_document_chunk+kb_result_read. ĐọcREADME.mdcủa kho chung để xác nhận hợp đồng v1.1 đến được client. - GPT / Codex: đọc lại
docs/WORKSPACE_TOOLS.md(mục bàn giao), rồi ca cross A/B GitHub hai chiều với Claude; sau đó làmoperation_id.
12.16 VÒNG 3 — đóng dứt điểm hạng mục kết nối GPT ↔ GitHub ↔ VPS — 2026-09-18 (Claude Code, Opus 5)
Prompt hợp nhất (nền GPT + 8 chỗ [CHỈNH] của Fible). Mọi dòng dưới đây gọi thật, không suy từ mã.
operation_id phía GPT nay ĐÃ XONG — nợ N15 đóng.
| Hạng mục | Trước | Sau | Test thật (backend / Claude Chat / GPT client) | Trạng thái |
|---|---|---|---|---|
operation_id cho 5 tool ghi |
OPEN — chỉ có bản bàn giao cho Codex | Có trong schema của đúng 5 tool ghi (workspace_edit, workspace_write_new, workspace_copy, workspace_move, workspace_transaction), tuỳ chọn, không tool nào bắt buộc |
backend 🟢 48/48 ca ngoại tuyến + 32/32 ca HTTP thật có commit+push thật · Claude Chat ⚪ không áp dụng · GPT client ⚪ chờ chủ/GPT tự chấm | 🟢 ĐÓNG (backend) |
| — replay cùng id + cùng nội dung | commit đôi | trả kết quả cũ, replayed:true, HEAD không nhúc nhích |
🟢 gọi thật cả 5 tool | 🟢 |
| — trùng id, khác nội dung | không có khái niệm | OPERATION_ID_REUSED, không file nào đổi |
🟢 gọi thật cả 5 tool | 🟢 |
| — mất phản hồi | có thể commit lần hai | client bị cắt thật (curl rc=28, không nhận gì) → máy chủ vẫn commit+push xong → gọi lại cùng id trả đúng commit đó, không commit thứ hai; GitHub xác nhận refs/heads/main |
🟢 ca thật, không giả lập bằng hàm nội bộ | 🟢 |
| — giao dịch nhiều file | — | 2 file = một commit; replay không sinh commit thứ hai; ca cố tình hỏng việc thứ 3 → không file nào đổi | 🟢 gọi thật | 🟢 |
| — bền qua restart | — | thao tác xong trước restart vẫn REPLAY sau restart; chốt chặn trùng id cũng sống sót | 🟢 5/5 sau một lần restart | 🟢 |
| — client cũ | — | không truyền operation_id thì hành vi y hệt cũ |
🟢 có ca kiểm riêng | 🟢 |
| Chốt dung lượng route GPT | client_max_body_size 0 |
KHÔNG ĐỔI vòng này — đã rà xong, có kết luận + đề xuất, chờ Codex/GPT xác nhận theo đúng [CHỈNH 5] |
🟢 đo 63.824 mẫu log: p50=334B, p90=381B, p99=979B, gói hợp lệ lớn nhất 22.110.499 B (~21 MiB) | ⚪ OPEN (chờ GPT/Codex) |
| Chốt dung lượng route KB | client_max_body_size 0 |
50m (chủ đã duyệt) | 🟢 request thường qua · 49 MB qua được · 55 MB → 413 trong 0,06 s | 🟢 ĐÓNG |
Worktree agent-data |
1 mục chưa theo dõi (bản lưu 13.579 B) | sạch — bản lưu chuyển (không xoá) ra /opt/incomex/backups/agent-data-docs-20260918/, sha256 giữ nguyên 5cacf3e0… |
🟢 git status --short rỗng |
🟢 ĐÓNG |
| Hai dịch vụ khởi động FAILED | FAILED, chưa rõ nguyên nhân | VÔ HẠI / HOÃN — xem 12.17 | 🟢 đo live: SSH, DNS, HTTPS ra ngoài, 12 container đều chạy | 🟢 ĐÓNG (kết luận có bằng chứng) |
| Hồi quy phía GPT | 37 tool | vẫn 37 tool, không hụt cái nào | 🟢 gọi thật workspace_list/stat/search/read/edit/transaction/result_read/task_start+status/exec+status/ui_inspect; từ chối bản cũ (VERSION_CONFLICT), từ chối chuỗi khớp nhiều chỗ (EXACT_MATCH_REQUIRED), quá tải trả WORKSPACE_BUSY chứ không treo, không force-push |
🟢 ĐÓNG |
| Tốc độ | nền cũ | sửa+push chậm hơn ~1,3 % (5,008 s → 5,072 s) — trong khoảng nhiễu, xa ngưỡng 2× | 🟢 A/B có/không operation_id, 3 lượt mỗi bên |
🟢 |
| [CHỈNH 5.2] rác thử vòng 2 | /root/v2-bodytest/ 162 MB (5 tệp toàn chữ a) |
đã xoá đúng thư mục đó | 🟢 đã kiểm nội dung là chữ độn trước khi xoá; /root/v2-keyrotate/ và 12 bản .bak-* giữ nguyên |
🟢 ĐÓNG |
| [CHỈNH 5.3] hai cửa chưa ai canh | 32 mục | 34 mục: thêm hostbin-mcp-writes-git và secrets-claude-mcp-deploy |
🟢 MISMATCH=0; và chứng minh biết kêu: sửa thử mcp-writes-git → MISMATCH CONTENT, khôi phục → CLEAN, sha256 trùng khít |
🟢 ĐÓNG |
[CHỈNH 5.4] bẫy .env rỗng |
kiểm bản nháp bằng .env rỗng |
incomex-config-apply-v0: bản nháp compose có ${ mà thiếu .env thật → từ chối, không kiểm bằng .env rỗng |
🟢 ca giả: ${KB_HOSTNAME} + .env rỗng → REJECTED_UNSAFE_CANDIDATE; có .env thật → qua; dịch vụ sống không hề bị đụng |
🟢 ĐÓNG |
| [CHỈNH 8] số tool phía Claude | lúc 21, lúc 23 | 23 — đã sửa mọi chỗ trong §12 | 🟢 đếm lại live bằng tools/list có bắt tay MCP: 23 (18 cũ + fs_stat, fs_copy, fs_transaction, ui_inspect, ui_act) |
🟢 ĐÓNG |
Nhánh / commit / image. Theo [CHỈNH 2], không ghi thẳng vào nhánh Codex:
nhánh riêng claude/gpt-operation-id-20260918 tách từ codex/mcp-hardening-20260918;
e365079 (mã operation_id) + 26bdca3 (tài liệu). Không push, không merge vào nhánh Codex.
Codex hoà giải sau bằng git merge claude/gpt-operation-id-20260918 hoặc cherry-pick đúng hai commit đó.
Lưu ý sửa một điểm trong prompt: repo agent-data-repo có remote (origin → Huyen1974/agent-data-test),
không phải "không có remote" — nhưng vòng này vẫn không push gì lên đó.
Image agent-data-hardening:20260918-opid dựng bằng lớp mỏng chồng (Dockerfile.opid, một lớp COPY ~50 KB)
chứ không build lại 1,87 GB; image cũ :20260918-final giữ nguyên để lùi; compose lưu
docker-compose.yml.bak-pre-opid-20260918. Kho chung: README.md đổi đúng dòng trạng thái
operation_id (commit 58ed6905) — ghi bằng chính workspace_edit có operation_id.
[CHỈNH 1] Đĩa. Trước build: 96G, dùng 83G, còn 14G (86%). Vì chọn lớp mỏng chồng nên phần tăng thêm ~50 KB chứ không phải 1,87 GB → cổng "còn dưới 10G thì DỪNG" không chạm. Sau build: vẫn 14G trống. Không xoá gì để lấy chỗ, không cài thêm gói nặng.
[CHỈNH 4b] Restart. agent-data restart đúng hai lần, đều sau khi xác nhận không có
tác vụ nền / upload dở / phiên GPT đang chạy (13 upload đều committed, 0 bản ghi dở, 0 giao dịch treo):
một lần để nạp image mới, một lần dành riêng cho ca thử "bền qua restart" — ca này bắt buộc phải có
restart thật mới chứng minh được. Không dịch vụ nào khác bị restart (nginx chỉ reload, vẫn Up 2 weeks).
Sau mỗi lần: healthy trong ~1 phút, 37 tool, tools/list đáp trong 0,064 s.
Sổ trạng thái operation_id. state_dir()/operations.jsonl — cùng thư mục với sổ audit sẵn có,
tức ngoài mọi gốc workspace, nằm trên bind mount host /opt/incomex/data/workspace-tools nên sống qua restart.
Ghi nối tiếp có fsync, quyền 0600; yêu cầu chỉ lưu dưới dạng băm chuẩn hoá (bỏ operation_id trước khi băm).
Trạng thái preparing → committed/applied/rolled_back/unknown. Mã lỗi và ngữ nghĩa đúng bằng phía Claude
(OPERATION_ID_REUSED, OUTCOME_UNKNOWN, RECOVERY_REQUIRED) để hai đầu nối cùng một luật (README §5).
Một bẫy đã trả giá trong chính vòng này, ghi lại để đời sau khỏi vấp. Bộ đếm commit đầu tiên của tôi
đọc git_log từ kết quả workspace_log — nhưng kết quả đó bị phân trang, nên git_log không có trong
trang đầu và bộ đếm luôn trả 0. Mọi khẳng định "không sinh commit thứ hai" khi ấy đều PASS vì lý do sai.
Đã chuyển sang đếm thẳng bằng git rev-list --count HEAD trên repo thật và đối chiếu ls-remote với GitHub.
⚠️ Đo bằng chính công cụ đang thử thì phải tính đến phân trang, nếu không phép đo sẽ nói dối theo hướng dễ chịu.
[CHỈNH 8] "2 shells still running". Đó là tiến trình nền của Claude Code trong phiên trước (mấy lệnh
theo dõi log nginx), không phải tiến trình trên VPS. Chúng sống theo phiên và đã tắt cùng phiên đó.
Đã soát lại VPS vòng này: không còn tail -f / docker logs -f / journalctl -f nào sót.
Tiến trình python chạy lâu duy nhất là mow-table2-state-server.py — dịch vụ có sẵn từ trước, không liên quan.
[CHỈNH 5.5] HẸN ĐO LẠI SAU 2026-09-25. Phần tự dọn rác (GC) của staging upload / result tạm chưa chứng minh được
vì lô tệp hiện tại chưa quá completed_result_ttl_seconds (7 ngày). Sau 2026-09-25, phiên nào chạm vùng này
phải đo lại: đếm tệp trong /opt/incomex/data/workspace-tools/{uploads,results} và xác nhận lô ngày 18/09 đã tự biến mất.
Nếu còn nguyên thì GC hỏng thật, không phải "chưa tới hạn".
12.17 Hai dịch vụ khởi động FAILED — kết luận có bằng chứng
Cả hai hỏng đúng một lần duy nhất, tại lần khởi động duy nhất của máy: 2026-02-12 10:16:52 và 10:19:52. Máy chạy liên tục từ 2026-02-12 10:16:41 tới nay (7 tháng 5 ngày, chưa reboot lần nào). Nhật ký hệ thống của lần khởi động đó đã xoay vòng, nhưng log riêng của cloud-init còn giữ nguyên nhân.
cloud-init.service— hỏng ởbootcmdtrong cloud-config của nhà cung cấp. Ba lệnh, lệnh cuối làpkill -HUP sshd. Lúcbootcmdchạy thì sshd chưa khởi động,pkillkhông khớp tiến trình nào nên trả mã 1, kéo cả đoạn/bin/shtrả 1 → cloud-init ghi nhận thất bại. Đã đo lại:pkillkhông khớp →rc=1, đúng cơ chế đó. Lỗi mẫu của nhà cung cấp, không phải lỗi của đầu nối. Các giai đoạn sau của cloud-init chạy 0 lỗi. (Ghi thêm: ý định củabootcmdlà bật đăng nhập root bằng mật khẩu; thực tếsshd_configđang làPermitRootLogin prohibit-password— tức an toàn hơn ý định đó, và ta vào bằng khoá nên không ảnh hưởng.)systemd-networkd-wait-online.service—eth0ở trạng tháiroutable (configuring); bản vá netplan bắt chờeth0routable, networkd không bao giờ báo "đã cấu hình xong", nên unit hết giờ sau 1 phút 1,9 giây.
Vì sao vô hại — đo, không đoán: network-online.target không có unit nào Requires= nó (chỉ
rescue-ssh.target, dùng ở chế độ cứu hộ). After= chỉ là thứ tự, không phải điều kiện, nên mọi dịch vụ xếp sau
nó vẫn khởi động bình thường — và thực tế đều đang chạy: docker.service, hermes-*, incomex-mcp-helper,
lark-mcp-remote, cùng 12 container. Mạng hiện tại đầy đủ: eth0 có IPv4+IPv6, có default route, DNS phân giải
được, curl https://github.com trả 200, SSH đang dùng.
Sẽ tái diễn ở lần khởi động sau không? Gần như chắc chắn có, vì cả cloud-config lẫn netplan đều không đổi. Nhưng hậu quả vẫn chỉ là hai dòng FAILED trong bảng, không mất chức năng nào.
Đề xuất (chỉ cần chủ gật/lắc, KHÔNG làm vòng này): để nguyên. Muốn bảng sạch thì sửa trong cửa sổ bảo trì có
reboot — đổi pkill -HUP sshd thành pkill -HUP sshd || true, và cân nhắc bỏ Required For Online cho eth0.
Không systemctl reset-failed để che: che đi thì lần sau không ai biết nó từng hỏng.
⚠️ Một việc riêng, không thuộc nhiệm vụ này nhưng thấy thì phải báo: cloud-config của nhà cung cấp tại
/var/lib/cloud/instance/cloud-config.txt chứa mật khẩu root dạng chữ thường (không chép lại ở đây).
Tệp thuộc quyền root. Nếu mật khẩu đó vẫn còn hiệu lực thì nên đổi trong một việc riêng — xin ý kiến chủ.
12.18 Chốt dung lượng route GPT — đã rà, KHÔNG đổi vòng này, kèm bằng chứng
[CHỈNH 5] nói rõ: route KB thì chủ đã duyệt, còn route GPT phải hỏi Codex/GPT trước. Codex hết quota,
không hỏi được, nên vòng này chỉ rà và kết luận, không tự đổi cấu hình route sống của GPT.
Hiện trạng đo được. gpt-mcp-route.conf: client_max_body_size 0 kèm proxy_request_buffering off,
proxy_buffering off, client_body_buffer_size 1m, timeout 300 s. Khác hẳn route Claude: ở đó 50m là con số
dẫn xuất từ FS_WRITE_MAX_BYTES 20 MB × 2 (chữ tiếng Việt escape \uXXXX) = 40 MB < 50m. Phía GPT cố ý
không có trần logic ("no arbitrary logical size/count ceilings", Owner 2026-09-18 ghi ngay trong mã).
Kích thước thật, 63.824 mẫu log nginx: p50 = 334 B · p90 = 381 B · p99 = 979 B ·
gói hợp lệ (status 200) lớn nhất = 22.110.499 B (~21,1 MiB). Hai gói 45 MB/31 MB trong log là rác thử
của vòng 2 (status 400) — đã xoá cùng /root/v2-bodytest/.
Vì sao đây là phơi nhiễm thật, không phải lo hão. proxy_request_buffering off nghĩa là nginx không đệm
thân request xuống đĩa — rủi ro "đầy đĩa" cổ điển không áp dụng. Nhưng backend đọc toàn bộ thân request
vào bộ nhớ bằng await request.json() (server.py, /mcp-gpt-full), không có chốt chặn riêng. mem_limit
là 6 GB, đang dùng ~2,1 GB → dư ~3,9 GB. Một request đơn lẻ vài GB sẽ giết bằng OOM đúng incomex-agent-data,
tức đường làm việc của ChatGPT. Với client_max_body_size 0, nginx sẵn sàng chuyển tiếp thân request bao nhiêu cũng được.
Vì sao vẫn KHÔNG đổi vòng này. (a) [CHỈNH 5] bắt hỏi Codex/GPT trước; (b) đặt trần sai chỗ sẽ làm hỏng
workspace_upload_append — kích thước từng mẩu base64 do client tự chọn, không phải máy chủ quy định;
(c) workspace_import_file (luồng đính kèm của ChatGPT) không đi qua route này — bytes được stream thẳng
từ phía máy chủ, nên một cái trần ở đây không bảo vệ gì cho luồng đó.
Đề xuất để chủ chỉ cần gật/lắc (một câu): đặt client_max_body_size **64m** cho route GPT —
rộng gấp ~3 lần gói hợp lệ lớn nhất từng đo (21,1 MiB), thừa chỗ cho phần phình base64 (×4/3) và JSON-escape
tiếng Việt (×2), mà vẫn chặn được một request đơn lẻ làm cạn 3,9 GB bộ nhớ còn lại. Trần logic của tệp
không đổi (vẫn không giới hạn: vẫn còn attachment gốc, upload nối tiếp, đọc tiếp).
Nếu Codex/GPT nói kích thước mẩu upload của họ có thể vượt 64 MB thì nâng theo số họ đưa, đừng đoán.
Gật thì làm trong ~5 phút, có đường lùi, không cần restart (chỉ nginx -s reload qua incomex-config-apply-v0).
§13. Hardening 2026-09-19 — RUN_ID HARDEN-20260919-01
Người làm: Claude Code (Opus 5). Work order: PROMPT.md @079cdb13643cd96051169dd28fed993a07181bbc
trong Huyen1974/incomex-workspace; READY khớp đúng 40 ký tự SHA của commit cuối chạm PROMPT.md
(kiểm lại lần hai lúc repo đã tiến lên f65fe21: PROMPT.md không bị chạm sau READY ⇒ READY còn hiệu lực).
Đích của đợt: bỏ bốn loại lỗi "im lặng" — đọc bản cũ mà không biết · từ chối mà không nói lý do · báo thành công giả · ghi đôi. Mỗi lỗi đều tái hiện bằng test đỏ trước khi sửa (§0.4 của work order).
13.1 Bảng trạng thái
| Mã | Phía | Trạng thái | Bằng chứng một dòng |
|---|---|---|---|
| H01 | Claude | 🟢 | fs_log/fs_diff nay pull trong cùng một yêu cầu với lượt đọc; kết quả có dòng NGUỒN: đã pull ngay trước khi đọc · source_head=847c231 |
| H02 | Cả hai | 🟢 | đọc quyết định (stat/log/diff) bỏ qua cache; đọc thường trần 2 s cả hai phía (_PULL_TTL_S 20→2, read_refresh_seconds() clamp ≤2) |
| H03 | GPT | 🟢 | workspace_stat không còn bị loại khỏi bước kéo; worktree bẩn ⇒ worktree_dirty:true + head local + remote_head, refreshed_at:null |
| H04 | GPT | 🟢 | lỗi git mang phase (fetch · fast-forward · add · diff-check · commit · push · rollback) + event_id tra được trong audit.jsonl; lệnh mạng không hề trả output/URL |
| H05 | GPT | 🟢 | hai dấu cách cuối dòng + dòng trống cuối file ⇒ THÀNH CÔNG kèm warnings, byte giữ nguyên; <<<<<<</=======/>>>>>>> ⇒ vẫn CHẶN kèm path:line |
| H06 | GPT | 🟢 | no-op ⇒ changed:false, commit:null, không ghi/không push, HEAD không nhích; hỗn hợp ⇒ files chỉ có file thật sự đổi |
| H07 | Cả hai | 🟢 | cùng id + payload y nguyên nhưng khác tool ⇒ OPERATION_ID_REUSED nêu tên cả hai tool; trước đó fs_move/workspace_move phát lại kết quả của copy và không hề chạy |
| H08 | Cả hai | 🟢 | hoàn tác không sạch ⇒ recovery_required (không còn rolled_back/failed), không tái thực thi, nêu rõ file cần kiểm |
| H09 | GPT | 🟢 | exec kéo mới + ghim source_head trước khi xếp hàng; worker chụp dưới khoá chia sẻ của gốc (cùng inode với đầu nối) và trả source_head THỰC TẾ đã chụp; đo thật: source_head=04a0676, source_head_moved:false, exit 0 |
| H10 | GPT | 🟢 | workspace_diff(from_version) nay đọc được cả bản lưu của transaction (trước đó chỉ thấy bản ghi của edit đơn ⇒ mọi thay đổi nhiều file vô hình) |
| H11 | GPT | 🟢 | locked() chờ có hạn + backoff, cấu hình resources.lock_wait_seconds; đo thật trên khoá ui DÙNG CHUNG: người giữ 2 s ⇒ đọc chờ 1,9 s rồi PASS; giữ quá hạn ⇒ WORKSPACE_BUSY + retry_after_seconds=5, dừng đúng 5,0 s, không treo. Vòng chờ phía Claude đã kiểm: có sẵn, giữ nguyên |
| H12 | Cả hai | 🟢 | GPT: CONNECTOR_SCHEMA_VERSION 20260918.1→20260919.1, SERVER_VERSION 1.3.0 thay "1.0.0" cố định ở cả 3 chỗ — đo trên máy chủ ĐANG CHẠY. Claude: SDK không có chỗ đặt serverInfo.version ⇒ công bố qua vps_status(muc='readiness') |
| H13 | GPT | 🟢 | metrics thêm cột phase + bảng errors theo tool × mã lỗi × phase (trước đó chỉ có một con số tổng) |
| H14 | Cả hai | 🟢 | GPT: workspace_exec/workspace_task_start nhận operation_id, upload_begin TIẾP TỤC bản dở thay vì bỏ lại bản mồ côi. Claude: đã an toàn sẵn ⇒ ghi bằng chứng bằng 3 ca test, không sửa |
13.2 Nghiệm thu backend A–G (§5 của work order)
| Ca | Trạng thái | Bằng chứng một dòng |
|---|---|---|
| A độ tươi chéo | 🟢 | Claude đẩy d5620de; lời gọi GPT đầu tiên ngay sau đó là workspace_stat → thấy đủ 62 B + source_head=d5620de; chiều ngược: GPT đẩy 847c231, Claude fs_log/fs_diff lạnh thấy ngay, fs_diff đọc được commit clone chưa từng có |
| B snapshot dưới khoá | 🟢 | ca test: giữ khoá ghi ⇒ snapshot KHÔNG bắt đầu (chờ), thả khoá ⇒ chạy tiếp ⇒ không có trạng thái lai. Đo thật trên hệ đang chạy: workspace_exec kéo mới, ghim source_head=04a0676, worker chụp dưới khoá chia sẻ, trả đúng commit đó, exit 0 |
| C lỗi minh bạch | 🟢 | C-khoang-trang.md ghi được + 2 warnings (:3 trailing whitespace, :5 new blank line at EOF), byte y nguyên (sha a1f21ac39ee4); C-conflict.txt bị chặn CONFLICT_MARKERS_PRESENT · phase=diff-check · 3 path:line, không để lại file; expected_version cũ ⇒ VERSION_CONFLICT có lý do đầy đủ |
| D giao dịch | 🟢 | no-op ⇒ changed:false HEAD không nhích; 2 file ⇒ đúng 1 commit; lỗi ở op #2 ⇒ không file nào được tạo, HEAD không nhích; push bị từ chối ⇒ hoàn tác đủ (hook pre-receive thật, trong bộ test); diff(from_version) sau transaction ⇒ đọc được trước/sau (H10) |
| E idempotency | 🟢 | hai phía: cùng id+tool+payload ⇒ replay đúng một commit; payload khác ⇒ từ chối; tool khác ⇒ từ chối; hoàn tác bẩn ⇒ không tái thực thi; mất phản hồi ở điểm tạo việc ⇒ không tạo việc trùng (H14) |
| F khoá | 🟢 | giữ ngắn hơn hạn ⇒ đọc tự chờ 1,9 s rồi PASS; giữ quá hạn ⇒ WORKSPACE_BUSY + retry_after_seconds=5 sau đúng 5,0 s, không treo. Đo trên chính file khoá dùng chung với đầu nối Claude (d3ef7de5…lock) |
| G schema | 🟢 | hỏi máy chủ ĐANG CHẠY (GET /mcp, không suy từ mã): version=1.3.0 · connector_schema_version=agent-data-hardening-20260919.1 · connector_schema_hash=f6a27f2622ff · 37 tool, trong đó workspace_exec và workspace_task_start đã có operation_id trong schema. Claude: readiness hiện claude-mcp-harden-20260919.1 · 23 tool · vân tay fca7e350ffd2 |
13.3 Nguyên nhân gốc và chỗ đã sửa
H01 — fs_log/fs_diff đọc clone tại chỗ. app/fsroots.py gọi trợ lý host để đọc sổ git, nhưng
khác fs_list/read/stat/search, hai hàm này không có bước _freshen. Sửa ở trợ lý, không phải ở đầu nối:
helper/mcp_host_helper.py thêm refresh_for_read() + read_freshness(), và git_log/git_diff/git_head
nhận refresh:true ⇒ pull --ff-only rồi đọc trong CÙNG một yêu cầu, nên không ai chen được vào giữa
hai bước. Trả thêm refreshed · head · refreshed_at. fsroots.fs_log/fs_diff truyền refresh:true
và in _source_note(...) — một dòng nói rõ đọc tại commit nào và có pull được hay không.
H02 — hai bên già đi khác nhau. _PULL_TTL_S mặc định 20 s → 2 s, có trần cứng _PULL_TTL_CAP_S
để cấu hình không kéo cao hơn được. fs_stat gọi _freshen(force=True) (trị số version của nó được
người gọi đưa thẳng vào fs_edit làm expected_version, nên đây là đọc quyết định). Phía GPT:
read_refresh_seconds() clamp giá trị cấu hình vào [0, 2]; SAFETY_READS = (stat, log, diff) luôn kéo mới,
bỏ qua TTL.
H03 — workspace_stat bị loại khỏi bước kéo. agent_data/workspace_tools.py, hàm điều phối call()
có hai chỗ viết name != 'workspace_stat'. Thay bằng refresh_for_read(name, root, spec): mọi read trên gốc
git đều đi qua đó. Worktree bẩn thì không merge đè (prepare_git không chạy) — workspace_stat vẫn trả
lời nhưng nói thẳng worktree_dirty:true + head (local) + remote_head + refreshed_at:null; các read còn
lại vẫn DIRTY_WORKTREE. Kết quả mọi read git nay mang source_head + refreshed_at.
H04 — mọi lỗi git gom thành một mã. git() cũ: fail('GIT_OPERATION_FAILED', 'Operation failed; …'),
bỏ stderr, không ghi log. Nay git_phase(args) suy phase từ chính câu lệnh (không call site nào quên được),
mã lỗi cụ thể theo phase (GIT_FETCH_FAILED, GIT_FAST_FORWARD_FAILED, GIT_ADD_FAILED, GIT_COMMIT_FAILED,
GIT_ROLLBACK_FAILED…), git_detail() che secret + URL (lệnh mạng thì không trả output), git_event()
ghi một dòng git_failed có event_id vào audit.jsonl và chỉ event_id đi ra ngoài. WorkspaceError
mang thêm phase/event_id, call() đưa hai trường đó vào phong bì lỗi.
Cùng nhóm này: 12 chỗ w.fail(...) trong workspace_operations.py trước đây trả detail RỖNG — đo thật
trên đầu nối đang chạy mới thấy (VERSION_CONFLICT ⇒ detail:""). Nay mỗi chỗ nêu path, version trước/sau,
và với ROLLBACK_CONFLICT thì nêu danh sách file người vận hành phải kiểm + số hiệu transaction.
H05 — một chốt chặn cho hai việc khác nhau. git diff --cached --check báo lẫn "trailing whitespace" /
"new blank line at EOF" với "leftover conflict marker", và cả ba đều làm ghi thất bại. Đây đúng là lỗi đã
chặn giao dịch của Host (P05): một dòng Markdown ngắt cứng = hai dấu cách cuối dòng. Nay staged_check()
chạy --check với check=False, phân loại từng dòng kết quả: nhóm khoảng trắng → warnings trả về cho
người gọi (không chặn, không tự sửa byte); conflict marker → CONFLICT_MARKERS_PRESENT kèm
phase=diff-check và path:line. Dùng ở cả write() và transaction().
H06 — changed=bool(after). after chứa mọi path của mọi op, kể cả op cho ra đúng byte đang có. Nay
real = {p for p,d in after.items() if d != before.get(p)}: path không thật sự đổi thì không ghi (mtime
không nhích), không stage, không commit, không push; changed=bool(real), files chỉ liệt kê real, và
no-op có thêm detail giải thích. reset/add khi hoàn tác cũng chỉ chạm real.
H07 — khoá idempotency thiếu tên tool. fs_copy và fs_move (và workspace_copy/workspace_move)
nhận bộ tham số giống nhau từng byte, nên khoá chỉ theo payload thì một id dùng cho copy sẽ phát lại
kết quả của copy cho move, và move không hề chạy — đo thật: fs_move trả về text của fs_copy, file nguồn
vẫn nằm nguyên chỗ cũ trong khi người gọi tin là đã di chuyển. Sửa: so prior['tool'] trước khi so hash,
lệch ⇒ OPERATION_ID_REUSED nêu tên cả hai tool. Giữ nguyên hash cũ (không trộn tên tool vào hash) để các id
đã ghi trong journal trước đợt này vẫn replay đúng.
H08 — hoàn tác bẩn bị ghi là thất bại sạch. Phía GPT: wrap() bắt mọi WorkspaceError và ghi
status='rolled_back' với chú thích "a clean refusal changed nothing, so the same id stays usable" — nhưng
ROLLBACK_CONFLICT nghĩa là hoàn tác không sạch. Đo thật: journal ghi
{'status': 'rolled_back', 'error': 'ROLLBACK_CONFLICT'}. Nay có danh sách UNCLEAN (ROLLBACK_CONFLICT,
RECOVERY_REQUIRED, OUTCOME_UNKNOWN, GIT_ROLLBACK_FAILED, OPERATION_FAILED) → ghi recovery_required kèm
paths (rút từ path/from/to/operations), và replay_or_none từ chối id đó bằng RECOVERY_REQUIRED
nêu đúng file cần kiểm. find() cho recovery_required quyền vượt trên dòng rolled_back muộn hơn.
Phía Claude: fs_transaction ghi status='failed' dù undo_problems khác rỗng (dòng ~1638). Nay
problems khác rỗng ⇒ journal recovery_required + ToolError('recovery_required', …) nói rõ "trên đĩa đang
là trạng thái LAI" và liệt kê file; _run_write không còn hạ cấp trạng thái đó xuống failed.
13.3b Nguyên nhân gốc và chỗ đã sửa — P1/P2
H09 — snapshot của exec không kéo mới, không giữ khoá. scripts/workspace-exec-worker.py
đi thẳng vào os.walk(source) rồi shutil.copy2, nên (a) cây có thể cũ hơn remote và (b) một giao dịch
nhiều file đang ghi dở sẽ bị chụp nửa vời. Chia làm hai chỗ, vì hai tiến trình khác nhau:
kéo làm ở đầu nối (workspace_execution.execute, w.locked(root) + prepare_git) rồi ghim
source_head vào chính bản ghi công việc — worker chạy user khác, cho nó chạy git trong clone là đúng
cái bẫy quyền file mà §0.3 của work order cảnh báo; chụp làm dưới cùng file khoá đầu nối dùng
(read_lock() trong worker tính đường dẫn bằng digest(tên gốc) trên q.base, tức cùng inode —
đã đo: cả container và worker đều chạy quyền root, 21a3230e…lock là một file). Khoá bao trọn: đọc HEAD →
đo dung lượng → đi cây → sao .git; không bao docker run (không giữ khoá gốc suốt lúc job chạy).
Kết quả trả source_head (commit THẬT đã chụp), source_head_at_queue và source_head_moved.
Giữ quá hạn chờ ⇒ xếp hàng lại, không bao giờ chụp một cây lai.
Đổi hành vi có chủ ý: workspace_exec trên gốc git nay từ chối khi clone bẩn (prepare_git), giống
mọi read và mọi write khác của đầu nối — không thể ghim một commit trên cây bẩn.
H10 — diff(from_version) không thấy giao dịch. Bản gốc (before-image) nằm ở hai chỗ: một file
<id>.before cho mỗi edit đơn, và một thư mục transactions/<tid>/<index> cho mỗi giao dịch. diff() chỉ
quét chỗ thứ nhất ⇒ mọi thay đổi nhiều file vô hình với diff. Nay quét cả hai và chọn bản mới nhất; manifest
lưu before là digest trần còn from_version của gốc git là head:digest, nên so bằng phần sau dấu :.
VERSION_NOT_RETAINED nay nói rõ vì sao.
H11 — locked() thử một lần rồi BUSY. README luôn hứa "chờ ngắn rồi trả BUSY", nhưng mã gọi
flock(..., LOCK_NB) đúng một lần. Nay chờ tới resources.lock_wait_seconds (mặc định 5 s) với backoff
0,05→0,5 s (không busy-spin), rồi WORKSPACE_BUSY kèm retry_after_seconds và lý do nêu cả ba người
có thể đang giữ khoá. WorkspaceError mang thêm retry_after nên con số đi ra ngoài là số thật, không còn
là 1 cứng. Không có chỗ nào lồng locked() trong locked() — đã soát cả 5 điểm gọi.
H12 — client giữ danh sách tool cũ. serverInfo.version phía GPT bị ghim "1.0.0" ở ba chỗ suốt
năm lần đổi schema, nên một client cache danh sách cũ không có cách nào biết. Nay SERVER_VERSION một chỗ,
CONNECTOR_SCHEMA_VERSION nâng cùng commit, kèm comment ghi rõ quy trình phát hành (nâng version →
dựng lại → làm mới client: ChatGPT Refresh app, Claude ngắt/kết nối lại connector + chat mới → kiểm
tools/list, không tin suông). Phía Claude, FastMCP.__init__ của SDK này không có tham số version
(đã kiểm chữ ký hàm), nên thay vì vá SDK hay đổi tên dịch vụ, bản dựng được công bố trong
vps_status(muc='readiness'): BUILD_ID + số tool + vân tay hash của (tên + mô tả) mọi tool, kèm một câu
nói thẳng "số liệu lệch = client đang giữ danh sách cũ". Không khai tools.listChanged: máy chủ không
thật sự gửi thông báo đó, khai ra là hứa hão.
H13 — metrics chỉ đếm tổng. Bảng metrics có cột error nhưng metrics() chỉ trả sum(error IS NOT NULL).
Thêm cột phase (có ALTER TABLE bảo vệ để bảng cũ giữ nguyên dữ liệu), call() ghi result.get('phase'),
và metrics() trả thêm errors nhóm theo tool × mã lỗi × phase kèm last_at.
H14 — mất phản hồi ở điểm tạo việc. Rà bốn điểm:
workspace_execvàworkspace_task_startsinh id ở máy chủ: mất phản hồi ⇒ người gọi không cójob_id/task_id⇒ việc thành mồ côi mà vẫn chạy, và một lần gọi lại tạo việc thứ hai. Nay cả hai nhậnoperation_id: trả lời lần gọi lại trước khi làm bất cứ gì (kể cả trước bước kéo), cùng payload ⇒ trả đúng việc đã xếp hàng (replayed:true), khác payload ⇒OPERATION_ID_REUSED.workspace_upload_begincũng sinh id ở máy chủ: nay cùng(root, path, sha256, total_bytes)mà bản cũ cònuploadingthì TIẾP TỤC bản đó (đúng nghĩa resumable) kèmoffset, thay vì bỏ lại một bản mồ côi chờ GC 7 ngày. Nhánh này nằm sau mọi chốt chặn version/FILE_EXISTS.workspace_import_filegọibegin+commitbên trong: đã an toàn sẵn (FILE_EXISTS/expected_versionchặn ghi đôi); chỉ tốn công tải lại. Không sửa.- Phía Claude: đã an toàn sẵn, KHÔNG sửa (work order H14 cho phép "ghi bằng chứng"). Mọi lượt ghi vào
gốc git-push đi qua MỘT yêu cầu tới trợ lý có
ridbền trên đĩa; trợ lý trả lời không kịp ⇒ journalunknownkèmrid; lần gọi lại cùngoperation_idđối chiếuhostq.answer_for(rid): trợ lý đã làm xong ⇒ trả kết quả, không ghi lần hai; trợ lý từ chối ⇒ chưa đổi gì nên id được phép chạy. Ba ca test khoá đúng ba nhánh đó lại.
13.4 Test mới
| Tệp | Số ca | Nội dung |
|---|---|---|
agent-data-repo/tests/continuation/test_hardening_20260919.py |
16 | H02–H08 trên git thật + remote thật (clone thứ hai đẩy commit, hook pre-receive từ chối push, git bị monkeypatch để dựng đúng tình huống hoàn tác bẩn); 2 ca chặn "từ chối không nói lý do" |
agent-data-repo/tests/continuation/test_hardening_20260919_p1.py |
14 | H09–H14: snapshot phải CHỜ người ghi nhả khoá (hai luồng, khoá thật); diff(from_version) sau transaction / edit đơn / copy; khoá chờ rồi PASS và BUSY kèm retry_after; version schema (đọc mã nguồn, không import agent_data.server vì import đó dựng client Qdrant); errors theo tool/mã/phase; 4 ca H14 |
claude-mcp/tests/test_fsroots_hardening_20260919.py |
12 | H01, H02, H07, H08 phía đầu nối (trợ lý host thay bằng bản ghi lại lời gọi); 3 ca bằng chứng H14; 2 ca H12 |
claude-mcp/tests/test_helper_freshness_20260919.py |
5 | H01/H02/H03 ở trợ lý host, git thật + remote thật |
Một khoảng trống đã tự lộ ra và đã bịt. Bộ test test_fsroots_hardening_20260919.py thay trợ lý bằng bản
ghi lại lời gọi, nên nó chứng minh đầu nối xin làm mới, chứ không chứng minh trợ lý thật sự pull. Trong
lượt chạy này một script sửa file phía trợ lý bị dừng giữa đường (assert trượt trước khi ghi ra đĩa) ⇒ sửa đổi
phía trợ lý mất, mà toàn bộ test vẫn xanh và commit d14e8e0 chỉ vào được 1 dòng. Phát hiện khi đối chiếu
git show --stat với việc mình nghĩ đã làm. Đã thêm test_helper_freshness_20260919.py — git thật, remote
thật — để lần sau chính test bắt được, không phải mắt người. Sửa đúng nằm ở e5132db.
Tổng số ca của đợt này: 47 (16 + 14 phía GPT, 12 + 5 phía Claude).
Bộ test cũ: phía Claude xanh hết — tests.test_fsroots_phase09 7/7, tests.test_is_binary 7/7 (hai
module Claude phải chạy riêng từng module; chạy chung một tiến trình thì hỏng do allowlist đặt lúc
import — lỗi có sẵn từ trước, không do đợt này). Phía GPT tests/continuation/: 63 xanh / 1 đỏ
(64 ca = 34 cũ + 30 mới). Ca đỏ duy nhất là ca CŨ test_background_operation_survives_initial_call,
đỏ vì ngân sách chờ — đã truy nguyên ở 13.6b và chứng minh không phải hồi quy của đợt này.
13.5 Commit mã
Trong /opt/incomex/docker/agent-data-repo (đầu nối GPT):
a40afbbH02,H03,H04,H05 ·workspace_tools.py04c04b2H05,H06 ·workspace_operations.py5dfb0feH07,H08 ·workspace_idempotency.py+ 14 ca test đỏ-trước1c98415H04 · mọi từ chối của transaction phải nói lý do + 2 ca test2a73e6fH10,H11 ·workspace_tools.py(diff đọc bản lưu transaction; khoá chờ có hạn + backoff)781a0beH13,H14 ·workspace_runtime.py(cộtphase, bảngerrors,find_by_operation)3b51627H09,H14 ·scripts/workspace-exec-worker.py+workspace_execution.py62a20b4H14 ·workspace_tasks.py+workspace_transfer.pyfa621eaH12 ·server.py+ 14 ca test P1/P2 đỏ-trước3a7f9bb·Dockerfile.harden20260919(công thức dựng overlay)
Trong /opt/incomex (đầu nối Claude; lưu ý repo này có sẵn 174 tệp bẩn không phải của đợt này —
mọi commit dưới đây đều git add/git commit theo đường dẫn chỉ định, không add -A):
d14e8e0H01,H02 · trợ lý host — commit thiếu, chỉ vào được 1 dòng (xem 13.4)43046d1H01,H02,H07,H08 ·app/fsroots.py+ 7 ca teste5132dbH01,H02,H03 · trợ lý host, bản sửa đủ + 5 ca test git thật5483e0dH12,H14 ·app/hosttools.py(BUILD_ID + vân tay trongreadiness) + 5 ca test015d07d· con trỏ image trong hai compose +claude-mcp/Dockerfile.harden20260919(xem cảnh báo ở 13.6)
13.6 Dịch vụ đã dựng và deploy
Cả hai container chạy mã trong image (đã kiểm: md5sum mã trong container == mã trên host), nên phải
dựng lại image; theo đúng nếp đã có (Dockerfile.phase08, Dockerfile.opid) dùng overlay mỏng một lớp
COPY, không cài gói, không cần mạng.
| Dịch vụ | Image mới | Lúc | Sức khoẻ | Đường lùi |
|---|---|---|---|---|
incomex-mcp-helper.service |
(chạy trực tiếp tệp trên host) | 2026-09-19 00:25:40 CEST | active |
/opt/incomex commit 5d821b8 |
incomex-claude-mcp |
claude-mcp-local:harden-20260919 (Dockerfile.harden20260919) |
00:25:4x | healthy < 2 phút |
image claude-mcp-local:rollback-pre-harden-20260919 (= phase0.9) + docker-compose.claude-mcp.yml.bak-harden-* |
incomex-agent-data |
agent-data-hardening:20260919-harden (Dockerfile.harden20260919) |
00:25:48, dựng lại ~00:37 cho 13.3/H04 | healthy < 2 phút |
image agent-data-hardening:rollback-pre-harden-20260919 (= 20260918-opid) |
Điều kiện §4 trước khi restart incomex-agent-data đã đo trước khi làm (cả hai lần): hàng đợi job rỗng
(không job queued/running/claimed), không manifest prepared/push_unknown/rollback_conflict,
không bản ghi ghi-đơn nào ngoài trạng thái kết thúc.
Deploy vòng 2 (P1/P2), 2026-09-19 ~01:30 CEST. Dựng lại cả hai image, restart trợ lý host, và
restart incomex-workspace-exec.service. Cả hai container healthy trong < 2 phút; bốn dịch vụ active.
Sau khi dựng đã đối chiếu md5sum từng tệp giữa mã trong container và mã trên host — 8/8 và 2/2 khớp.
🔴 Hai điều Host/Owner cần biết về vòng deploy này:
incomex-workspace-exec.servicekhông nằm trong danh sách được phép restart ở §4 của work order, nhưngscripts/workspace-exec-worker.pylại nằm trong phạm vi sửa ở §1, và bản sửa H09 chỉ có hiệu lực sau khi restart. Đã restart với đúng điều kiện áp choincomex-agent-data(hàng đợi rỗng — đã đo). Nêu ra đây để Host/Owner xác nhận cách hiểu, không phải để lấp liếm.- Overlay
Dockerfile.harden20260919phía GPT ban đầu chỉ COPY 3 tệp (chép theo nếpDockerfile.opid), trong khi vòng P1/P2 đụng 8 tệp ⇒ lần dựng đầu, 5 tệp sửa không vào image. Bước đối chiếumd5sumcontainer-vs-host bắt được ngay; đã liệt kê đủ 8 tệp rồi dựng lại và đối chiếu lại. Vẫn liệt kê tên tệp tường minh thay vìCOPY agent_data/cả thư mục, để__pycache__và tệp rác AppleDouble._*trong cây làm việc không bao giờ lọt vào image.
Con trỏ image trong compose. docker/docker-compose.yml dòng 189 và
claude-mcp/docker-compose.claude-mcp.yml dòng 46 được nâng sang tag mới — đây là cách deploy của chính nếp
này. Đã commit ở 015d07d cùng hai Dockerfile.harden20260919, để không còn gì nửa chừng.
⚠ Lưu ý cho Host: dòng trong docker/docker-compose.yml đã bẩn từ trước khi đợt này bắt đầu (vòng trước
nâng 20260918-final → 20260918-opid mà chưa commit), nên commit 015d07d mang theo cả lần nâng chưa
commit của vòng trước; thông điệp commit nói rõ điều đó.
Cách hoàn nguyên nếu cần (§4 cho phép đúng một đường lùi): đổi hai dòng image: về
claude-mcp-local:rollback-pre-harden-20260919 và agent-data-hardening:rollback-pre-harden-20260919,
docker compose up -d, và git revert các commit [Claude Code] liệt kê ở 13.5 (trong đó
scripts/workspace-exec-worker.py cần systemctl restart incomex-workspace-exec.service).
13.6b Một ca test CŨ đang ĐỎ — không phải do đợt này
tests/continuation/test_workspace.py::test_background_operation_survives_initial_call trượt. Đã truy
đến cùng thay vì báo suông:
- Ca đó chỉ chờ 5,0 s (100 vòng × 0,05 s) cho một việc nền. Việc nền phải spawn hai tiến trình con
(supervisor rồi worker), mỗi tiến trình import cả gói
agent_data. - Đo thật trên máy đang tải (load average ≈ 4, do chính các lượt chạy test của đợt này): việc nền
chạy ĐÚNG —
status=completed, nội dung trả về chính xác — nhưng mất 11,8 s, tức quá ngân sách 5 s. - A/B để loại trừ đợt này: lấy
workspace_tasks.pynguyên bản từgit show HEADchạy 3 lượt → trượt cả 3; bản của đợt này chạy 3 lượt → cũng trượt cả 3, thời gian như nhau. ⇒ không phải hồi quy của đợt này. (Lần bisect đầu tiên chỉ chạy 1 lượt và tình cờ xanh, suýt dẫn đến kết luận sai; ghi lại để đừng tin một lượt chạy.) - Không sửa ca test đó: nó không thuộc phạm vi work order (§6 cấm mở rộng). Đề nghị cho Host: nới ngân sách chờ của ca này, hoặc cho nó chờ theo tín hiệu thay vì đếm vòng.
13.7 Việc còn chờ
- Chờ nghiệm thu ở client thật (không phải việc của Agent — §8 của work order): làm mới hai client theo
quy trình phát hành, rồi Claude Chat kiểm
fs_log/fs_difflạnh +fs_transaction+operation_id; GPT Chat kiểm gọi thẳngworkspace_edit/workspace_transactioncóoperation_id,workspace_statlạnh, và cảnh báo khoảng trắng. Chưa có gì được gọi là "production ready" khi client chưa PASS. - Một dòng để Host biết:
COLLAB.mdmục Prompt vẫn ghiOwner giao chạy: chưa. Owner đã ra lệnh RUN trực tiếp cho Agent trong phiên này; §6 cấm Agent sửaCOLLAB.mdnên dòng đó để Host cập nhật.
13.8 Chứng tích thử trong repo chung
Toàn bộ commit thử nằm trong _thu-nghiem/hardening-20260919/, không xoá (§5), tác giả
"Claude via MCP" (phía Claude) hoặc "AI via Incomex Workspace" (phía GPT):
d5620de · 847c231 (A, độ tươi chéo) · 4510fb0 · d03153a (C, khoảng trắng + version cũ) ·
a0ec25e · 11b5fc1 · d37cb68 (D, giao dịch) · a044f66 · f42248e (E, idempotency phía GPT) ·
ce486ef · 04a0676 (E, idempotency phía Claude).
10 tệp: A-do-tuoi-cheo-claude.txt · A-do-tuoi-cheo-gpt.txt · C-khoang-trang.md · D-mot.txt ·
D-hai.txt · D-that-su-moi.txt · E-idem.txt · E2-copy.txt · E-claude-idem.txt · E-claude-copy.txt.
AGENTS.md · README.md · COLLAB.md · PROMPT.md không bị chạm (kiểm bằng
git log 34371d8..origin/main -- <4 tệp>: chỉ có hai commit của Host, không có commit nào của Agent).
13.9 K10 — journal operation_id sống qua TIẾN TRÌNH MỚI (RUN K10-PERSIST-20260919-02)
Người làm: Claude Code (Opus 5). READY kiểm trên bản sao riêng dưới /tmp:
READY@83b3a07749d79f84481723dddb72619d70b4d9ee khớp commit cuối chạm PROMPT.md; kiểm lại lần hai lúc
repo đã tiến lên 2fc3cf6 (P09/P10) — PROMPT.md không bị chạm sau READY ⇒ READY còn hiệu lực.
Không restart production chỉ để thử; không sửa runtime.
13.9.1 Bằng chứng (a) — journal nằm ngoài mọi gốc, trên bind mount của host
Đo bằng docker inspect trên container ĐANG CHẠY, không suy từ compose:
| Phía | Journal / state | Bind mount host → container | Gốc có chứa nó? |
|---|---|---|---|
| Claude | FS_OPS_JOURNAL=/var/log/mcp-audit/fs-ops.jsonl |
/var/log/mcp-audit → /var/log/mcp-audit rw=true |
không (gh · ui · docs · code) |
| GPT | state_dir=/workspace/state ⇒ operations.jsonl và queue.sqlite |
/opt/incomex/data/workspace-tools → /workspace/state rw=true |
không (ui · docs · workspace=/workspace/state/github-workspace · agent-data) |
docker-compose.yml phía GPT là 0600 nên không đọc được qua gốc code — đó là lý do dùng
docker inspect chứ không đọc compose. Phía Claude compose đọc được (dòng 88 + dòng 115).
Đã sống qua restart thật, không chỉ đúng cấu hình. Container hiện tại khởi động
incomex-claude-mcp 2026-09-18T23:13:54Z · incomex-agent-data 2026-09-18T23:16:38Z; dòng ĐẦU TIÊN còn
trong journal là 2026-09-18T07:12:13Z (Claude) và 2026-09-18T12:05:00Z (GPT) — tức cũ hơn lần khởi
động ~16 h và ~11 h. Journal đã đi qua restart, không phải nằm trong lớp ghi của container.
13.9.2 Bằng chứng (b) — tiến trình A ghi → A CHẾT → tiến trình B MỚI phát lại
Hai bộ test độc lập, mỗi lượt gọi là một python3 riêng (subprocess.run đợi, nên lượt sau chắc chắn
bắt đầu khi lượt trước đã chết); chỗ duy nhất hai lượt chia sẻ là file journal / state tạm. Lý do phải
là tiến trình thật: cả hai đầu nối đọc lại journal từ đĩa mỗi lần gọi, nên một ca cùng tiến trình vẫn
xanh dù trạng thái chỉ nằm trong RAM — nó không chứng minh được điều K10 cần.
| Test | Đường dẫn | Ca | Kết quả |
|---|---|---|---|
| Claude | claude-mcp/tests/test_fsroots_persistence_k10_20260919.py |
8 | 🟢 Ran 8 tests · OK |
| GPT | agent-data-repo/tests/continuation/test_persistence_k10_20260919.py |
8 | 🟢 8 passed |
Chạy lại:
cd /opt/incomex/claude-mcp && python3 -m unittest tests.test_fsroots_persistence_k10_20260919 -v
docker run --rm -v /opt/incomex/docker/agent-data-repo:/src:ro -v /opt/incomex/config/workspace-tools.json:/prod-config.json:ro -e K10_PROD_CONFIG=/prod-config.json -w /src --entrypoint python3 agent-data-hardening:20260919-harden -m pytest tests/continuation/test_persistence_k10_20260919.py -o addopts= -q
Bốn điều kiện PASS, đo được từng cái:
- phát lại: B nhận
[REPLAY operation_id=…](Claude) /replayed:true+first_performed_at(GPT), kèm ĐÚNG commit/job_id của A; - không làm lần hai: phía Claude B không gọi
gh_put_commit_pushvà journal chỉ có MỘT dòngdone; phía GPTrev-list --count HEADkhông tăng, remote không nhích,MARKER-K10xuất hiện đúng 1 lần, vàworkspace_exec/workspace_task_startchỉ có đúng 1 job/task cho mộtoperation_id; - cùng id + payload khác bị từ chối:
operation_id_reused(Claude) /OPERATION_ID_REUSED(GPT), và không ghi gì. Phía Claude còn khoá cả H07 qua ranh giới tiến trình (id củafs_copykhông dùng chofs_move); - phát lại KHÔNG ghi lại file: gốc cục bộ — sau khi A ghi, một tay khác đổi file, B phát lại thì nội dung giữ nguyên bản đã bị đổi.
13.9.3 Test có thật sự bắt lỗi không — đối chứng ÂM và đột biến
Không tự nhận "xanh nghĩa là đúng". Ba phép đo:
- Đối chứng ÂM nằm SẴN trong cả hai bộ test (ở lại vĩnh viễn): xoá journal → tiến trình mới phải
thực hiện thật (Claude: có
gh_put_commit_push, hết[REPLAY]); xoá bảng jobs →workspace_execcấpjob_idMỚI. Không có ca này thì "B không ghi" chưa chứng minh được là nhờ đọc journal cũ. - Đột biến 1 (
/tmp/k10-mutate, bản sao cô lập, KHÔNG chạm production): cho_journal_findluôn trảNone⇒ 5/8 ca ĐỎ. - Đột biến 2 (
/tmp/k10-mutate2): đặt journal BÊN TRONG một gốc + compose bỏ dòng mount ⇒ 2/8 ca ĐỎ.
Ca thứ 8 (test_journal_path_comes_from_the_environment) là kiểm hợp đồng đơn giản, không kỳ vọng bắt lỗi.
Nói thẳng về sức nặng của từng khẳng định (đo chứ không đoán): phía GPT mọi công cụ ghi đều đã có chốt
version/tồn-tại, nên một lần chạy lặp KHÔNG thể commit hai lần — nó trả VERSION_CONFLICT/FILE_EXISTS.
Vì vậy ở đường GHI phía GPT, khẳng định gánh trọng lượng là "B nhận đúng kết quả ĐÃ GHI thay vì lỗi
khó hiểu đó"; còn "không sinh lần hai" gánh trọng lượng thật ở đường JOB (không có gì ngoài sổ hàng đợi
ngăn một retry xếp việc lần hai) và ở đường ghi phía Claude (fs_write lên gốc gh không cần
expected_version nên chạy lại là push thật — đối chứng ÂM đã cho thấy đúng như vậy).
13.9.4 Hồi quy liên quan của cả hai đầu nối
- Claude — 39/39 🟢, chạy một module một tiến trình: k10 8 · hardening_20260919 12 · phase09 7 ·
helper_freshness 5 · is_binary 7.
⚠ Phát hiện có sẵn, không do đợt này: các module test phía Claude đặt biến môi trường ở cấp module nên
không chạy chung một tiến trình được — gộp cả 5 module cho 15 ca đỏ GIẢ. Kiểm bằng cách gộp 4 module CŨ
(không có module mới): vẫn
31 tests, 8 failures. Đề nghị cho Host, ngoài phạm vi work order: tách env ra fixture hoặc khai báo rõ "một module một tiến trình" trong runbook. - GPT — 72/72 🟢 trong một lượt
pytest tests/continuation/(426,8 s): caps 2 · hardening 10 · hardening_20260919 16 · hardening_20260919_p1 14 · k10 8 · workspace 22. ⚠ Ca nợ ở 13.6b (test_background_operation_survives_initial_call) XANH trong lượt này. KHÔNG coi là đã trả nợ: lượt này chạy trong containerdocker run --rmkhông giới hạn CPU, tức ngân sách thời gian khác production — đúng nguyên nhân đã truy ở 13.6b. Nợ vẫn để nguyên cho Host.
13.9.5 Có sửa runtime không · commit
KHÔNG sửa runtime. Test K10 xanh ngay từ đầu, nên theo §5 của PROMPT không dựng image, không deploy,
không restart. Kiểm chứng: NRestarts=0 và StartedAt của cả hai container không đổi
(23:13:54Z / 23:16:38Z), ActiveEnterTimestamp của incomex-mcp-helper.service và
incomex-workspace-exec.service không đổi, 48 image không thêm không bớt. Cả hai container healthy.
Lượt chạy test dùng container dùng-một-lần docker run --rm từ image có sẵn, nguồn mount :ro.
Chưa commit — cố ý. PL1 giới hạn việc git trong bản sao riêng dưới /tmp, nên Agent không commit trên
VPS. Hai tệp đã ở trên đĩa, Host commit bằng một lệnh:
/opt/incomex/claude-mcp/tests/test_fsroots_persistence_k10_20260919.pysha256c9e8fd1d1320eaf93fb264d5c6e7d01bc12efea8d02dcdd59d4df2aa4504b641(nằm trong repo/opt/incomex, nhánhfeat/s177-sprint1-round-a, 171 mục bẩn có từ trước — nếp cũ là stage đúng tệp cần, không commit cả cây)/opt/incomex/docker/agent-data-repo/tests/continuation/test_persistence_k10_20260919.pysha256cbf5e7567d833bb0e2283e40b263cff35fdd3a2d662803d7f2d8c63a184d555c(nhánhclaude/gpt-operation-id-20260918, đang??untracked)
13.9.6 Tự kiểm Phụ lục P10 (PL1–PL3) — KHÔNG thấy vi phạm
Host yêu cầu đọc lại P10 giữa lượt chạy và ghi bằng chứng. Đã rà từng việc đã làm:
- PL1 · git chỉ trong bản sao /tmp. Clone +
fetchchỉ ở bản sao riêng của phiên dưới/tmp. Trên VPS khôngfetch/pull/checkout/commitở bất cứ đâu. Trong/opt/incomex/docker/agent-data-repo(KHÔNG thuộc hai clone bị nêu tên) chỉ chạy git chỉ-đọc:log,status --short,rev-parse;.gitvẫn duy nhấtroot:root. Hai clone bị nêu tên: git của root không mở được/opt/incomex/mcp-roots/gh("dubious ownership", Agent KHÔNG thêmsafe.directory), chủ sở hữu vẫn duy nhấtincomex-mcp-helper:incomex-mcp-helper; hoạt động 01:14–01:21Z ởdata/workspace-tools/github-workspacelà của GPT Chat — reflog ghi chính commit2fc3cf6"[GPT] P09,P10 · COLLAB.md", không phải của Agent. - PL2 · chỉ sửa runtime khi test đỏ. Test xanh ⇒ không sửa, không dựng, không restart (số liệu ở 13.9.5). Không cần tag rollback vì không dựng gì.
- PL3 · không xoá, không đổi auth/URL/secret, test chỉ dùng state tạm. Không xoá file/dữ liệu/image/nhánh
nào của hệ thống; chỉ tạo rồi giữ lại hai thư mục nháp của chính Agent (
/tmp/k10-mutate,/tmp/k10-mutate2— để lại làm chứng tích đột biến). Không chạm auth/URL/secret. Test không ghi vào journal thật — bằng chứng đo được:grep -c 'k10-'= 0 ở CẢ HAI journal thật;operations.jsonlphía GPT y nguyên (19351 B · 52 dòng · mtime 2026-09-19T00:37). Hai dòng mới trong journal Claude lúc00:59:29Zvà01:10:58Zlàfs_edit root=ghvới id tự sinhop-9f0f44c39fc2136a/op-cedf0a9755269a7b— đó là Claude Chat sửaCOLLAB.md, không phải Agent.
13.9.7 Kết luận và việc còn chờ
K10: phần của Agent PASS ở CẢ HAI phía — bind mount host đã xác minh trên container đang chạy, journal đã đi qua restart thật, tiến trình MỚI phát lại đúng từ journal cũ, không sinh commit/job/write lần hai, cùng id + payload khác bị từ chối, và hồi quy liên quan xanh (39/39 · 72/72).
Không tuyên bố gì về client. K1–K9 vẫn là việc của Claude Chat / GPT Chat, và ba lượt L1→L2→L3 của P08
chưa chạy. Agent không chạm AGENTS.md, README.md, COLLAB.md, PROMPT.md.
13.10 R02 đóng tầng máy — RUN R02-CLOSEOUT-20260919-03
Người làm: Claude Code (Opus 5). READY kiểm trên bản sao riêng dưới /tmp (git clone kho công khai,
không đụng hai clone làm việc): READY@8537a8e3a073af9b623047984a5f253af039640f khớp đủ 40 ký tự với
commit cuối chạm PROMPT.md; kho lúc kiểm ở c4d7f00. Áp PL1–PL3 theo đính chính 7159559: PL1 chỉ
cấm git-ghi trên /opt/incomex/mcp-roots/gh và /opt/incomex/data/workspace-tools/github-workspace;
repo mã được commit cục bộ, không push.
13.10.1 Bảng bốn tầng
| Tầng | Trạng thái | Ai | Bằng chứng |
|---|---|---|---|
| CODE | 🟢 PASS | Agent | K10 + runner đã commit: Claude ee76fd5, GPT f3f4c41. Hồi quy bằng chính lệnh chuẩn: Claude 39/39, GPT 72/72 |
| SERVER | 🟢 PASS | Agent | tools/list qua đúng route công khai của client: Claude 23 tool · vân tay fca7e350ffd2 · GPT 37 tool · 1.3.0 · agent-data-hardening-20260919.1+b1gdc+workspace-20260918 · hash f6a27f2622ff. Cả 9 năng lực bắt buộc đủ ở hai phía, 0 BLOCK |
| CLIENT | ⏳ PENDING | Claude Chat · GPT Chat | K1, K2, K9 — chưa chạy. Agent không tuyên bố gì ở tầng này |
| CROSS | ⏳ PENDING | hai chat | K3–K8 — chưa chạy (ba lượt L1→L2→L3 của P08) |
Không dòng nào ở đây được đọc là "production ready". Tầng máy hết nợ; hai tầng còn lại chưa đo.
13.10.2 Chứng tích K10 đã vào repo mã (bước 2)
- Claude —
/opt/incomexcommitee76fd5:claude-mcp/tests/test_fsroots_persistence_k10_20260919.pyclaude-mcp/tests/run_acceptance.py. Stage theo đúng hai đường dẫn; số tệp bẩn 173 → 171, tức 171 tệp bẩn có sẵn không bị đụng. Không push.
- GPT —
/opt/incomex/docker/agent-data-repocommitf3f4c41:tests/continuation/test_workspace.pytests/run_acceptance.py. Cây làm việc sau commit sạch. Không push. Tệp K10tests/continuation/test_persistence_k10_20260919.pyđã nằm trong commit3955c6ctừ trước (xem cảnh báo ngay dưới); sha256cbf5e756…d555ckhớp bản trên đĩa.
🔴 Một việc Host phải biết, Agent không gây ra nhưng đo được. 3955c6c không phải commit của người:
nó do cron sao lưu hằng ngày /opt/incomex/scripts/git-push-gh-daily-v2.sh tạo lúc 04:00 UTC 19/09.
Cron đó, trên /opt/incomex/docker/agent-data-repo: thấy cây bẩn ⇒ git add -A ⇒ commit
"auto-snapshot: N files" ⇒ git branch -f vps-daily-<ngày> ⇒ git push --force lên
Huyen1974/agent-data-test. /var/log/git-push-gh.log ghi đúng ba bước đó.
Hệ quả: (a) chứng tích K10 đã rời VPS lên GitHub ngày 19/09 ở nhánh vps-daily-20260919 — không do
Agent, nhưng là sự thật cần ghi; (b) bất cứ tệp nháp nào còn bẩn lúc 04:00 hoặc 16:00 UTC đều bị gom vào
một commit chung rồi force-push. Repo /opt/incomex (phía Claude) không nằm trong cron này, nên 171
tệp bẩn ở đó an toàn. Sau f3f4c41 cây agent-data-repo đã sạch nên mốc 16:00 UTC không có gì để gom.
Đề nghị cho Host (ngoài phạm vi lượt này, Agent không tự sửa): cho cron stage theo đường dẫn thay vì add -A.
13.10.3 Test-harness đáng tin (bước 3)
Claude — nguyên nhân gốc của 9 đỏ giả, bỏ được mà không đụng runtime.
Mỗi module test tự ghim hộp cát của nó bằng os.environ[...] ở cấp module, trong khi
app/fsroots.py (dòng 93–95: _WRITE_LOG, _OPS_JOURNAL, _LOCK_DIR) và app/hostq.py (dòng 27–28:
_QUEUE_IN, _QUEUE_OUT) đọc các biến đó vào biến toàn cục lúc import. Module nào import trước thì
thắng cả tiến trình; module sau chạy trên hộp cát không phải của mình.
Đo trước khi sửa: gộp 5 module trong một tiến trình ⇒ Ran 39 tests · FAILED (failures=5, errors=4)
— 9 đỏ giả; một module một tiến trình ⇒ 39/39 xanh.
Sửa: không đổi một dòng runtime nào, chỉ thêm claude-mcp/tests/run_acceptance.py chạy mỗi module
trong một tiến trình riêng.
Chứng minh đúng tiêu chí: 3 lần chạy liên tiếp cho kết quả GIỐNG NHAU TỪNG BYTE
(sha256 cả ba lượt a4d0f1f26798178e), 39/39 xanh, 0 đỏ giả, trong lúc máy đang tải 5.16.
GPT — bỏ phụ thuộc may rủi tải máy ở test_background_operation_survives_initial_call.
Bản cũ chờ đúng 5,0 s (100 vòng × 0,05 s) rồi assert — ngân sách bấm giờ, nên máy bận là đỏ (đúng nợ
§13.6b). Bản mới chờ theo trạng thái của chính tác vụ: await_task() hỏi workspace_task_status cho
tới khi status vào tập kết thúc {completed, failed, cancelled}, backoff 0,02→0,5 s, kèm trần thời gian
hữu hạn WORKSPACE_TASK_WAIT_SECONDS (mặc định 120 s) để một tác vụ treo thật vẫn đỏ chứ không treo
bộ test. Trần là chốt chống kẹt, không phải bấm giờ: lớn hơn ~10 lần thời gian tệ nhất từng đo (11,8 s)
và nhỏ hơn hạn 300 s của route. Hỏng thì báo đủ status + heartbeat + position.
🔴 Một giả định của vòng trước đã SAI, đo bằng docker inspect trên container ĐANG CHẠY.
§13.9.4 viết "lượt này chạy trong container docker run --rm không giới hạn CPU, tức ngân sách thời
gian khác production". Sự thật: production incomex-agent-data có
NanoCpus=0 · CpuQuota=0 · CpuShares=0 · CpusetCpus=[] — production cũng KHÔNG giới hạn CPU; giới hạn
duy nhất là Memory=6442450944 và MemorySwap=12884901888. Vậy "đúng giới hạn container production" =
đúng hai trần bộ nhớ đó và không trần CPU.
Chứng minh:
- Đúng giới hạn production, 20 lượt liên tiếp: 20/20 XANH, thời gian phẳng 17,1–20,4 s mỗi lượt.
- A/B có đối chứng (cùng image, cùng trần bộ nhớ, cùng bóp CPU
--cpus=0.35, container MỚI mỗi lượt, khác nhau đúng một thứ là thân test): thân MỚI 6/6 XANH (66,6–76,4 s) · thân CŨ 0/6 xanh, 6/6 ĐỎ vớiKeyError: 'result'— đúng kiểu hỏng của ngân sách 5 s hết hạn khi tác vụ còn đang chạy. ⇒ phụ thuộc tải máy đã mất, không phải "lần này may".
⚠ Một cái bẫy trong chính phép đo của tôi, ghi lại để đời sau khỏi vấp. Lần dựng A/B đầu tiên chạy
20 lượt trong CÙNG một container; thời gian mỗi lượt tăng đều 64,9 → 79,5 → 96,1 → 123,6 s. Không
phải test chậm đi: soi /proc trong container thấy các tiến trình agent_data.workspace_tasks supervise
của lượt trước vẫn sống, cùng chia nhau đúng hạn 0,35 CPU. Đã kiểm production để khỏi kết luận bừa:
container incomex-agent-data có 3 supervisor, cả ba cùng ~5,5 giờ tuổi (bằng tuổi container),
PPid=1, trạng thái S — tức một bể tiến trình sống lâu quanh task_concurrency=2, KHÔNG phải rò rỉ
tăng dần. Kết luận: con số tăng dần là lỗi thiết kế phép đo của tôi, không phải lỗi đầu nối; đã dựng
lại A/B với container mới mỗi lượt và thời gian trở lại phẳng. ⚠️ Chạy nhiều lượt việc-nền trong một
container bị bóp CPU thì phải tính đến bể supervisor, nếu không phép đo sẽ nói dối theo hướng khó chịu.
13.10.4 Minimum shared capability (bước 4) — đo theo NĂNG LỰC, không theo số tool
Đọc từ tools/list qua đúng route công khai mà client dùng, không hỏi container.
| Năng lực | Claude (Incomex VPS) | GPT (Incomex AgentData) | Kết |
|---|---|---|---|
| discover/list | fs_list |
workspace_list |
🟢 |
| read/search/stat | fs_read · fs_search · fs_stat |
workspace_read · workspace_search · workspace_stat |
🟢 |
| edit/create | fs_edit · fs_write |
workspace_edit · workspace_write_new |
🟢 |
| diff/log | fs_diff · fs_log |
workspace_diff · workspace_log |
🟢 |
| copy/move | fs_copy · fs_move |
workspace_copy · workspace_move |
🟢 |
| multi-file transaction | fs_transaction |
workspace_transaction |
🟢 |
| expected version/head | expected_version ở fs_write/fs_edit, expected_head ở fs_transaction |
expected_version ở workspace_edit, expected_head ở workspace_transaction/workspace_write_new |
🟢 |
| idempotency | operation_id ở 5/5 tool ghi |
operation_id ở 7/7 tool ghi/tạo việc |
🟢 |
| freshness | gọi thật: fs_log lạnh trả NGUỒN: đã pull ngay trước khi đọc · source_head=c4d7f00 |
gọi thật: workspace_stat trả source_head=c4d7f00 + refreshed_at |
🟢 |
0 BLOCK. Không viết tính năng mới, không tạo tool trùng để bằng số lượng (Claude 23 ≠ GPT 37 là đúng thiết kế: mỗi bên có tool riêng ngoài hợp đồng chung).
Ba chỗ lệch nhưng không phải thiếu, ghi để hai chat khỏi suy diễn:
workspace_editphía GPT bắt buộcexpected_version(nằm trongrequired);fs_editphía Claude để tuỳ chọn. Cùng năng lực, khác độ chặt.- Chuỗi nạp tệp phía GPT (
workspace_upload_begin/append/commit,workspace_import_file) không cóoperation_id— đúng thiết kế H14: chống ghi đôi bằng khoá tự nhiên(root, path, sha256, total_bytes)vàFILE_EXISTS/expected_version, không bằng sổ idempotency. write_filephía Claude (đường allowlist cũ, ngoài bộfs_*git) không cóoperation_id; nó không thuộc hợp đồng chung.
13.10.5 Schema / release gate (bước 5)
Bề mặt sống, đo qua đúng endpoint của client (nginx → container); secret không ghi ở đâu.
| Claude — Incomex VPS | GPT — Incomex AgentData | |
|---|---|---|
| protocolVersion | 2025-06-18 |
2024-11-05 |
| serverInfo.name | incomex-claude-mcp |
agent-data-mcp-gpt-full |
| serverInfo.version | 1.27.1 ⚠ là version của SDK mcp, KHÔNG phải bản dựng đầu nối |
1.3.0 (đầu nối tự đặt) |
| version thật của bản dựng | claude-mcp-harden-20260919.1 qua vps_status(muc='readiness') |
connectorSchemaVersion = agent-data-hardening-20260919.1+b1gdc+workspace-20260918 |
| hash/vân tay | fca7e350ffd2 |
connectorSchemaHash = f6a27f2622ff |
| số tool | 23 | 37 |
capabilities |
tools.listChanged = false; resources/prompts listChanged = false; subscribe = false |
{"tools": {}} — không khai listChanged |
⚠ serverInfo.version phía Claude là mồi nhử cho việc chốt phát hành. Đầu nối không đặt trường này
ở đâu cả (đã soát app/server.py); 1.27.1 chính là version gói mcp trong container (đo bằng
importlib.metadata.version("mcp")). Nó chỉ đổi khi nâng SDK, không bao giờ đổi khi bề mặt tool đổi.
Đây là chỗ H12 nói "SDK không có chỗ đặt serverInfo.version" — nói chính xác hơn: trường đó có, và nó
nói sai điều người đọc tưởng. Tín hiệu đúng vẫn là BUILD_ID + vân tay trong readiness.
✅ Vân tay phía Claude TỰ KIỂM ĐƯỢC từ phía client — đã chứng minh, không phải hứa: lấy tools/list
qua route công khai rồi tính sha256(repr(sorted((name, description[:400]))))[:12] ⇒ fca7e350ffd2,
khớp đúng con số đầu nối tự báo. Một chat nghi "mình thấy thiếu tool" có thể tự chứng minh.
⚠ Hash phía GPT thì KHÔNG tự kiểm được từ dây: _mcp_schema_hash() băm MCP_TOOLS nội bộ, mà bản
lên dây có thêm _meta/annotations ⇒ tính lại từ tools/list ra 2d925e5d5017, không phải
f6a27f2622ff. Hash đó vẫn làm đúng việc của nó (đổi schema thì đổi hash ⇒ client bust cache), nhưng
client không dùng nó để tự đối chứng được. Phép kiểm dùng được ở phía GPT: 37 tool +
connectorSchemaVersion + có operation_id ở bốn tool đã nêu.
listChanged: cả hai đều KHÔNG hứa, và đó là đúng. Claude khai false tường minh; GPT không khai
trường này. Không bên nào thật sự gửi notifications/tools/list_changed, nên không có cơ chế chuẩn nào
đẩy schema mới sang client đang mở. Không phát minh workaround riêng.
⇒ Luật phát hành (chốt): đổi schema ⇒ nâng version cùng commit ⇒ dựng lại ⇒ client cũ KHÔNG được dùng
để nghiệm thu; cổng phát hành là ngắt/kết nối lại (Claude) hoặc Refresh app (GPT) + MỞ CHAT MỚI, rồi
đối chiếu số tool/vân tay trước khi tin.
Phân xử lỗi client GPT đang thấy (transaction bị chặn vì thiếu operation_id): LỖI CLIENT, KHÔNG PHẢI
LỖI MÁY CHỦ. Gọi tools/list qua đúng URL công khai ChatGPT dùng (qua nginx, profile gpt-full,
không gọi thẳng container) rồi đọc inputSchema thật:
| tool | operation_id trên máy chủ SỐNG |
|---|---|
workspace_edit |
✅ có |
workspace_transaction |
✅ có |
workspace_exec |
✅ có |
workspace_task_start |
✅ có |
Đủ cả bốn ⇒ theo đúng nhánh PROMPT mục 5 quy định: "client giữ schema cũ", chỉ còn reconnect.
Không có test đỏ, không sửa, không deploy cho việc này. (Các fs_* ghi phía Claude cũng đủ operation_id.)
13.10.6 Một lệnh nghiệm thu chuẩn cho mỗi đầu nối (bước 6)
Cả hai là runner nằm trong repo mã hiện hữu, không thêm tài liệu, và trả một dòng
ACCEPTANCE: PASS|FAIL bao trùm cả hồi quy lẫn kiểm capability/schema:
- Claude:
cd /opt/incomex/claude-mcp && python3 tests/run_acceptance.py --expect-fingerprint fca7e350ffd2(hồi quy: mỗi module một tiến trình; cổng phát hành: đọc bề mặt từ container đang chạyincomex-claude-mcp—BUILD_ID, số tool, vân tay, và tham số bắt buộc của từng năng lực.) - GPT:
python3 /opt/incomex/docker/agent-data-repo/tests/run_acceptance.py --expect-schema-hash f6a27f2622ff(hồi quy:tests/continuation/trong container dùng-một-lần dựng từ image production, nguồn mount:ro, kèmworkspace-tools.jsonproduction cho các ca K10; cổng phát hành:tools/listqua route công khai. Secret của route đọc từ cấu hình nginx lúc chạy và không bao giờ in ra.)
Cả hai có --no-live để chạy riêng phần hồi quy; khi đó lệnh trả INCOMPLETE, không bao giờ PASS.
13.10.7 Hồi quy toàn bộ + kiểm máy chủ sống (bước 7)
Chạy bằng chính hai lệnh chuẩn trên:
| Đầu nối | Hồi quy | Cổng phát hành | Kết |
|---|---|---|---|
| Claude | 39/39 (5 module, mỗi module một tiến trình) | build claude-mcp-harden-20260919.1 · 23 tool · vân tay fca7e350ffd2 · 9/9 năng lực |
🟢 ACCEPTANCE: PASS |
| GPT | 72/72 trong 324,6 s (tests/continuation/) |
1.3.0 · …20260919.1+b1gdc+workspace-20260918 · hash f6a27f2622ff · 37 tool · 9/9 năng lực |
🟢 ACCEPTANCE: PASS |
Trong 72 ca có ca nợ §13.6b test_background_operation_survives_initial_call — nay xanh, và lần này
xanh có trọng lượng vì nó chờ theo tín hiệu chứ không theo đồng hồ (13.10.3).
Không phát hiện lỗi runtime ⇒ KHÔNG restart gì (đúng §7 của work order). Kiểm sau khi chạy xong:
incomex-claude-mcp StartedAt=2026-09-18T23:13:54Z · incomex-agent-data StartedAt=2026-09-18T23:16:38Z,
cả hai RestartCount=0, healthy, 48 image không thêm không bớt, 0 Traceback ở cả hai trong
30 phút cuối, và không còn container thử nào sót. Mọi lượt chạy test dùng container dùng-một-lần
docker run --rm với nguồn mount :ro.
Tự kiểm PL1: /opt/incomex/mcp-roots/gh/.git vẫn duy nhất incomex-mcp-helper:incomex-mcp-helper
và git-của-root vẫn bị từ chối "dubious ownership" (Agent không thêm safe.directory); không
fetch/pull/checkout/commit ở cả hai clone làm việc. HEAD kho chung vẫn c4d7f00 — Agent không đẩy gì.
13.10.8 Bề mặt CHÍNH XÁC mà K1–K9 phải thấy (tầng CLIENT/CROSS — việc của hai chat)
Agent không chạm K1–K9. Ghi sẵn con số để hai chat đối chiếu thay vì đoán:
| Mã | Ai | Kỳ vọng CHÍNH XÁC |
|---|---|---|
| K1 | Claude Chat | chat mới sau khi ngắt/kết nối lại: đúng 23 tool; vps_status(muc='readiness') in claude-mcp-harden-20260919.1 · 23 tool · vân tay fca7e350ffd2. Lệch số hoặc lệch vân tay = client còn giữ danh sách cũ |
| K2 | GPT Chat | chat mới sau Refresh: đúng 37 tool; gọi thẳng workspace_edit, workspace_transaction, workspace_exec, workspace_task_start có operation_id mà client không chặn. Máy chủ đã đủ schema (13.10.5) ⇒ còn chặn = client chưa refresh |
| K3 | Claude → GPT | GPT gọi workspace_stat/workspace_log trước khi đọc gì khác vẫn thấy commit của Claude; đáp phải có source_head = commit đó và refreshed_at khác null |
| K4 | GPT → Claude | fs_log/fs_diff lạnh thấy commit của GPT; đáp phải có dòng NGUỒN: đã pull ngay trước khi đọc · source_head=<commit đó> |
| K5 | Claude cũ → GPT sửa | VERSION_CONFLICT, tệp giữ nguyên v2 của GPT |
| K6 | chéo hai chiều | ghi thành công, commit cha là commit của bên kia, tệp của bên kia còn nguyên |
| K7 | mỗi bên | cùng operation_id + payload y nguyên ⇒ đúng 1 commit; lần hai REPLAY/replayed:true, không commit mới |
| K8 | mỗi bên | id của copy đem dùng cho move ⇒ OPERATION_ID_REUSED nêu tên cả hai tool, tệp nguồn không bị động |
| K9 | GPT Chat | hai dấu cách cuối dòng ⇒ THÀNH CÔNG kèm warnings, byte giữ nguyên (chặn chỉ dành cho dấu hiệu xung đột) |
Tệp thử chỉ nằm trong _thu-nghiem/client-20260919/ (P08).
13.10.9 Nợ còn lại sau lượt này
- Phía đầu nối: không còn nợ nào đã biết mà chưa xử lý. Nợ §13.6b đã trả (13.10.3 + 13.10.7); nợ "đỏ giả do env cấp module" phía Claude (§13.9.4) đã trả bằng runner, không đụng runtime.
- Chỉ còn: hai client làm mới + K1–K9. Không việc nào của Agent chặn hai việc đó.
- Hẹn đo lại sau 2026-09-25 (giữ nguyên từ §12.16): GC của staging upload / result tạm.
- Chưa làm, cố ý (ngoài phạm vi, chờ Owner/Codex): trần
client_max_body_sizecho route GPT (đề xuất64m, §12.18); crongit add -A+ force-push (13.10.2); hai dịch vụ FAILED lúc khởi động (§12.17); 171 tệp bẩn có sẵn trong/opt/incomex. - Agent không chạm
AGENTS.md,README.md,COLLAB.md,PROMPT.md; không xoá dữ liệu/nhánh/image; không đổi auth/URL/secret; không push.
13.11 Đợt vá vòng đời tệp (G1–G16 / C1–C16) — báo cáo Agent 15:20 (+07) ngày 2026-09-19
Nguồn: phiên Claude Code chạy addendum G1–G16/C1–C16. Agent chỉ trả báo cáo trong chat; Owner chuyển cho Claude Chat;
Claude Chat chép nguyên văn vào đây lúc 08:45Z (không sửa chữ). Trạng thái: GPT-side đã code + test, CHƯA deploy;
3 quyết định phạm vi chờ Founders (COLLAB P12: D09 NFC, D10 không mở HTTP mới, D11 gh text-only).
Đính chính của Claude Chat (COLLAB P12): dòng "MOVE DIR · Claude · unverified" là sai — đã PASS thật ở commit f5870e9;
dời thư mục vào chính con → invalid_move.
46 passed, 0 failed across both continuation suites. Here is where things stand — I'm stopping before the remaining items because three of them conflict with the standing contract or need your decision.
Done and tested — GPT connector (agent-data), backend only
ID: G1
Change: Every create/destination path now makes its missing parents automatically — write_new,
import_file, upload commit, copy, move, and transaction create/copy/move. No flag, matching
what the Claude connector already did. New dirs inherit the parent's mode (setgid kept) so
the shared ui root stays writable by both sides.
Test: ✅
────────────────────────────────────────
ID: G2
Change: workspace_stat gives a directory a deterministic tree token; copy/move expand a
directory into its files inside one commit, guarded by that token.
Test: ✅
────────────────────────────────────────
ID: G3
Change: workspace_import_file gets a derived request identity (the volatile download_url
excluded, file_id kept), so a retry after a lost response replays instead of writing twice.
A failed download is now a clean refusal, not a quarantined id.
Test: ✅
────────────────────────────────────────
ID: G4
Change: workspace_upload_begin dedupe moved ahead of the FILE_EXISTS check and extended to
committed uploads, so a lost answer returns the original upload and its result.
Test: ✅
────────────────────────────────────────
ID: G7
Change: Directory copy/move into its own descendant (or ancestor) is refused before anything
is read. Defect in the G2 code I had just written.
Test: ✅
────────────────────────────────────────
ID: G11/G15
Change: Rollback removes the parents it created; a move removes the folders it emptied. Same
defect class.
Test: ✅
SCHEMA_CHANGED: NO — no property, required field, or tool added or removed. Tool count stays 37. Only five tool descriptions changed (they previously stated "Existing parent directories required", which is now false). Old clients keep working without refreshing; a refresh only buys accurate descriptions.
Not deployed. Production still runs agent-data-hardening:20260919-harden, untouched; rollback tag agent-data-hardening:rollback-pre-mkdir-20260919 created. I held the build deliberately — you asked for exactly one client refresh, and the open decisions below can still change the schema.
Verified on the Claude side (real calls, not code reading)
- C15 ghost folders — confirmed. /opt/incomex/mcp-roots/gh/_thu-nghiem/audit-claude-20260919/a/b/c is empty and untracked; fs_list shows it, GitHub does not.
- C2/C8 — confirmed. fs_stat on a directory returns no version at all, and fs_copy refuses directories outright ("thư mục: chuyển từng file").
- C16 — confirmed in code (fsroots.py:1288): files ≤ 51200 B are fully replaceable with no expected_version. This one is not a contract change — README §3 already requires a version on every overwrite, so fixing it enforces the contract.
Three items I will not do without your decision
1. C14/G14 (NFC normalisation) contradicts the governing contract. README §8 v1.3 says, verbatim: "Giữ nguyên path/tên file Unicode kể cả dạng NFD: không tự normalize/rename." C14 asks for the opposite at every entry point. That is a D decision for the Founders, not something an Agent edits underneath the contract.
2. C13/G13 (signed one-time export GET) is a new public HTTP surface on a public-facing host — nginx route, token store, TTL. That is outside "sửa connector" and squarely inside this RUN's own stop rule.
3. G9/C9 (binary byte-for-byte) cannot be satisfied as specified. Both connectors are deliberately text-only on the Git root: GPT's upload commit does raw.decode('utf-8'), Claude's fs_copy refuses NUL bytes on gh. Binary support is a design change, not a fix.
G12/C12 I cannot complete at all: it requires the two real chat clients. I can drive the Claude connector from here (I did, above), but I cannot drive the ChatGPT client.
Lifecycle matrix
| Stage | GPT | Claude | Status |
| DISCOVER · READ · SEARCH · STAT(file) · EDIT · COPY FILE · CONCURRENCY | ✅ | ✅ | PASS (pre-existing + suite) |
| STAT (directory version) | ✅ new | ❌ none | Claude GAP |
| CREATE nested · MOVE FILE nested | ✅ new | ✅ existing | PASS |
| IMPORT | ✅ + derived idempotency | N/A | PASS |
| EXPORT | ❌ | ❌ | BLOCKED (C13) |
| LARGE UPLOAD | ✅ resumable | fs_write ≤20 MB | partial |
| BINARY | ❌ | ❌ | GAP — design conflict |
| COPY DIR | ✅ new | ❌ refuses | Claude GAP |
| MOVE DIR | ✅ new | unverified | Claude GAP |
| TRANSACTION (incl. trees) | ✅ | files only | Claude partial |
| IDEMPOTENCY | ✅ 5 + import | ✅ | PASS |
| FAILURE/RECOVERY (no ghosts) | ✅ new | ❌ C15 proven | Claude GAP |
| CLIENT BINDING | not deployed | not deployed | PENDING |
Repo cleanup (Việc 2) not started — your own gate is "sau khi toàn bộ matrix xanh", and the matrix cannot go green until the three decisions above are made.
What I need from you: (a) NFC — keep README §8 or supersede it with a new D? (b) Export endpoint — approve the new HTTP surface, or drop C13? (c) Binary on the Git root — lift the text-only rule, or accept ui-root-only? With those, I can finish the Claude-side C1–C16 work and deploy both connectors in a single refresh.
Background check confirms the count I reported: the live container's workspace module registers exactly 26 tools (the same list my test asserts), and server.py adds the 11 knowledge-base tools — 37 total, unchanged, with no delete/chmod/symlink/mkdir tool among them.
13.11.1 RUN R03-FILE-LIFECYCLE-20260919-01 — tầng máy (Claude Code, 2026-09-20)
Lệnh RUN của Owner, PROMPT READY@7d0521916bbc9705eade326b3dcbb00c30f81c53 (đã so đủ 40 ký tự trên bản
sao riêng /tmp/r03-20260919/repo; commit cuối chạm PROMPT.md đúng bằng SHA đó, hai Founder đã
REVIEWED@ cùng SHA). Áp PL1–PL3 theo đính chính 7159559: không fetch/pull/checkout/commit
trong /opt/incomex/mcp-roots/gh hay /opt/incomex/data/workspace-tools/github-workspace bằng tay —
mọi thay đổi ở hai clone đó chỉ đi qua chính hai đầu nối; repo mã VPS commit theo path; không xoá
dữ liệu/nhánh/image; không mở route/nginx mới.
Commit mã (VPS là SSOT của mã):
/opt/incomex → 5825714 [Claude Code] R03 · claude-mcp + trợ lý · vòng đời tệp (12 tệp).
/opt/incomex/docker/agent-data-repo → effb04e [Claude Code] R03 · agent-data · vòng đời tệp (8 tệp).
Không tệp nào ngoài thao tác bị cuốn vào hai commit (đã kiểm git show --stat).
A. Ma trận G1–G16 / C1–C16
| ID | Việc | Kết | Bằng chứng |
|---|---|---|---|
| G1 | mọi đường tạo tự sinh thư mục cha | 🟢 | có sẵn từ đợt 19/09, nay ĐÃ DEPLOY; kiểm sống: workspace_write_new tạo _thu-nghiem/R03/cheo/gpt-viet.txt khi cheo/ chưa tồn tại (commit 4a0779f) |
| G2 | mã cây cho thư mục + copy/move cả cây trong 1 commit | 🟢 | tests/continuation/test_workspace_folders.py (24 ca) |
| G3 | workspace_import_file có danh tính yêu cầu dẫn xuất |
🟢 | cùng bộ ca trên |
| G4 | upload_begin khử trùng trước FILE_EXISTS |
🟢 | cùng bộ ca trên |
| G5 | guarded replace qua đường upload/import | 🟢 | KHÔNG cần đổi schema — expected_version đã có sẵn ở workspace_upload_begin/workspace_import_file; commit biến nó thành một edit có khoá. 2 ca mới: thay cả tệp dưới version đúng, và version cũ ⇒ VERSION_CONFLICT, tệp giữ nguyên |
| G6 | tải lên dở dang sống sót qua khởi động lại | 🟢 | ca mới: append 2000/5000 byte → đọc workspace_upload_status từ tiến trình khác → offset=2000, status=uploading → append tiếp → commit đủ 5000 byte. Không restart production |
| G7 | copy/move thư mục vào chính nó ⇒ từ chối trước khi đọc | 🟢 | test_workspace_folders.py; phía Claude kiểm sống (dưới) |
| G8 | mã cây xác định (đi cùng G2) | 🟢 | workspace_stat trả version=<head>:<sha>; trùng từng ký tự hex với mã cây của đầu nối Claude (xem #5) |
| G9 | binary byte-for-byte trên gốc gh |
⬜ N/A(D11) | gốc gh là text-only; phần còn lại thu về “SHA binary giữ nguyên trên ui” |
| G10 | quét bí mật trên đường copy | 🟢 | không có bề mặt mới; safe_content chạy trên mọi nội dung vào Git |
| G11/G15 | hoàn tác gỡ thư mục cha tự sinh; move dọn thư mục vừa làm rỗng | 🟢 | prune_empty trong workspace_operations + ca kiểm |
| G12 | client binding | 🟡 mục 4 | xem mục C dưới đây — Agent KHÔNG chấm được |
| G13 | export GET ký một lần | ⬜ N/A(D10) | không mở bề mặt HTTP mới trong R03 |
| G14 | normalize NFC | ⬜ thay bằng D09 | không normalize; chặn sinh đôi (xem #24) |
| G16 | — | ⬜ không tồn tại | C16 chỉ có ở phía Claude; giữ hàng để không ai tưởng bị bỏ sót |
| C1 | upload trực tiếp từ sandbox Claude | ⬜ N/A(D10) | |
| C2 | binary phía Claude trên gh |
⬜ N/A(D11) | (trong báo cáo 15:20 mã C2 còn được dùng lẫn cho “fs_stat thư mục không có version” — phần đó đóng ở C4/C8, nên không còn khoảng trống dù hiểu theo nghĩa nào) |
| C3 | fs_copy nhận CẢ thư mục |
🟢 | đệ quy · MỘT commit · được-ăn-cả-ngã-về-không · symlink/gitlink ở bất kỳ cấp ⇒ từ chối cả lượt · quét bí mật TỪNG tệp · trần 200 tệp / 20 MB. Kiểm sống: _thu-nghiem/R03/cay → cay-chep, 3 tệp/2 thư mục, commit e81a90c |
| C4 | khoá theo mã cây cho copy/move/giao dịch | 🟢 | kiểm sống: mã cây cũ sau khi một tệp con đổi ⇒ version_mismatch (không chép gì) |
| C5 | = C16 | 🟢 | xem C16 |
| C6 | — | ⬜ N/A(D10) | |
| C7 | copy/move thư mục vào con/cha của nó | 🟢 | kiểm sống: DENIED:invalid_copy “chưa có gì bị đọc hay ghi” |
| C8 | mã cây xác định, đổi khi tệp con đổi | 🟢 | fs_stat(dir) trả tree_token = version; 5 ca kiểm (đổi tệp rất sâu, thêm tệp, gọi hai lần, symlink ⇒ unsafe_tree) |
| C9 | SHA binary giữ nguyên trên ui |
⬜ N/A(D11) cho gh |
|
| C10 | quét bí mật từng tệp khi chép cây | 🟢 | ca kiểm: một tệp con chứa api_key = … ⇒ secret_in_content, KHÔNG tạo gì ở đích |
| C11 | dọn thư mục cha tự sinh khi hoàn tác | 🟢 | ca kiểm: os.rename hỏng giữa giao dịch ⇒ moi/rat/sau/ biến mất |
| C12 | client binding | 🟡 mục 4 | |
| C13 | export GET | ⬜ N/A(D10) | |
| C14 | normalize NFC | ⬜ thay bằng D09 | |
| C15 | thư mục ma | 🟢 | trợ lý host gỡ MỌI thư mục rỗng trong bản clone ở mỗi lần pull/commit/hoàn tác. _thu-nghiem/audit-claude-20260919/a/b/c đã biến mất nhờ chính cơ chế đó, không dọn tay (kiểm bằng fs_list(depth=0) sau khi deploy) |
| C16 | fs_write thay tệp đã tồn tại luôn cần expected_version |
🟢 | kiểm sống: tệp 4 byte, không version ⇒ DENIED:expected_version_required kèm cách lấy version; có version đúng ⇒ ghi được (commit bd904f3) |
B. Cổng 29 năng lực
Cách chấm: một ô chỉ PASS khi PASS trên mọi root/client áp dụng. Git SSOT = GPT workspace,
Claude gh; ui mutation = N/A(D08/R04); docs chỉ đọc.
| # | Năng lực | GPT | Claude | Bằng chứng | Kết |
|---|---|---|---|---|---|
| 1 | LIST/ROOT DISCOVERY | workspace_list không root → danh sách gốc + writable + mode |
fs_list root sai → “Các gốc đang khai: gh (rw), ui (rw), docs (ro), code (ro)”; vps_status(readiness) in đủ 4 gốc + mode + read/write |
gọi sống cả hai | PASS |
| 2 | READ/TEXT FIDELITY | read cắt lát chuỗi thô, BOM/CRLF/khoảng trắng nguyên vẹn |
sửa trong R03: fs_read/fs_search chỉ tách ở \n (trước đây splitlines() nuốt \r của CRLF và tách cả ở \v,\f,U+2028); header nói rõ xuong_dong= và bom= |
ca kiểm hai phía + gọi sống (bom=có, khoảng trắng cuối dòng giữ nguyên) |
PASS |
| 3 | SEARCH (nội dung + TÊN/đường dẫn) | workspace_search + include/exclude glob |
fs_search + include/exclude glob |
không đổi schema: tìm theo tên = glob ở include với pattern='(?s).', regex=true. Gọi sống: include=['**/that-sau/*.txt'] → đúng 2 tệp |
PASS |
| 4 | STAT FILE | workspace_stat |
fs_stat |
bytes/sha256/version/head/freshness — gọi sống | PASS |
| 5 | STAT DIRECTORY (mã cây) | 🟢 từ đợt 19/09 | mới: fs_stat(dir) → tree_token = version |
hai đầu nối ra CÙNG một chuỗi: c5359d1984902869f81575d0ab71ca6e9cbee67efa99e63fd1522f4a90bf7d4c cho _thu-nghiem/R03/cay |
PASS |
| 6 | LOG (lọc path + follow khi dời) | mới: --follow tự bật khi path là MỘT tệp |
fs_log(follow=true) |
gọi sống: lịch sử của …/luu-tru/cay-chep/sau/that-sau/ba.txt xuyên qua cả lượt move thư mục lẫn lượt copy |
PASS |
| 7 | DIFF giữa HAI phiên bản bất kỳ | mới: workspace_diff(from_ref[, to_ref]) |
mới: fs_diff(from_version[, to_version]) → hành động trợ lý git_diff_range |
ca kiểm hai phía (giới hạn theo path, mặc định to=HEAD, ref không phải hex ⇒ từ chối); cổng phát hành đọc thấy tham số trên tools/list sống |
PASS (client: xem mục C) |
| 8 | CREATE nested | 🟢 | 🟢 | giao dịch tạo cay/sau/that-sau/ba.txt từ con số không, 1 commit 5a197f9 |
PASS |
| 9 | EDIT + literal replace_all có đếm |
mới replace_all/expected_count |
mới replace_all/expected_count |
gọi sống phía Claude: expected_count=2 ⇒ edit_count_mismatch (không ghi gì), expected_count=3 ⇒ “chỗ đã thay=3”, commit 324d40b |
PASS |
| 10 | GUARDED REPLACE | 🟢 (workspace_edit bắt buộc version; upload/import có expected_version) |
mới (C16): mọi ghi đè đều cần version | gọi sống + ca kiểm | PASS |
| 11 | COPY FILE | 🟢 | 🟢 | gọi sống: chép vào thư mục chưa tồn tại, sha256 khớp, commit 087691c |
PASS |
| 12 | COPY DIRECTORY | 🟢 | mới (C3) | 1 commit cho cả cây; symlink/gitlink ⇒ từ chối; quét bí mật từng tệp; trần tệp/byte | PASS |
| 13 | MOVE FILE | 🟢 | 🟢 | gọi sống a3f5702 |
PASS |
| 14 | MOVE DIRECTORY (+prune) | 🟢 | mới: fs_move nhận expected_version (mã cây); dọn thư mục vừa làm rỗng |
gọi sống: cay-chep → luu-tru/cay-chep (c325084), không còn thư mục ma; move tệp cuối ⇒ “thư mục rỗng đã dọn: _thu-nghiem/R03/chi-mot” |
PASS |
| 15 | TRANSACTION FILE+TREE | 🟢 | mới: op copy nhận thư mục; chỉ stage đúng tệp của thao tác |
ca kiểm: 1 op copy thư mục + 1 op write ⇒ MỘT commit, đúng 4 path | PASS |
| 16 | DELETE | — | — | luật nền của chủ: hai đầu nối cố ý không có tool xoá | N/A (quyền Owner) |
| 17 | IMPORT ATTACHMENT | 🟢 backend (G3, fixture HTML 2–3 MB) | ⬜ | Claude direct import = N/A(D10); phần client của GPT (gọi thật từ một attachment) thuộc mục 4 | PASS backend · client → mục 4 |
| 18 | LARGE/RESUMABLE UPLOAD | 🟢 (G4 + G6) | ⬜ | Claude direct upload = N/A(D10) | PASS |
| 19 | EXPORT/DOWNLOAD/BACKUP | — | — | N/A(D10) | |
| 20 | CONCURRENCY/LOCK | expected_version/expected_head; mã cây cho thư mục |
mới: mã cây làm khoá cho copy/move/giao dịch | gọi sống: mã cây cũ sau khi tệp con đổi ⇒ version_mismatch; BUSY trả retry_after, không mutation nửa vời |
PASS |
| 21 | IDEMPOTENCY/LOST RESPONSE | operation_id 7/7 |
operation_id 5/5 |
gọi sống: cùng id + cùng payload ⇒ [REPLAY …], không commit thứ hai |
PASS |
| 22 | FAILURE/RECOVERY HYGIENE | 🟢 | mới: hoàn tác gỡ cả thư mục cha tự sinh; trợ lý prune sau mỗi restore | ca kiểm hai phía; clone sạch sau mọi ca từ chối | PASS |
| 23 | DANGEROUS PATHS | 🟢 | 🟢 | gọi sống: ../../../etc/passwd ⇒ path_not_allowed; .git, symlink, tên khớp danh sách chặn ⇒ từ chối |
PASS |
| 24 | UNICODE/NFC/PORTABILITY | mới NAME_COLLISION |
mới name_collision_nfc / name_collision_case |
Chính sách rõ cho hoa-thường: TỪ CHỐI (Linux giữ được cả hai, macOS/Windows thì không). Gọi sống hai phía: Bao-Cao.md có sẵn ⇒ bao-cao.md bị từ chối và nêu tên tệp đang có. Nhánh NFC/NFD chứng minh ở tầng đầu nối (ca kiểm): tầng chat tự gộp NFC nên không gõ được tên NFD từ client |
PASS |
| 25 | POLICY/GIT SAFETY | 🟢 | 🟢 | text không phải UTF-8 ⇒ từ chối rõ (UTF8_TEXT_REQUIRED / binary_file); chỉ stage tệp của thao tác; main chỉ fast-forward, không force; D12 giữ nguyên (Agent không đổi quyền nào của Owner) |
PASS |
| 26 | FRESHNESS/CROSS-CLIENT | 🟢 | 🟢 | gọi sống HAI CHIỀU: GPT ghi 4a0779f ⇒ Claude đọc lạnh thấy ngay; Claude ghi e574fd4 ⇒ GPT workspace_stat thấy ngay với refreshed_at |
PASS |
| 27 | CLIENT BINDING CONTRACT | ⏳ | ⏳ | CHƯA CHẤM — bắt buộc, không được ghi N/A. Xem mục C | mục 4 |
| 28 | HISTORICAL READ / SAFE RESTORE | mới: workspace_read/list(ref) + op restore |
mới: fs_read/fs_list(ref) + op restore |
gọi sống trên chính repo thật: hoàn tác 324d40b ⇒ ba chỗ “Ban chạy tốt” trở lại “Ban chạy” mà dòng thêm sau đó vẫn còn (commit bù 0cf7762); hoàn tác một commit ĐÃ TẠO tệp: không có archive_dir ⇒ delete_forbidden, có ⇒ tệp được DỜI vào vùng lưu trữ trong cùng commit a6d617f. Ca kiểm: hunk chồng lấn ⇒ DỪNG cả giao dịch, HEAD và cây làm việc y nguyên |
PASS |
| 29 | OWNER-VIEW PUBLISH BOUNDARY | — | — | connector không có copy chéo workspace/gh → ui |
N/A(R04/D08) |
28/29 dòng PASS hoặc N/A có số quyết định. Dòng còn lại là #27, thứ về bản chất chỉ đo được SAU khi deploy (DANH-MUC §2 đã ghi “cần nghiệm thu sau deploy”), nên cổng §3 được đọc là: không còn FAIL nào ở 28 dòng đo được trước deploy ⇒ deploy; #27 thuộc mục 4 và Agent không được tự chấm.
C. Deploy (một lần cho cả hai) và hợp đồng client
Test trước deploy: Claude 115/115 xanh (7 module, mỗi module một tiến trình; 76 ca mới),
GPT 124/124 xanh (28 ca mới). Hai ca kiểm cũ bị sửa vì kiểm sai thứ, không phải vì hạ chuẩn:
(a) fixture kho bare của GPT không ghim symbolic-ref HEAD trong khi máy chủ không đặt
init.defaultBranch ⇒ mọi ls-tree HEAD chết với mã 128 (chính là 7 ca đỏ khi bắt đầu lượt này);
(b) hai ca H12 ghim nguyên chuỗi ngày 20260919 trong BUILD_ID/CONNECTOR_SCHEMA_VERSION ⇒ đỏ ở
MỌI đợt sau, nay kiểm hình dạng <tên>-<YYYYMMDD>.<n> và bắt buộc phải đổi.
| Claude — Incomex VPS | GPT — Incomex AgentData | |
|---|---|---|
| image cũ (tag lùi) | claude-mcp-local:rollback-pre-r03-20260920 |
agent-data-hardening:rollback-pre-r03-20260920 |
| image mới | claude-mcp-local:r03-20260920 |
agent-data-r03:20260920-lifecycle |
| bản dựng công bố | claude-mcp-r03-lifecycle-20260920.1 |
agent-data-r03-lifecycle-20260920.1+b1gdc+workspace-20260918 |
| vân tay / hash | fca7e350ffd2 → 4f1000e9aad3 |
f6a27f2622ff → dbbfc590a969 |
| số tool | 23 → 23 (không đổi) | 37 → 37 (không đổi) |
run_acceptance.py sau deploy |
ACCEPTANCE: PASS (115 ca + cổng phát hành) | ACCEPTANCE: PASS (124 ca + tools/list qua route công khai) |
Trợ lý host incomex-mcp-helper.service và incomex-workspace-exec.service được restart (điều kiện
PL2 đã kiểm trước: 0 manifest prepared/push_unknown/rollback_conflict, hàng đợi việc rỗng).
🔴 Một số đo lệch chuẩn, nói thẳng: incomex-claude-mcp healthy sau 96 s; incomex-agent-data
healthy sau 124 s — tức quá 4 giây so với mốc “healthy trong 2 phút” của PROMPT §3. Đây là nhịp
khởi động sẵn có của image đó (start_period 45 s + interval 15 s ⇒ lần thăm dò thành công đầu tiên
rơi vào mốc 124 s), không phải triệu chứng của đợt sửa này; hai container từ đó tới giờ healthy,
RestartCount=0. Agent KHÔNG hoàn nguyên, và ghi rõ con số ở đây để Host quyết có coi là đạt hay không.
Cờ hợp đồng client (§2 #27):
TOOL_LIST_CHANGED= NO — không thêm/bớt/đổi tên tool nào (23 và 37 giữ nguyên).TOOL_INPUT_SCHEMA_CHANGED= YES — đúng MỘT đợt, ở cuối: Claudefs_diff(versionthành tuỳ chọn +from_version/to_version),fs_read(+ref),fs_list(+ref),fs_move(+expected_version); GPTworkspace_diff(+from_ref/to_ref),workspace_read(+ref),workspace_list(+ref),workspace_editvà opeditcủaworkspace_transaction(+replace_all/expected_count),workspace_transaction(+ biến thể oprestore). Các thay đổi nằm TRONGedits/opscủa đầu nối Claude không tính là đổi schema vì hai tham số đó vốn là mảng đối tượng tự do.TOOL_METADATA_CHANGED= YES — mô tả của cả hai bên đổi (đó là lý do vân tay đổi).CLIENT_REFRESH_REQUIRED= YES — làm mới/kết nối lại đúng một lần cuối, rồi mở chat mới.
D. Smoke mà Agent thực sự làm được — và chỗ Agent dừng
Agent chạy nhóm ca ghi thật trên Git SSOT root của cả hai đầu nối, trong _thu-nghiem/R03/
(theo P11(c)): tạo cây lồng 1 commit · fs_stat thư mục lấy mã cây · chép cả thư mục 1 commit ·
chép vào chính con ⇒ từ chối trước khi đọc · ghi đè không version ⇒ từ chối · mã cây cũ ⇒ từ chối ·
replace_all sai expected_count ⇒ huỷ cả lượt, đúng expected_count ⇒ 3 chỗ · tên chỉ khác
hoa-thường ⇒ từ chối và nêu tên có sẵn (cả hai đầu nối) · chuyển cả thư mục · chuyển tệp cuối ⇒ dọn
thư mục rỗng · hoàn tác một commit giữ nguyên thay đổi sau · hoàn tác commit đã tạo tệp ⇒ no-delete ·
operation_id phát lại · chéo GPT↔Claude hai chiều · .. bị chặn.
🔴 Chỗ Agent DỪNG, đúng theo §4/§5: phiên client đang chạy này giữ danh sách tool trước deploy
(kiểm được: vps_status(readiness) của máy chủ in 4f1000e9aad3, trong khi schema mà client giữ vẫn
là bản cũ — fs_diff còn bắt buộc version, fs_read chưa có ref, fs_move chưa có
expected_version; phía GPT, mô tả workspace_move mà client giữ còn nói “Existing parent
directories required”, điều đã sai từ đợt 19/09). Vì vậy #27 và các ca cần tham số MỚI
(fs_diff(from_version), fs_read/fs_list(ref), fs_move(expected_version),
workspace_diff(from_ref), workspace_read/list(ref), workspace_edit(replace_all),
workspace_transaction(op=restore)) chưa được chấm ở tầng client — chúng đã PASS ở tầng máy chủ
(ca kiểm + tools/list sống của cổng phát hành), nhưng theo §2 #27 “mỗi surface phải GỌI TOOL THẬT”.
Cần đúng một lần refresh plugin GPT + ngắt/kết nối lại connector Claude, rồi nghiệm thu ở chat
mới trên GPT Chat, GPT Work, Claude Code (không được N/A) và Claude Chat.
Ngoài phạm vi, không chặn, ghi để khỏi quên: DANH-MUC-CONG-CU.md §2 vẫn đang là bảng chấm TRƯỚC
R03 — Agent cố ý không sửa (đó là tài liệu chấm của Host, và phải đợi tầng client xong);
_thu-nghiem/R03/ còn nguyên tệp thử theo P11(c) (xoá là quyền Owner khi đóng R03); chỗ lưu trữ của
op restore giữ nguyên đường dẫn gốc bên dưới archive_dir (dài dòng nhưng không mơ hồ).
R03-FILE-LIFECYCLE-20260919-01: MACHINE_DONE — client/cross còn mục 4 · KB §13.11
13.11.2 — RUN R03-NAME-TWIN-REPAIR-20260920-01 (2026-09-20): #24 ở MỌI đường tạo tên
Người làm: Claude Code. Giấy phép: PROMPT_SHA 8114352e5599b826657dcd302fd4cbdf0155049d ·
APPROVAL_COMMIT 75471ff5ae91a5abc569c184d52f1c7aa63ac346 · HEAD lúc khởi động
006f693d29423f474fb85f9bcb08762d421a8b03. Giữa lượt, repo được tái cấu trúc; bridge P16
(work/mcp-workspace/COLLAB.md @ 3117f3a247d41d6023683c9a1ea8ac6b8382b5ff) cho run này chạy
tiếp theo gate cũ, thu phạm vi còn đúng #24 D1–D4, và chuyển mọi path thử sang
work/mcp-workspace/_thu-nghiem/R03/. Agent đã đọc lại remote trước khi deploy/ghi, và áp đúng.
A. Bảng entry point × cổng — đọc từ MÃ, không từ trí nhớ
grep mọi chỗ rename / mkdir / ghi tệp mới / write-blob ở cả hai đầu nối + trợ lý host. Số dòng là
bản SAU khi sửa. #24 = sinh đôi mọi thành phần path · #23 = path nguy hiểm · #10 = bắt version
khi thay tệp có sẵn · #25 = chỉ text UTF-8 + quét bí mật.
Claude — claude-mcp/app/fsroots.py
| Entry point | #24 | #23 | #10 | #25 |
|---|---|---|---|---|
fs_write (1611) |
FAIL → SỬA: chỉ so cái lá, và bỏ qua cả nó khi cha chưa có ⇒ _name_guard(base, real) 1665 |
PASS _resolve 167 (.., tuyệt đối, .git, symlink, ngoài gốc) |
PASS expected_version_required 1646 + overwrite_guard 1655 |
PASS _public_policy 1666 · NUL 1620 · UTF-8 260–264 |
fs_copy tệp + cây (1732) |
FAIL → SỬA: 1767 (đích) + 1793 (mọi tệp cây nở ra). COPY không bao giờ bỏ qua nguồn | PASS _resolve + unsafe_tree 606/617 |
PASS dest_exists (copy không ghi đè) |
PASS _public_policy 1723/1774 · binary_file 1776 |
fs_move tệp + cây (1968) |
FAIL (D1) → SỬA: 1999 keep=_move_keep(base, src) — bỏ qua ĐÚNG một tên, tên của chính nguồn |
PASS + invalid_move (vào chính nó / vào cha) |
PASS version tệp, mã cây cho thư mục | N/A (không sinh byte mới) |
fs_transaction op write tạo mới (2239) |
FAIL (D4) → SỬA: trước đó KHÔNG gọi guard nào | PASS _resolve |
PASS overwrite_guard 2242 |
PASS _public_policy 2277 |
fs_transaction op copy tệp (2274) / thư mục (2250 + mỗi tệp nở ra 2260) |
FAIL một phần → SỬA: trước chỉ so cái lá | PASS | PASS dest_exists |
PASS binary_file 2273 |
fs_transaction op move (2284) |
FAIL (D1) → SỬA: 2284 kèm keep |
PASS | PASS version / mã cây | N/A |
fs_edit (1566) |
N/A — không bao giờ sinh tên | PASS | PASS expected_version |
PASS _public_policy 1595 |
fs_transaction op restore → trợ lý gh_revert_commit_push |
🔴 FAIL, KHÔNG sửa lượt này — xem mục F.1 | PASS safe_rel |
PASS expected_head + apply --3way |
PASS staged_check |
trợ lý host mcp_host_helper.py |
KHÔNG phải entry point: gốc gh là MỘT thư mục /opt/incomex/mcp-roots/gh, mount read-only vào container MCP, và người gửi việc duy nhất cho trợ lý là chính container đó ⇒ guard chạy trên đúng byte mà trợ lý sắp commit. Ngoại lệ: gh_revert_commit_push, nơi git tự tính tên (mục F.1) |
PASS safe_rel 177 |
PASS check_expected_head 282 |
PASS staged_check |
GPT — agent-data-repo/agent_data/*
| Entry point | #24 | #23 | #10 | #25 |
|---|---|---|---|---|
workspace_write_new → write(create=True) (tools 899) |
FAIL → SỬA: name_twin nay đi qua mọi thành phần MỚI |
PASS relative 93 (DENY, .., tuyệt đối, \) |
PASS chỉ-tạo-mới, FILE_EXISTS 898 |
PASS safe_content 58 · UTF8_TEXT_REQUIRED 804 |
workspace_edit → write(create=False) |
N/A | PASS | PASS VERSION_CONFLICT |
PASS |
workspace_copy / workspace_move → transfer() → transaction() |
FAIL (D1 + D3) → SỬA: load(new=True) (operations 234) nay nhận keep + seen |
PASS | PASS expected_version bắt buộc |
PASS |
workspace_transaction create/copy/move |
FAIL → SỬA: cùng chỗ 234 | PASS | PASS | PASS |
expand_directories (copy/move cả thư mục) |
FAIL → SỬA: tên thư mục ĐÍCH nay được kiểm ở operations 65, TRƯỚC khi nở ra từng tệp | PASS G7 (không lồng vào chính nó) | PASS mã cây VERSION_CONFLICT |
PASS từng tệp |
workspace_import_file → begin + commit → write(create=True) |
FAIL → SỬA (thừa hưởng bản vá của write) |
PASS | PASS | PASS UTF8_TEXT_REQUIRED 117 |
workspace_upload_begin/append/commit → write(create=True) |
FAIL → SỬA ở commit. begin vẫn chỉ hỏi tệp đích có chưa — xem F.2 |
PASS | PASS expected_version 64–68 |
PASS 117 |
restore_transaction (operations 69) |
🔴 FAIL, KHÔNG sửa lượt này — mục F.1 | PASS relative |
PASS expected_head |
PASS staged_check |
B. Luật đã cài — một hàm, hai phía như nhau
Claude _name_guard / _twin_of / _move_keep (fsroots.py) và GPT name_twin(spec, parts, keep, seen)
(workspace_tools.py 685) làm đúng một việc: đi từ gốc xuống, và ở MỖI thành phần chưa tồn tại thì so
nó với toàn bộ anh em trong thư mục đó. keep = những đường dẫn lượt này DỌN ĐI (nguồn của MOVE) —
ngoại lệ duy nhất của §2, lấy từ operations như người gọi viết nên đổi tên cả THƯ MỤC cũng được.
seen = những tên chính lượt gọi này đã hẹn tạo: chưa cái nào nằm trên đĩa lúc kiểm, nên nếu không
nhớ thì hai op của một giao dịch sinh ra cay/ và Cay/ mà không thấy nhau. COPY không bao giờ
truyền keep. Byte của tên đang có KHÔNG bị đổi ở bất cứ nhánh nào.
C. Đỏ → xanh (test đỏ TRƯỚC, đo trên repo tạm)
| Bộ ca | Trước khi sửa | Sau khi sửa |
|---|---|---|
claude-mcp/tests/test_fsroots_name_twin_r03_20260920.py (23 ca) |
18 ĐỎ (13 failures + 5 errors) | 23 xanh |
agent-data-repo/tests/continuation/test_workspace_name_twin_r03.py (21 ca) |
17 ĐỎ | 21 xanh |
Đỏ được đo bằng cách mount ĐÈ bản .bak-twin-20260920 của chính các module đó vào image đang chạy,
nên con số là của cùng một tệp test, không phải của một bản test khác. Phủ §3.2: (a)(b)(c) tự đổi
tên ⇒ được · (d) move sang sinh đôi của tệp KHÁC ⇒ từ chối · (e) COPY ⇒ từ chối (đối chứng âm) ·
(f) create/copy/move/upload/op giao dịch vào Cay/x.txt khi đã có cay/, và bản NFD/NFC ⇒ từ chối ·
(g) op write tạo tệp mới sinh đôi ⇒ từ chối · (h) op move tự đổi hoa-thường ⇒ được · (i) cùng
operation_id ⇒ REPLAY, không commit thứ hai · (j) mọi ca từ chối: HEAD, cây làm việc, trạng thái git
y nguyên, không thư mục ma. Thêm: hai op của CÙNG một giao dịch không được sinh đôi lẫn nhau.
Hồi quy: Claude 138 ca / 8 module xanh · GPT 145 ca xanh (cả hai chạy lại SAU deploy).
run_acceptance.py hai phía: PASS trước deploy và PASS lại sau deploy.
D. Deploy lần hai (§4)
| Claude | GPT | |
|---|---|---|
| Tag hoàn nguyên | claude-mcp-local:rollback-pre-nametwin-20260920 = e33bf3c0efe3 |
agent-data-r03:rollback-pre-nametwin-20260920 = d3f5a6f54744 |
(đã docker inspect đối chiếu) |
trùng ĐÚNG image id đang chạy | trùng ĐÚNG image id đang chạy |
| Image mới | claude-mcp-local:r03-nametwin-20260920 = 7e25b17974bd |
agent-data-r03:20260920-nametwin = 519b2965db4a |
| Overlay | FROM claude-mcp-local:r03-20260920 + COPY app/fsroots.py app/hosttools.py |
FROM agent-data-r03:20260920-lifecycle + COPY workspace_tools.py workspace_operations.py server.py |
| Healthcheck thật | start_period=20s interval=15s timeout=5s retries=5 |
start_period=45s interval=15s timeout=10s retries=10 |
| Healthy sau | 8 s từ State.StartedAt |
113 s từ State.StartedAt |
| RestartCount | 0 | 0 |
Trần §4 là 180 s tính từ State.StartedAt: cả hai trong mốc, không phải hoàn nguyên.
Không restart trợ lý host và incomex-workspace-exec.service (mã hai cái đó không đổi). Cổng PL2 đã
kiểm trước khi dựng lại agent-data: 37 manifest giao dịch, 0 cái ở prepared/push_unknown/
rollback_conflict; hàng đợi jobs/tasks rỗng. docker logs --since 20m sau deploy: 0 Traceback
cả hai container.
Commit mã (cục bộ trên VPS, không push — PL1): claude-mcp 9ee71c6c68db90dd12f362540d5eb22f11d9b3eb ·
agent-data-repo 1d9ef1dbe999e5c9dff78f14bdc0395dc391114b. Chỉ stage đúng tệp của lượt này; cây bẩn
sẵn có của claude-mcp (data/phai-cu/*, docs/mcp-writes/*) KHÔNG bị cuốn vào.
E. Smoke sống (§5) — trong work/mcp-workspace/_thu-nghiem/R03/ theo P16
| # | Đầu nối | Lời gọi | Kết quả |
|---|---|---|---|
| 1 | Claude | fs_move ban-sao.txt → Ban-Sao.txt |
✅ commit 5b2ab35 — đúng ca sáng nay còn bị từ chối |
| 2 | Claude | fs_write Cay/x.txt (đã có cay/) |
✅ TỪ CHỐI name_collision_case, nêu tên cay |
| 3 | Claude | fs_transaction op write BAN-SAO.txt |
✅ TỪ CHỐI name_collision_case (D4 sống) |
| 4 | GPT | workspace_write_new ban-sao-gpt.txt |
✅ commit 3d56ef6 (GPT tự tạo tệp của mình) |
| 5 | GPT | workspace_move → Ban-Sao-Gpt.txt |
✅ commit 7da09c7 |
| 6 | GPT | workspace_write_new Cay/y.txt |
✅ TỪ CHỐI NAME_COLLISION |
| 7 | GPT | workspace_copy Ban-Sao-Gpt.txt → ban-sao-gpt.txt |
✅ TỪ CHỐI NAME_COLLISION (copy không bỏ qua nguồn) |
fs_list(depth=0) sau smoke: không có bản sinh đôi nào trong cả cây — cay/ một mình, Ban-Sao.txt
và Ban-Sao-Gpt.txt mỗi cái một bản. Chéo: GPT workspace_list thấy ngay commit move của Claude
(head=5b2ab35, refreshed_at mới).
F. Ngoài phạm vi — ghi lại, KHÔNG sửa lượt này (P16: chỉ D1–D4)
- 🔴 #28 restore vẫn sinh tên mà không cổng nào nhìn, cả hai phía: trợ lý
act_gh_revert_commit_push(mcp_host_helper.py ~824) và GPTrestore_transaction(workspace_operations.py 69). Đảo một commit rename sẽ dựng lại tên cũ (ví dụcay/bên cạnh mộtCay/mới sinh sau đó), vàarchive_dirđược tạo bằngos.makedirstrần. Agent đã soạn ca đỏ cho cả hai, rồi rút ra khi P16 thu phạm vi. Hướng sửa đã khảo sát: so tên thêm/đổi (git diff --cached --name-status -M) với cây ở HEAD trước, ngoại lệ là nguồn của rename ngược. workspace_upload_beginchỉ hỏi tệp đích đã tồn tại chưa; bản sinh đôi bị bắt ởupload_commit(đã kiểm bằng ca sống). Từ chối vẫn ĐÚNG và không ghi gì — giá phải trả chỉ là số byte đã tải lên phí công. Thêm một chốt sớm ởbeginlà việc của lượt khác.- Image
agent-datađang chạy CŨ hơn chính repo của nó: cây làm việc có bảnworkspace_transfer.pymang bản sửa G4 ngày 19/09 mà image chưa bao giờ nhận (Dockerfile vòng trước cố ý chỉ COPY 3 tệp). Overlay lượt này cố ý không mang nó theo — đã đối chiếudiffgiữa image cũ và image mới:workspace_transfer.pyy hệt. - 🔴
incomex-config-drift-check.serviceđã DRIFT TỪ TRƯỚC lượt này, không phải do lượt này:mcp-compose MISMATCH CONTENT,TOTAL=34 MATCH=33. Baseline/var/lib/incomex-config-guard-v0/baseline/mcp-compose.contentghi ngày 2026-09-18 11:57 và vẫn nóiimage: claude-mcp-local:phase0.9— tức hai vòng deploy trước (harden 19/09 và lifecycle 20/09) đều đổi đúng dòng đó mà không bless lại. Journal chứng minh:STATUS=DRIFTliên tục lúc 06:26, 06:31 … trước khi Agent deploy lúc 07:04. Agent không chạm baseline — bless lại sẽ vô tình đóng dấu cả hai vòng trước. Cần một lượt riêng của Host/Owner. /var/lib/incomex-mcp-helper/queue/incòn 5 tệp.payloadmồ côi từ 18/09 (không có tệp yêu cầu đi kèm). Không chặn gì; không đụng.- Đính chính đặc tả D2 trong PROMPT §1 (§3.1 buộc đọc từ mã): GPT copy/move/import/upload CÓ
kiểm cái LÁ của đích — tất cả đều đi qua
write(create=True)hoặcload(new=True)của giao dịch. Lỗ thật trên những đường đó là D3 (thư mục cha tự sinh) và D1 (move từ chối chính nguồn). - D1 không chỉ của phía Claude như PROMPT §1 ghi:
workspace_movephía GPT cũng từ chối đổi hoa-thường của chính nó (ca đỏtest_d1_move_renames_a_file_to_its_own_upper_lower_spelling). Cả hai đã sửa.
G. Các dòng bảng 29 bị chạm
#24 FAIL → PASS (cả hai phía, mọi entry point trừ restore ở F.1). #8 (nested create), #11/#12 (copy tệp/thư mục), #13/#14 (move tệp/thư mục), #15 (transaction), #17 (import), #18 (upload): hành vi nghiệp vụ KHÔNG đổi, nhưng mỗi dòng nay đi qua cổng #24 đầy đủ — bằng chứng là ca đỏ→xanh tương ứng ở mục C. #28: không đổi mã; còn lỗ #24 ở nhánh restore, ghi ở F.1 — dòng này chưa được coi là sạch #24.
H. Cờ hợp đồng client (§6)
TOOL_LIST_CHANGED= NO — 23 (Claude) và 37 (GPT) giữ nguyên.TOOL_INPUT_SCHEMA_CHANGED= NO — vân tay Claude4f1000e9aad3vàconnectorSchemaHashGPTdbbfc590a969không đổi trước/sau deploy. Đó chính là bằng chứng máy cho dòng này.TOOL_METADATA_CHANGED= YES, nhưng CHỈ ở mã bản dựng:BUILD_IDclaude-mcp-r03-lifecycle-20260920.1→claude-mcp-r03-nametwin-20260920.1(chính hosttools.py ghi luật phải bump khi đổi hành vi tool);CONNECTOR_SCHEMA_VERSIONagent-data-r03-lifecycle-20260920.1→agent-data-r03-nametwin-20260920.1. Không mô tả hay annotation nào bị sửa — theo §3.3, Agent đã rà và không mô tả nào đang nói SAI về tên/va chạm, nên không chạm.SERVER_VERSIONgiữ1.3.0.CLIENT_REFRESH_REQUIRED= YES (do đợt R03) — và CHƯA làm: theo P16 + DCLIENT01, tài khoản ChatGPT Pro không có Refresh, nên GPT phải tạo MCP app mới từ đúng server hiện hữu và Scan Tools/so schema trước khi Owner connect tay, giữ app cũ làm đường lùi. Agent không tạo/connect/ reconnect client nào.
Mốc để phiên client MỚI đối chiếu: Claude claude-mcp-r03-nametwin-20260920.1 · 23 tool · vân tay
4f1000e9aad3. GPT agent-data-r03-nametwin-20260920.1+b1gdc+workspace-20260918 · 37 tool · hash
dbbfc590a969.
R03-NAME-TWIN-REPAIR-20260920-01: MACHINE_DONE — chờ tạo/connect client mới một lần · KB §13.11.2
13.11.3 — RUN R03-FINAL-CLOSE-20260920-01 (2026-09-20): E1–E4, rồi ĐÓNG BĂNG backend
Người làm: Claude Code. Giấy phép: PROMPT_SHA d15eac28b2f8a843e2a4b267cee988fbf44446ba ·
APPROVAL_COMMIT cf2a6ef3ce700c8fdcdb9ad959c812540c66334b · HEAD kho tài liệu lúc khởi động
34a30941ecadaa2fea02e60dfbd6f25f04cb3156. Kiểm trên bản sao riêng dưới /tmp (clone công khai
Huyen1974/incomex-workspace): commit cuối chạm work/mcp-workspace/PROMPT.md đúng bằng PROMPT_SHA
đủ 40 ký tự; work/mcp-workspace/COLLAB.md có REVIEWED@ của GPT Founder (không soạn) + Host
READY@ cùng SHA, và RUN = ACTIVE kèm commit giấy phép. Luật nền đọc bằng git show trong bản sao
đó: §1 + §6 của 7d05219:PROMPT.md, §2 của 8114352:PROMPT.md. PL1–PL3 (đính chính 7159559) áp:
không fetch/pull/checkout/commit bằng tay trong hai clone làm việc; repo mã VPS commit CỤC BỘ theo
path, không push; chỉ restart dịch vụ trong danh sách PL2 và chỉ khi cổng PL2 mở.
Commit mã (VPS là SSOT của mã, không push):
/opt/incomex → 44ff02608e0e5dfaf28cd4c81413cea82c1c228d [Claude Code] claude-mcp + trợ lý (6 tệp).
/opt/incomex/docker/agent-data-repo → 4f428c1cd3af0af00fcd67803e269f4cb2f43601 [Claude Code] agent-data (6 tệp).
Chỉ stage đúng tệp của lượt này; cây bẩn sẵn có (data/phai-cu/*, docs/mcp-writes/*, ba tệp
.bak-twin-20260920 untracked) KHÔNG bị cuốn vào. Khác lượt trước ở một điểm cố ý: hai dòng
image: của compose lần này ĐƯỢC commit — để nguyên chúng không commit chính là thói quen đẻ ra
F.3 và F.4.
A. E1 (F.3) — image = mã
So BYTE từng module python THỰC CHẠY với git show HEAD:<path> và với cây làm việc.
TRƯỚC (41 dòng · 39 "giống", 2 lệch — cả hai ở phía GPT):
| module | image | HEAD | kết |
|---|---|---|---|
agent_data/workspace_idempotency.py |
e66ca880 |
b71fdb7e |
🔴 IMAGE CŨ HƠN HEAD — thiếu G3 (danh tính yêu cầu dẫn xuất cho workspace_import_file), commit 0097c44 ngày 19/09 |
agent_data/workspace_transfer.py |
c98be851 |
8dcfb13c |
🔴 IMAGE CŨ HƠN HEAD — thiếu G4 (khử trùng TRƯỚC FILE_EXISTS, kể cả upload đã commit) và phần G3 "tải hỏng = từ chối sạch", cùng commit 19/09 |
Không phải chuyện lý thuyết: cùng MỘT tệp ca kiểm mới chạy trên ba nền cho ba kết quả khác nhau —
mã ở HEAD ⇒ 6 đỏ; mã lấy thẳng từ image đang chạy ⇒ 7 đỏ, ca thêm là
test_e3_a_good_begin_still_works_and_replays_after_commit, gãy với FILE_EXISTS. Tức dòng #18/#21
từng được chấm 🟢 trên mã mà production chưa bao giờ chạy. Đó là F.3 đo bằng số.
SAU deploy: 41/41 dòng "giống", không còn dòng "image cũ hơn HEAD" nào trong phạm vi hai đầu
nối. Bảng gồm cả hai tiến trình HOST chạy thẳng từ repo (claude-mcp/helper/mcp_host_helper.py,
agent-data-repo/scripts/workspace-exec-worker.py). G4 chạy thật trên production: xem mục E.
Module ngoài hai đầu nối (KB…) không chạm; không module nào của hai đầu nối còn thay đổi chưa commit.
🔴 Một quyết định phải nói thẳng. Bản HEAD của workspace_transfer.py còn mang MỘT thay đổi chạm
tới CLIENT: mô tả của workspace_import_file (thêm "into a new nested folder if needed" + câu về
replay). _mcp_schema_hash() là sha1 trên TOÀN BỘ MCP_TOOLS kể cả description, nên bê
nguyên bản HEAD vào image sẽ làm dbbfc590a969 đổi. §2 của PROMPT cấm đổi hash (đổi ⇒ DỪNG trước
deploy) và cổng Scan của app MCP mới đọc đúng con số đó. Mô tả cũ không nói SAI điều gì, chỉ nói
thiếu. Nên: giữ mô tả y hệt bản production đang phục vụ, và commit chính bản giữ đó để
image = HEAD. Bản mô tả đầy đủ hơn là NỢ, dành cho lượt nào được phép đổi bề mặt client.
Đã đo trước khi deploy trên chính image mới: 37 tool · hash dbbfc590a969 không đổi.
B. E2 (F.1) — hoàn tác (#28) không còn sinh tên sinh đôi, cả hai phía
Luật cài đúng §2 bản 8114352, nhưng đọc từ CÂY GIT chứ không từ đĩa — vì lúc kiểm thì
git apply --reverse --index đã đặt trạng thái mới lên cả cây làm việc lẫn chỉ mục, nên đĩa không
còn nói được tên nào là "mới". Một hàm, hai phía như nhau:
before= cây ở HEAD,after= cây mà commit bù sẽ để lại — cả hai đã nở thư mục cha ra thành tên riêng, vì luật là luật của TỪNG thành phần.- Tên bị kiểm =
after − before. Ngoại lệ =before − after, những tên mà CHÍNH commit này dọn đi — đúng là "nguồn của rename ngược", nên hoàn tác một lượt tự đổi hoa-thường vẫn làm được. archive_dirđi qua cùng cổng: đích lưu trữ được đưa vàoaftertrước khi ghi gì, rồios.makedirs(..., exist_ok=True)trần được thay bằng tạo TỪNG thành phần, mỗi thành phần mới vẫn so với anh em ngay tại chỗ (phía GPT gọi thẳngname_twin— đúng hàm mọi đường ghi khác dùng).
Chỗ sửa: trợ lý host act_gh_revert_commit_push (claude-mcp/helper/mcp_host_helper.py) và
restore_transaction (agent-data-repo/agent_data/workspace_operations.py).
Ca đỏ mà lượt trước soạn rồi rút ra không được giữ lại ở đâu cả (không trên đĩa, không trong lịch sử Git của hai repo mã) — nên ca được soạn lại theo đúng đặc tả ở F.1.
| Bộ ca | Trước khi sửa | Sau khi sửa |
|---|---|---|
claude-mcp/tests/test_helper_name_twin_r03_20260920.py (9 ca, git thật + remote thật) |
5 ĐỎ | 9 xanh |
agent-data-repo/tests/continuation/test_workspace_restore_twin_r03.py (10 ca, E2+E3) |
6 ĐỎ trên mã HEAD · 7 ĐỎ trên mã của image đang chạy | 10 xanh |
Phủ: hoàn tác dựng lại THƯ MỤC sinh đôi ⇒ từ chối · dựng lại TỆP sinh đôi ⇒ từ chối · bản NFD/NFC
⇒ từ chối · archive_dir sinh đôi ⇒ từ chối · ngoại lệ: hoàn tác một lượt tự đổi hoa-thường ⇒
ĐƯỢC · đối chứng: hoàn tác thường ⇒ được, lưu trữ vào thư mục mới ⇒ được, tệp bị commit cũ dời ⇒
dời ngược không tạo bản thứ hai · mọi ca từ chối: HEAD, tệp trong cây và trạng thái git y nguyên,
không thư mục ma.
C. E3 (F.2) — workspace_upload_begin hỏi #24 ngay ở begin
w.name_twin(spec, w.file_parts(path)) chạy ngay sau prepare_git trong khoá gốc, TRƯỚC vòng quét
resume và trước khi nhận một byte nào. Tên đã có ĐÚNG BYTE không phải sinh đôi, nên resume và
replay G4 vẫn tới được câu trả lời của mình.
Một ca cũ phải sửa vì hành vi đặc tả đổi, không phải vì hạ chuẩn:
test_d3_upload_commit_into_a_twin_folder_is_refused_and_writes_nothing ghim cổng ở upload_commit
(khi viết nó, begin chỉ hỏi tệp đích có chưa, nên phải tải hết payload rồi mới bị từ chối). Nay
đổi tên thành test_d3_upload_into_a_twin_folder_is_refused_at_begin_and_writes_nothing: cùng một
tính chất — tải vào Cay/ khi đã có cay/ thì không ghi gì — chỉ khác thời điểm chứng minh.
D. Deploy — lần cuối của R03
| Claude — Incomex VPS | GPT — Incomex AgentData | |
|---|---|---|
| Tag hoàn nguyên | claude-mcp-local:rollback-pre-finalclose-20260920 = 7e25b17974bd |
agent-data-r03:rollback-pre-finalclose-20260920 = 519b2965db4a |
(docker inspect đối chiếu) |
trùng ĐÚNG image id đang chạy | trùng ĐÚNG image id đang chạy |
| Image mới | claude-mcp-local:r03-finalclose-20260920 = 6b48248fa29f |
agent-data-r03:20260920-finalclose = cb20894c3bf8 |
| Overlay | FROM :r03-nametwin-20260920 + COPY app/hosttools.py |
FROM :20260920-nametwin + COPY 5 tệp workspace_operations/transfer/idempotency/tools + server.py |
| Healthcheck thật | start_period=20s interval=15s timeout=5s retries=5 |
start_period=45s interval=15s timeout=10s retries=10 |
| Healthy sau | 6 s từ State.StartedAt |
113 s từ State.StartedAt |
| RestartCount | 0 | 0 |
run_acceptance.py sau deploy |
ACCEPTANCE: PASS (147 ca / 9 module + cổng phát hành) | ACCEPTANCE: PASS (155 ca + tools/list qua route công khai) |
Trần §3 là 180 s tính từ State.StartedAt: cả hai trong mốc. Trước deploy cả hai cũng đã
ACCEPTANCE: PASS (Claude 147, GPT 155), nên đỏ→xanh là của chính lượt này chứ không phải nền sẵn.
Vì sao container claude-mcp cũng được dựng lại dù E2 nằm ở trợ lý host: hosttools.py tự viết
luật "bump BUILD_ID trong cùng commit với BẤT KỲ thay đổi schema HOẶC hành vi tool", mà hành vi
của op restore trong fs_transaction đã đổi (nay từ chối được). Bump BUILD_ID ⇒ phải dựng lại.
Vân tay không đổi vì vân tay chỉ băm tên + mô tả tool.
Trợ lý host incomex-mcp-helper.service được restart sau khi kiểm cổng PL2: hàng đợi
/var/lib/incomex-mcp-helper/queue/in không có tệp yêu cầu nào (chỉ 5 .payload mồ côi từ 18/09,
để nguyên), 223 manifest giao dịch = 145 committed + 73 applied + 5 rolled_back, 0 cái
ở prepared/push_unknown/rollback_conflict, hàng đợi jobs/tasks rỗng.
incomex-workspace-exec.service KHÔNG restart (mã của nó không đổi).
🔴 Một quan sát phải nói thẳng, không phải của lượt này: docker logs của incomex-agent-data
sau deploy có 1 Traceback lúc khởi động — zipfile.BadZipFile: File is not a zip file trong
langroid/parsing/search.py → nltk.data.find. Đây là một cuộc đua lúc boot: hai worker uvicorn cùng
tải bộ liệu NLTK vào /root/nltk_data (nằm TRONG container, dựng lại mỗi lần recreate), một cái đọc
wordnet.zip trong khi cái kia còn đang ghi. Worker chết, được sinh lại, cả hai Application startup complete, các zip hiện đều hợp lệ, container healthy, RestartCount=0, acceptance PASS. Không
phải do lượt này: delta của server.py giữa image lùi và image mới chỉ đúng một dòng
CONNECTOR_SCHEMA_VERSION (đã diff để chứng minh), và cả stack không có khung nào của
agent_data.workspace_*. Cũng chính nó giải thích 113 s tới healthy. Ghi nợ, không sửa (ngoài
4 việc). claude-mcp: 0 Traceback; journal trợ lý: 0 lỗi.
E. Smoke sống (§4) — trong work/mcp-workspace/_thu-nghiem/R03/e2e-finalclose/
| # | Đầu nối | Lời gọi | Kết quả |
|---|---|---|---|
| 1 | Claude | fs_write cay/a.txt → fs_move sang khac/a.txt (98d7400) → fs_write Cay/b.txt (369ad88) |
✅ ba bước đều được; cay/ rỗng đã được dọn nên Cay/ là tên hợp lệ |
| 2 | Claude | fs_transaction op restore version=98d7400 |
✅ TỪ CHỐI name_collision, nêu đúng …/claude/Cay đang có, "KHÔNG có gì bị ghi; bản clone ĐÃ trả về trạng thái trước" |
| 3 | Claude | hoàn tác một commit SỬA (2870d19) |
✅ được — commit bù e301fbc, nội dung trở lại 26 B |
| 4 | GPT | workspace_write_new cay/a.txt → workspace_move sang khac/a.txt (8e903ed) → workspace_write_new Cay/b.txt |
✅ |
| 5 | GPT | workspace_transaction op restore version=8e903ed |
✅ TỪ CHỐI NAME_COLLISION, "Nothing was written; the undo is rolled back" |
| 6 | GPT | hoàn tác một commit SỬA (56fdca9) |
✅ được — commit bù a41cc97 |
| 7 | GPT | workspace_upload_begin vào cay/tai-len.txt (đã có Cay/) |
✅ TỪ CHỐI NAME_COLLISION ngay ở begin, không upload_id, không nhận byte nào |
| 8 | GPT | upload_begin + append + commit vào tên sạch tai-len/moi.txt (49ed255) |
✅ |
| 9 | GPT | G4: gọi LẠI đúng upload_begin đó sau khi đã commit |
✅ trả về cùng upload_id, resumed:true, status:"committed" kèm nguyên result đã ghi — không FILE_EXISTS |
Ca 9 chính là bằng chứng "G4 chạy thật" mà E1 đòi: cho tới sáng nay production trả FILE_EXISTS ở
đúng lời gọi này. Lưu ý câu chữ: §4 của PROMPT viết "upload_begin cùng operation_id", nhưng
workspace_upload_begin không có tham số operation_id (chỉ 7 tool có: workspace_edit,
write_new, copy, move, transaction, exec, task_start). Danh tính của G4 là chính yêu cầu
(root,path,sha256,total_bytes), và đó là thứ đã được đo.
fs_list/workspace_list sau smoke: không có bản sinh đôi nào — mỗi bên đúng một Cay/, không
có cay/. Chéo hai chiều vẫn tươi: fs_list của Claude thấy ngay head=49ed255 (commit cuối của
GPT), workspace_list của GPT thấy cây Claude vừa ghi.
F. E4 (F.4) — mốc kiểm cấu hình
Trước: TOTAL=34 MATCH=33 MISMATCH=1, STATUS=DRIFT, incomex-config-drift-check.service failed
liên tục từ 2026-09-18. Đích lệch duy nhất là mcp-compose. (Nhãn trên chính unit còn nói
nginx-default-conf lệch 49 dòng từ 27/07 — đã hết, nay MATCH; nhãn đó cũ.)
Từng dòng lệch, diff baseline ↔ cấu hình thật:
- image: claude-mcp-local:phase0.9
+ image: claude-mcp-local:r03-finalclose-20260920
Đúng MỘT dòng, và đúng là dòng image: — baseline đóng dấu 2026-09-18 11:57, còn bốn vòng deploy
sau đó (harden 19/09, lifecycle 20/09, nametwin 20/09, finalclose hôm nay) đều đổi đúng dòng đó mà
không bless lại. Điều kiện Owner đặt cho E4 thoả: không có dòng lệch nào khác.
Đã làm: (1) sao lưu baseline cũ ngay cạnh nó —
/var/lib/incomex-config-guard-v0/baseline/mcp-compose.{content,meta,sha256}.bak-pre-finalclose-20260920;
(2) bless qua ĐÚNG cổng do chính unit chỉ định, /usr/local/sbin/incomex-config-apply-v0, với
candidate = bản sống y hệt từng byte và --expected-baseline = sha của chính bản sống ⇒
RESULT=APPLIED, POST_ACTION=NONE, có dòng audit và bản lùi trong rollback/. Không đổi một byte
nội dung nào.
Một chi tiết của cổng, ghi để lượt sau không mất thì giờ: validator DOCKER_COMPOSE_CONFIG chạy
docker compose -f <candidate> config -q, mà tệp compose này có env_file: - .env tương đối, nên
lần gọi đầu bị REJECTED_UNSAFE_CANDIDATE. Cách đúng là đặt một .env RỖNG cạnh bản nháp:
tệp này có 0 chỗ ${...} (đã đếm), nên .env rỗng không thể làm đổi kết quả thay biến — chính
bẫy mà validator tự nêu — và không một byte bí mật nào bị chép sang thư mục thay đổi.
Sau: TOTAL=34 MATCH=34 MISMATCH=0 MISSING=0 ERROR=0, STATUS=CLEAN, unit chạy lại thoát 0
(hết failed). F.4 đóng.
G. Các dòng bảng 29 bị chạm
- #18 LARGE/RESUMABLE UPLOAD —
beginnay từ chối tên sinh đôi TRƯỚC khi nhận byte; và G4 (khử trùng trướcFILE_EXISTS, kể cả upload đã commit) lần đầu có mặt trong production. Trước lượt này dòng #18 được chấm trên mã mà image chưa từng chạy. - #21 IDEMPOTENCY/LOST RESPONSE — G3 (danh tính dẫn xuất cho
workspace_import_file, và tải hỏng = từ chối sạch thay vì cách ly id) cũng lần đầu có mặt trong production. - #24 UNICODE/NFC/PORTABILITY — nay sạch ở mọi entry point: đường cuối cùng còn hở (restore) đã đóng ở cả hai phía. Bảng entry point của §13.11.2 mục A: hai ô 🔴 "FAIL, KHÔNG sửa lượt này" chuyển thành PASS.
- #28 HISTORICAL READ / SAFE RESTORE — hành vi hoàn tác không đổi (vẫn đảo delta, vẫn giữ thay
đổi sau không chồng lấn, vẫn no-delete qua
archive_dir), nhưng nay đi qua cổng #24 đầy đủ. Dòng này được coi là sạch #24 — điều §13.11.2 mục G nói rõ là CHƯA.
Các dòng khác không đổi. #27 CLIENT BINDING vẫn là việc của tầng client (mục §6 của PROMPT), Agent không chấm.
H. Cờ hợp đồng client
TOOL_LIST_CHANGED= NO — 23 (Claude) và 37 (GPT) giữ nguyên, không tool nào được thêm/bớt/đổi tên.TOOL_INPUT_SCHEMA_CHANGED= NO — đo TRƯỚC deploy trên chính image mới và đo LẠI sau deploy: vân tay Claude4f1000e9aad3vàconnectorSchemaHashGPTdbbfc590a969KHÔNG đổi.TOOL_METADATA_CHANGED= YES, nhưng CHỈ ở mã bản dựng:BUILD_IDclaude-mcp-r03-nametwin-20260920.1→claude-mcp-r03-finalclose-20260920.1;CONNECTOR_SCHEMA_VERSIONagent-data-r03-nametwin-20260920.1→agent-data-r03-finalclose-20260920.1. Không mô tả hay annotation nào bị sửa — kể cả mô tảworkspace_import_file, đã cố ý giữ nguyên (mục A).SERVER_VERSIONgiữ1.3.0.CLIENT_REFRESH_REQUIRED= YES (do đợt R03) — và CHƯA làm: Agent không tạo/connect/ reconnect client nào.
Mốc cho phiên client MỚI đối chiếu:
Claude claude-mcp-r03-finalclose-20260920.1 · 23 tool · vân tay 4f1000e9aad3.
GPT agent-data-r03-finalclose-20260920.1+b1gdc+workspace-20260918 · 37 tool · hash
dbbfc590a969.
I. Ngoài phạm vi — ghi lại, KHÔNG sửa
- NỢ mô tả
workspace_import_file(mục A): bản đầy đủ hơn đã có trong lịch sử Git củaworkspace_transfer.py, chờ lượt được phép đổi bề mặt client. tests/run_acceptance.pyphía GPT vẫn mặc địnhACCEPTANCE_IMAGE=agent-data-r03:20260920-lifecycle— một image cũ hai vòng. Lượt này truyền biến môi trường đúng image mỗi lần chạy. Sửa mặc định nằm ngoài 4 việc.- Cuộc đua NLTK lúc boot của
agent-data(mục D) — tự lành, nhưng là lý do thật của 113 s tới healthy. - 5 tệp
.payloadmồ côi trong/var/lib/incomex-mcp-helper/queue/in(từ 18/09): để nguyên, đúng lệnh. - Ba tệp
*.bak-twin-20260920untracked trong hai repo mã: để nguyên. _thu-nghiem/không dọn; không xoá gì (xoá là quyền Owner).- Không chạm
AGENTS.md,README.md,COLLAB.md(gốc vàwork/mcp-workspace/),PROMPT.md,DANH-MUC-CONG-CU.md. Không mở route/nginx mới, không đổi auth/URL/secret, không push kho mã.
Từ đây backend R03 ĐÓNG BĂNG: không deploy gì nữa (trừ rollback) cho tới khi nghiệm thu client xong.
R03-FINAL-CLOSE-20260920-01: MACHINE_DONE — backend đóng băng, chờ tạo/connect client một lần · KB §13.11.3