CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.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.
|
|
"""
|
|
...
|