Cổng chặn lệnh của tôi chết 14 tiếng 5 phút và không một hệ thống nào báo — vì nó báo bằng dòng log
2026-09-27 · H&V Company
Mười bốn tiếng năm phút
Ngày 26/09/2026, cổng cấm vốn của tôi — thứ duy nhất đứng giữa một tín hiệu và một lệnh thật — không chạy được từ 00:32 đến 14:37. Mười bốn tiếng năm phút.
Trong 14 tiếng đó, mọi tín hiệu tới webhook đều được phép đi.
Không job nào báo. Không mail nào tới. Tôi biết chuyện này lúc 16:26 cùng ngày, khi chạy tay capital_ban_gate.py để kiểm một việc khác.
Điều làm tôi viết bài này không phải con số 14 tiếng. Là chuyện cổng có kêu — và tiếng kêu đó đi vào đúng nơi không ai nghe.
Hai tầng, hai hành vi ngược nhau
Hệ thống có hai tầng bảo vệ, và tôi từng nghĩ chúng cùng hướng. Chúng không.
| tầng | kích hoạt khi | hành vi |
|---|
logic cổng capital_ban_gate.py:91 | cổng chạy được nhưng thiếu cấu hình | ✅ CHẶN (fail-closed) |
lớp bọc webhook_server.py:459-461 | cổng không chạy nổi (import fail / crash / treo) | 🔴 LỆNH VẪN ĐI (fail-open) |
Đây là lớp bọc, nguyên văn:
def _capital_ban_chan(nhan: str) -> bool:
try:
import capital_ban_gate as _cbg
chan, _ly_do = _cbg.kiem_tra()
except Exception as _e: # cổng hỏng KHÔNG được chặn lệnh,
log.error(f"🔴 CỔNG CẤM VỐN LỖI ({_e}) — lệnh vẫn đi, cổng đang MÙ")
return False # nhưng phải kêu to.
...
Đọc kỹ dòng comment tôi tự viết: "cổng hỏng KHÔNG được chặn lệnh, nhưng phải kêu to." Đó là một quyết định có ý thức, không phải bug. Lý do lúc đó: không muốn một lỗi import làm đóng băng cả hệ thống giao dịch.
Và nó có kêu to. log.error. Với emoji đỏ.
Không ai đọc log. Không có job nào grep file đó. Không có alert nào gắn vào nó. Nó "kêu to" vào một căn phòng trống suốt 14 tiếng.
Đó là toàn bộ lỗ hổng. Không phải logic sai — logic đúng như tôi thiết kế. Sai ở chỗ tôi coi ghi log là báo động.
Điều tôi đã kết luận sai về chính lỗi này
Phiên kiểm soát đầu tiên tôi viết hai kết luận, và cả hai đều sai. Ghi lại vì cái sai này dễ lặp:
| tôi nói | thật |
|---|
| "dọn cây thư mục B thì cổng mất nguồn cấu hình" | ❌ SAI. capital_ban_gate.py:47-49 → OFF_PATH = DIR/setup_off.json (cây A). BAN_PATHS[1] trỏ thư mục CHA của B, không phải B. setup_gate3.py:185 đọc HERE/gate_von.json cũng cây A. Không nguồn nào của cổng nằm trong cây B. |
| "cổng fail-open" | ❌ nửa sai. Logic cổng là fail-closed từ 26/09: capital_ban_gate.py:91 → if not giu: return True. Chỉ lớp bọc mới fail-open. |
Tôi sai vì suy từ tên đường dẫn thay vì đọc dòng code. projects/trading/ và projects/trading/ES-NQ/ trông giống nhau trong đầu tôi; chúng là hai thư mục khác nhau trên đĩa.
Kiểm bằng lệnh thay vì bằng trực giác cho ra kết luận ngược: kiến trúc hai cây là đúng, không cần gộp — cây A chạy lệnh (435 file), cây B phân tích (53 file), 19 file trùng tên thì 16 giống hệt, chỉ 2 lệch thật.
Tôi để cái sai này trong bài vì phần lớn nội dung kỹ thuật tôi đọc chỉ đăng phiên bản sau khi đã đúng. Cái sai là phần có giá trị: nó cho thấy một chẩn đoán nghe hợp lý ("dọn thư mục làm mất cấu hình") sống được bao lâu trước khi một lệnh grep giết nó.
Vì sao fail-closed không phải câu trả lời ở đây
Phản xạ hiển nhiên: sửa return False thành return True. Cổng mù thì chặn hết.
Tôi không làm, và đây là lý do đo được:
Sáng 27/09, gate_von.json cho verdict KHONG_CO_CAU_HINH_SONG — 0/18 cặp (setup × sàn) qua đủ 5 cổng. Tức cổng đang CHẶN = True một cách hợp lệ. Trong trạng thái đó, đổi lớp bọc sang fail-closed không thêm một mảy bảo vệ nào, nhưng thêm một cách mới để cả hệ thống đứng: một lỗi import trong thư viện phụ giờ có thể đóng băng đường phát lệnh mà không ai biết vì sao.
Đổi fail-open thành fail-closed là đổi chế độ lỗi im lặng này lấy chế độ lỗi im lặng khác. Vấn đề chưa bao giờ là hướng của return. Vấn đề là 14 tiếng không ai biết.
Cái phải sửa là khâu biết.
Thứ tôi dựng thay vào: cổng tự báo khi nó chết
mach_cong.py, chạy mỗi 15 phút qua launchd (com.hv.trading.machcong):
- Gọi cổng đúng cách webhook gọi nó. Không viết lại logic kiểm — viết lại là tự
cấp chứng chỉ cho mình. Nếu webhook import capital_ban_gate rồi gọi kiem_tra() thì mạch làm y hệt, cùng cwd, cùng biến môi trường.
- Chạy trong tiến trình con. Cổng chết nặng (segfault,
sys.exit) không giết luôn
mạch — mạch vẫn còn sống để báo. Đây là chỗ một healthcheck in-process sẽ chết cùng thứ nó đang canh.
- Timeout 90 giây. Bắt cả ca TREO — nguy hiểm hơn ca crash, vì treo không sinh
exception nào để log.
- Mail đi thẳng, KHÔNG qua
email_gate. Cổng email của tôi mặc định im lặng để
chống spam; đúng loại tin này sẽ bị nó nuốt. Kênh báo động không được đi qua thứ có quyền im lặng.
- Chống spam bằng đổi-trạng-thái: chỉ bắn khi sống↔chết đổi chiều, hoặc mỗi 6 giờ
nếu vẫn chết. Mạch bắn mỗi 15 phút sẽ bị lọc vào thư rác trong hai ngày, và lúc đó nó tệ hơn không có.
- **KHÔNG sửa cổng, KHÔNG chặn lệnh, KHÔNG đụng tham số tiền thật, KHÔNG đụng job
đang chạy.** Thứ đo không được phép thay đổi thứ bị đo.
Sổ nó ghi, đọc được bằng mắt:
{"song": true, "chan": true,
"ly_do": "CẤM VỐN TOÀN BỘ: gate_von.json verdict=KHONG_CO_CAU_HINH_SONG — 0/18 cặp qua đủ cổng",
"luc": "2026-09-27T06:09:50"}
Hai trường quan trọng là hai câu hỏi khác nhau, và trước 26/09 tôi gộp chúng làm một: song = cổng có chạy được không. chan = cổng có đang chặn không. Một cổng song=false báo chan=false không phải là "cho phép" — nó là "không biết". Suốt 14 tiếng ngày 26/09, hệ thống đọc "không biết" thành "cho phép".
Điều đáng lấy đi khỏi bài này
log.error không phải báo động. Nó là báo động chỉ khi có một tiến trình khác
đọc nó và biết gọi ai. Nếu không, nó là nhật ký cho buổi hậu kiểm — tức là cho sau khi thiệt hại xảy ra.
- Kiểm cả hai tầng, không chỉ tầng logic. Cổng của bạn có thể fail-closed hoàn hảo
và vẫn vô dụng nếu lớp gọi nó fail-open. Hỏi: "nếu cổng không chạy nổi thì lệnh có đi không?" — không phải "nếu cổng thiếu cấu hình thì sao?".
- Thứ canh phải sống lâu hơn thứ bị canh. Tiến trình con, có timeout, kênh báo
riêng không qua bất cứ cổng im-lặng-mặc-định nào.
- Phân biệt "không" với "không biết". Số 0 do đọc-không-được và số 0 do
thật-sự-không-có phải là hai giá trị khác nhau trong sổ, nếu không bạn sẽ ra quyết định trên một con số dối.
- Đọc dòng code, đừng suy từ tên đường dẫn. Tôi mất một chẩn đoán sai vào bài học
này.
Tự kiểm chứng
# trạng thái cổng hiện tại (song / chan / lý do / thời điểm kiểm)
cat ~/Documents/company/projects/trading/mach_cong.json
# lớp bọc fail-open, nguyên văn
sed -n '455,465p' ~/Documents/trading/ES-NQ/webhook_server.py
# logic cổng fail-closed
sed -n '85,95p' ~/Documents/company/projects/trading/capital_ban_gate.py
# job canh có đang chạy không
launchctl list | grep machcong
Cảnh báo rủi ro
Bài này là phân tích giáo dục về hạ tầng giám sát rủi ro, không phải lời khuyên đầu tư. Hiệu suất quá khứ không bảo đảm kết quả tương lai. Giao dịch đòn bẩy có rủi ro mất toàn bộ vốn. Hệ thống mô tả trong bài chạy trên tài khoản demo (demo.ctrader.5885753); lỗ hổng fail-open mô tả ở đây áp dụng như nhau cho tài khoản thật, và đó là lý do tôi công bố nó. Tôi không nhận góp vốn, không bán tín hiệu.