Hồi quy đã vá
-------------
F-12 Kéo–thả hoặc dán tệp vào ô chat ném NameError. R08 tách `_Input` sang
`chat_input_box.py` nhưng để `_paths_from_mime()` ở lại
`composer_widget.py`, nên hai hàm sự kiện Qt gọi một cái tên không tồn
tại. Bốn hàm dùng chung chuyển sang `composer_mime.py` — module thứ ba
là chỗ duy nhất không lặp lại được lỗi này. Đo lại: cả thả lẫn dán đều
gắn 1 tệp, khớp bản trước refactor.
F-01 Đổi provider thì bộ chọn model AI-Edit không làm gì. Hook cũ kiểm
`folder.ai_model_combo`, thuộc tính R08-T12 đã dời sang
`ai_panel.resolver`. Làm mới vô điều kiện, đúng như tab cũ: lần lấy đầu
tiên hỏng thì đổi provider chính là lúc phải thử lại.
F-07 Hàng chọn kỳ của Dashboard bị đẩy xuống dưới các thẻ số liệu. Hàng này
lọc CẢ BA thẻ con chứ không riêng biểu đồ, nên để nó nằm dưới là bắt
người dùng đọc con số trước khi thấy con số đó tính cho kỳ nào. Kèm
theo: `TokenUsageCardWidget` bị bỏ sót `setContentsMargins(0,0,0,0)`
mà hai thẻ con còn lại đã có, đẩy cả hàng thẻ lệch 9px.
`check_layout_geometry` nay khớp TỪNG BYTE với bản trước refactor.
F-11 Hai lớp khai trùng tên phương thức; Python giữ bản sau nên bản đầu là
mã chết. `co4e_tab.py::showEvent` bản đầu gọi `_narrow_guard.attach()`
và không bao giờ chạy.
Tách file (F-09)
----------------
Bốn file chạm trần 400 dòng, mỗi lần cắt ra một trách nhiệm thật:
graph_renderer.py -> graph_scene_builder.py + graph_export.py
co4e_workflow_service.py -> co4e_run_history.py
json_config_repository.py -> config_sections.py
agents_admin_tab.py -> shared/agent_kind_visuals.py
File cuối còn xoá 3 bản sao của hàm đã có trong `shared/formatters.py`,
giống hệt đến từng dòng — nay định dạng thời gian và avatar không lệch nhau
giữa các bảng Giám sát nữa.
Docstring
---------
41,6% -> 100% (3.478/3.478 định nghĩa production), kể cả module dormant và
phương thức dunder. Toàn bộ phần bổ sung viết bằng tiếng Việt; comment tiếng
Anh có sẵn giữ nguyên — dịch ngược là một đợt riêng.
Seam chưa nối dây (F-05)
------------------------
9 seam mang nhãn `SEAM · dựng <ngày>` kèm hai câu: được nối khi nào, và để
dormant thì hỏng gì. Ngày lấy từ lịch sử git, không phải hạn tự đặt. Gate O
đọc nhãn đó và nhắc khi quá 30 ngày.
859 test xanh · 4/4 cổng CASAN · 19/24 checker khớp từng byte bản cũ.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
129 lines
5.3 KiB
Python
129 lines
5.3 KiB
Python
"""Cổng chính sách cho lời gọi tool — hình dạng dữ liệu, chưa phải cài đặt.
|
|
|
|
BẢN ĐỀ XUẤT, chờ Team Hoa xác nhận
|
|
==================================
|
|
Sơ đồ phân hệ trong ``plan.md`` giao ``domain/security/`` cho Team Gamma và
|
|
``application/conversations/tool_policy_gateway.py`` cho Team Hoa. Nên Gamma
|
|
định nghĩa *hình dạng*, Hoa *cài đặt*.
|
|
|
|
Viết trước vì N3 (Co4E) cần gọi tool và Team Hoa chưa bắt đầu. Không có nó thì
|
|
N3 phải tự phỏng đoán rồi sửa lại sau — mà phỏng đoán của một người thì tệ hơn
|
|
một đề xuất viết ra để cả hai bên soi.
|
|
|
|
Nếu Hoa thấy khác, sửa file này chứ đừng đẻ kiểu thứ hai. Đổi sớm rẻ hơn đổi
|
|
muộn: hiện chỉ N3 dùng.
|
|
|
|
Mô hình bám theo code đang chạy, không bịa:
|
|
* ``core/agent_security.py::SecurityVerdict`` — allowed / reason / layer
|
|
* ``ui/permission_dialog.py`` — hộp thoại hỏi người dùng khi
|
|
``ctx.project_confirm_commands()`` bật (``ui/chat_panel.py:1312``)
|
|
|
|
Điểm khác biệt duy nhất so với hôm nay: gộp hai thứ đó thành **một câu trả lời
|
|
ba trạng thái**, thay vì code gọi phải tự nhớ hỏi cả hai nơi.
|
|
|
|
SEAM · dựng 2026-08-21 · chưa nối dây (F-05)
|
|
------------------------------------------------------------
|
|
Được nối khi: ``application/conversations/tool_policy_gateway.py`` trả về ``PolicyDecision`` thay cho ``bool``.
|
|
Để dormant thì sao: Hiện gateway chỉ trả đúng/sai nên lý do chặn bị mất —
|
|
đúng thứ kiểu dữ liệu này sinh ra để mang theo.
|
|
|
|
Cổng ``scripts/check_orphan_modules.py`` đếm tuổi seam từ ngày trên
|
|
và nhắc khi quá ``SEAM_MAX_AGE_DAYS``. Đổi nội dung dòng đó thì cổng
|
|
đọc theo — đừng sửa ngày để làm im lời nhắc.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Any, Dict, Protocol, runtime_checkable
|
|
|
|
|
|
class PolicyOutcome(str, Enum):
|
|
"""Ba trạng thái. ``ASK`` là thứ hệ thống hiện tại đã có (hộp thoại xin
|
|
phép) nhưng chưa được coi là một kết quả chính thức."""
|
|
|
|
ALLOW = "allow"
|
|
DENY = "deny"
|
|
ASK = "ask"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolCallRequest:
|
|
"""Một lời gọi tool đang chờ được duyệt.
|
|
|
|
``surface`` cho biết chỗ phát sinh — ``"cowork"``, ``"code"``, ``"co4e"``,
|
|
``"task"``. Chính sách khác nhau theo màn: Co4E chạy nền nên không thể bật
|
|
hộp thoại hỏi giữa chừng như Cowork.
|
|
"""
|
|
|
|
name: str
|
|
arguments: Dict[str, Any] = field(default_factory=dict)
|
|
surface: str = "cowork"
|
|
project_id: str = ""
|
|
#: True nếu tool đến từ MCP server ngoài, False nếu là tool dựng sẵn.
|
|
external: bool = False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PolicyDecision:
|
|
"""Câu trả lời của cổng.
|
|
|
|
``reason`` bắt buộc có khi DENY hoặc ASK — người dùng phải biết vì sao bị
|
|
chặn, và ``core/audit_log.py`` cần nó để ghi lại.
|
|
|
|
``layer`` giữ đúng từ vựng của ``SecurityVerdict``: ``"prompt"`` |
|
|
``"attachment"`` | ``"command"``, cộng thêm ``"policy"`` cho quyết định của
|
|
chính cổng này.
|
|
"""
|
|
|
|
outcome: PolicyOutcome
|
|
reason: str = ""
|
|
layer: str = "policy"
|
|
|
|
@property
|
|
def allowed(self) -> bool:
|
|
"""Tương thích với chỗ đang đọc ``SecurityVerdict.allowed``.
|
|
|
|
Chú ý: ``ASK`` KHÔNG phải allowed — còn phải hỏi người dùng đã.
|
|
"""
|
|
return self.outcome is PolicyOutcome.ALLOW
|
|
|
|
def __post_init__(self):
|
|
"""Ép mọi quyết định DENY/ASK phải kèm lý do.
|
|
|
|
Người dùng thấy lý do trên hộp thoại, và nhật ký kiểm toán ghi lại nó — một
|
|
quyết định chặn không lý do là không truy được về sau.
|
|
"""
|
|
if self.outcome is not PolicyOutcome.ALLOW and not self.reason:
|
|
raise ValueError("DENY và ASK bắt buộc có reason — người dùng và "
|
|
"audit log đều cần biết vì sao")
|
|
|
|
|
|
def allow() -> PolicyDecision:
|
|
"""Quyết định cho phép. Không cần lý do: đây là đường đi bình thường."""
|
|
return PolicyDecision(PolicyOutcome.ALLOW)
|
|
|
|
|
|
def deny(reason: str, layer: str = "policy") -> PolicyDecision:
|
|
"""Quyết định chặn hẳn, kèm lý do và tên lớp đã ra quyết định."""
|
|
return PolicyDecision(PolicyOutcome.DENY, reason, layer)
|
|
|
|
|
|
def ask(reason: str, layer: str = "policy") -> PolicyDecision:
|
|
"""Quyết định phải hỏi người dùng, kèm lý do và tên lớp đã ra quyết định."""
|
|
return PolicyDecision(PolicyOutcome.ASK, reason, layer)
|
|
|
|
|
|
@runtime_checkable
|
|
class ToolPolicyGateway(Protocol):
|
|
"""Hỏi trước khi chạy tool. Cài đặt thật: Team Hoa (R07, hạn 29/08)."""
|
|
|
|
def check(self, request: ToolCallRequest) -> PolicyDecision:
|
|
"""Được chạy tool này không.
|
|
|
|
KHÔNG được tự bật hộp thoại bên trong — cổng chỉ *trả lời*, còn hỏi ai
|
|
và hỏi thế nào là việc của tầng giao diện. Có vậy thì Co4E chạy nền mới
|
|
dùng chung cổng được với Cowork chạy tương tác.
|
|
"""
|
|
...
|