docs(refactor): add the Team Hoa completion report for R05/R06
Mirrors docs/refactor/BaoCao_TeamDuy_R01_R03_R04.md's structure: per-EPIC results, test evidence, the two real bugs found and fixed, secondary improvements, open items needing another team's sign-off, untested scope, and what's next. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
# BÁO CÁO KẾT QUẢ — TEAM HOA: EPIC R05, R06
|
||||
|
||||
* **Dự án**: Cowork Local (Cowork-Local BamBOO)
|
||||
* **Team**: 🟢 Team Hoa — Workspace, Filesystem, Scheduling & Tool Registry
|
||||
* **Nhánh**: `feature/teamhoa/r05-r06` (tạo từ `origin/feature/deltateam/refactor-plan`, chưa push lên remote — xem mục 7)
|
||||
* **Thời gian thực hiện**: 21/08/2026, 21:40 ➔ 22:57
|
||||
* **Ngày báo cáo**: 22/08/2026
|
||||
* **Tài liệu gốc**: `Feature_Architecture_Proposal.md`, `Refactoring_Checklist.md`, `plan.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. Tóm tắt điều hành
|
||||
|
||||
Hoàn tất **10/10 task** của 2 EPIC được giao: **R05** (Tool, MCP & Connector Policy) và **R06** (Workspace, Filesystem & History Isolation). Đã commit 2 commit trên branch cục bộ; **chưa push lên Gitea** — remote từ chối với lỗi quyền ghi (xem mục 7 #1).
|
||||
|
||||
| Chỉ số | Kết quả |
|
||||
| :--- | :--- |
|
||||
| Task hoàn thành | **10/10** (R05: 5, R06: 5) |
|
||||
| Commit | 2 (`ae4fe72`, `cf542b7`) |
|
||||
| File thay đổi | 41 (27 file mới, 14 file sửa — 1 file (`docs/refactor/Refactoring_Checklist.md`) sửa ở cả 2 commit) |
|
||||
| Dòng code | +3.054 / −459 |
|
||||
| Test | **283 pass** / 12,5s (283/287 — 4 fail có sẵn từ trước, không do R05/R06) |
|
||||
| Test suite nhanh (unit + contract + characterization + routing) | **256 pass / 4,5s** |
|
||||
| CASAN Check 3 (`scripts/check_imports.py`) | **PASS** — 0 Qt import trong `domain/`, `application/` |
|
||||
| File production > 400 dòng (file mới) | **0** — lớn nhất `domain/tools/tool_registry.py` 125 dòng |
|
||||
|
||||
**2 lỗi thật được phát hiện và sửa trong quá trình làm** (chi tiết mục 5): một lỗ hổng bảo mật (MCP/connector tool không qua permission gate) và một race condition (turn chạy ngầm lưu nhầm lịch sử vào project khác).
|
||||
|
||||
---
|
||||
|
||||
## 2. Kết quả theo từng EPIC
|
||||
|
||||
### 🔹 EPIC R05 — Tool, MCP & Connector Policy (5/5)
|
||||
|
||||
| Task | Sản phẩm | Ghi chú |
|
||||
| :--- | :--- | :--- |
|
||||
| R05-T01 | `domain/tools/tool_descriptor.py`, `tool_registry.py` | `ToolCapability` (Flag: READ/WRITE/EXECUTE/NETWORK, kết hợp được) + `ToolDescriptor` + `ToolRegistry` |
|
||||
| R05-T02 | `infrastructure/filesystem/{file_tools,command_tools,fetch_tools,tool_context}.py` | Tách if/elif dispatcher của `core/tools.py`; `core/tools.py` còn 291 dòng (từ 566), là shim strangler-fig |
|
||||
| R05-T03 | `application/conversations/tool_policy_gateway.py` | `ToolPolicyGateway.allow(name, gate, payload)` — thay 2 chỗ check hardcode riêng biệt (`chat_agent.py`, `code_agent.py`) bằng 1 lookup capability |
|
||||
| R05-T04 | Sửa `core/chat_agent.py`, `core/mcp_client.py` | **Thay đổi hành vi có chủ đích** — xem mục 5, Lỗi 1 |
|
||||
| R05-T05 | `infrastructure/mcp/mcp_source_manager.py` | Tách lifecycle connection MCP khỏi `state.py::AppContext` |
|
||||
|
||||
**Vấn đề gốc đã giải quyết** — cùng một việc "tool này có cần xác nhận trước khi chạy không" tồn tại **3 cách trả lời khác nhau**:
|
||||
|
||||
```
|
||||
core/chat_agent.py::run_cowork name in ("run_command", "install_package")
|
||||
core/code_agent.py::run_code name in (WRITE_TOOLS | MS365_WRITE_TOOLS)
|
||||
core/mcp_client.py / ext_connectors.py (không hỏi gì cả)
|
||||
```
|
||||
|
||||
Cách thứ 3 là một lỗ hổng thật, không phải khác biệt thiết kế — xem mục 5.
|
||||
|
||||
### 🔹 EPIC R06 — Workspace, Filesystem & History Isolation (5/5)
|
||||
|
||||
| Task | Sản phẩm | Ghi chú |
|
||||
| :--- | :--- | :--- |
|
||||
| R06-T01 | `domain/workspaces/workspace_session.py` | `WorkspaceSession` — snapshot bất biến (project_id/workspace_root/sandbox_dir/allowed_paths) + `is_allowed(path)`, cùng khuôn với `ConversationExecutionRequest` (R04-T01) |
|
||||
| R06-T02 | `infrastructure/persistence/json/{atomic_write,workspace_repository_impl,conversation_repository_impl}.py` | **Sửa bug thật** — xem mục 5, Lỗi 2 |
|
||||
| R06-T03 | `infrastructure/filesystem/execution_workspace.py` | Đặt tên cho quy ước `.scratch` đã có, không đổi vị trí file |
|
||||
| R06-T04 | Sửa `ui/chat_panel.py` | **Sửa race condition thật** — xem mục 5, Lỗi 3 |
|
||||
| R06-T05 | `application/workspaces/file_workspace_service.py` | File Explorer/AI Editor gọi `core/tools.py::execute_tool` giống agent, không viết lại logic |
|
||||
|
||||
---
|
||||
|
||||
## 3. Kiến trúc sau refactor
|
||||
|
||||
```text
|
||||
presentation/ (chưa đổi ở đợt này — ui/chat_panel.py chỉ thêm 1 field "home_history_dir")
|
||||
│
|
||||
▼
|
||||
application/ conversations/tool_policy_gateway.py ← ALLOW/CONFIRM cho mọi tool call
|
||||
workspaces/file_workspace_service.py ← file ops cho File Explorer/AI Editor
|
||||
│ (100% pure Python — check_imports.py chặn import Qt)
|
||||
▼
|
||||
domain/ tools/{tool_descriptor,tool_registry}.py ← capability + catalogue
|
||||
workspaces/workspace_session.py ← snapshot workspace bất biến
|
||||
▲
|
||||
infrastructure/ filesystem/{file_tools,command_tools,fetch_tools,tool_context,execution_workspace}.py
|
||||
mcp/mcp_source_manager.py ← lifecycle connection MCP
|
||||
persistence/json/{atomic_write,*_repository_impl}.py
|
||||
```
|
||||
|
||||
**Nguyên tắc di trú (ADR-001 mục 4, tiếp nối cách Team Duy làm ở R04)**: **không viết lại engine**. `core/tools.py::execute_tool`, `core/chat_agent.py::run_cowork`, `core/code_agent.py::run_code` vẫn là engine bên dưới — tầng mới chỉ sở hữu phần phân loại rủi ro (R05) và phần định danh workspace (R06) mà trước đây nằm rải rác/hardcode. `pytest` xanh liên tục giữa các bước.
|
||||
|
||||
---
|
||||
|
||||
## 4. Bằng chứng kiểm thử
|
||||
|
||||
### Phân bố test (bao gồm test mới của Team Hoa)
|
||||
|
||||
| Suite | Số test | Ghi chú |
|
||||
| :--- | ---: | :--- |
|
||||
| `tests/unit/` | 137 | +41 test mới (R05: 26, R06: 15 — không tính `test_history_dir_race.py`, ở `integration/`) |
|
||||
| `tests/contracts/` | 29 | có sẵn từ R03, không đổi |
|
||||
| `tests/characterization/` | 13 | có sẵn từ R01, vẫn xanh — xác nhận `run_cowork` không hồi quy sau khi sửa gate |
|
||||
| `tests/routing/` | 79 | có sẵn từ trước, không đụng |
|
||||
| **Cộng 4 suite nhanh** | **256** (4 fail routing-env, không do R05/R06) | 4,5s |
|
||||
| `tests/integration/` | 27 | +2 test mới: `test_history_dir_race.py` — Qt offscreen thật, không phải test double |
|
||||
| **Tổng** | **287** (283 pass) | 12,5s |
|
||||
|
||||
### Đối chiếu Definition of Done (theo `DeltaTeam_prompt.md` / mẫu Team Duy)
|
||||
|
||||
| # | Tiêu chí | Kết quả |
|
||||
| :--- | :--- | :--- |
|
||||
| 1 | Mọi file mới < 400 dòng | ✅ Lớn nhất: `domain/tools/tool_registry.py` 125 dòng |
|
||||
| 2 | 0 import Qt trong `domain/`, `application/` | ✅ `check_imports.py` PASS |
|
||||
| 3 | Comment tiếng Anh giải thích lý do ở mọi khối sửa/mới | ✅ |
|
||||
| 4 | Có unit/contract/integration test, verify bằng chạy thật | ✅ 41 test mới + 2 test Qt offscreen thật cho race condition |
|
||||
| 5 | Không hồi quy | ✅ 283/287 pass — 4 fail là lỗi có sẵn từ trước R05/R06 (2 EPIC R02, 2 do môi trường máy có Ollama thật) |
|
||||
| 6 | Ghi Start/End vào Checklist | ✅ 10 task đã tick kèm mốc thời gian |
|
||||
| 7 | Cổng CASAN (`run_quality_gate.py`, R10-T02) | ⚠️ Chưa viết (thuộc R10, chưa tới lượt) — Check 3 đã PASS |
|
||||
|
||||
---
|
||||
|
||||
## 5. Hai lỗi thật phát hiện và sửa trong quá trình làm
|
||||
|
||||
### 🔴 Lỗi 1 (R05-T04) — Tool MCP/Connector chạy hoàn toàn không qua permission gate
|
||||
|
||||
`core/chat_agent.py::run_cowork` có 2 nhánh dispatch tool call: nhánh built-in (`read_file`, `run_command`, ...) đi qua gate xác nhận khi Settings bật "confirm before running commands"; nhánh `extra_tools` (mọi tool từ MCP server hoặc Connector — `core/mcp_client.py`, `core/ext_connectors.py`) gọi thẳng:
|
||||
|
||||
```python
|
||||
if name in extra_names and extra_executor is not None:
|
||||
...
|
||||
result = extra_executor(name, args) # KHÔNG có bước xác nhận nào
|
||||
```
|
||||
|
||||
Nghĩa là một MCP server (kể cả server tự cấu hình, hoặc MS365 write-tool như `send_mail`) chạy **auto-run tuyệt đối**, bất kể người dùng đã bật "confirm before running commands" trong Settings hay chưa. Đây không phải khác biệt thiết kế có chủ đích — không có ghi chú, không có toggle riêng cho việc này.
|
||||
|
||||
*Sửa*: mọi `extra_tools` được gắn `ToolCapability` mặc định bảo toàn (`WRITE|EXECUTE|NETWORK` — vì MCP không có chuẩn khai báo rủi ro), đăng ký vào registry của turn, và đi qua CÙNG `ToolPolicyGateway` với built-in tools.
|
||||
|
||||
**Đây là thay đổi hành vi người dùng sẽ thấy**: khi "confirm before running commands" đang bật, tool MCP/connector từ giờ sẽ hỏi xác nhận — giống `run_command`. Verify bằng test `tests/unit/test_cowork_extra_tool_policy.py` (3 test: rejected trước khi executor chạy, approved thì chạy, `gate=None` vẫn auto-run như cũ).
|
||||
|
||||
### 🟠 Lỗi 2 (R06-T04) — Turn chạy ngầm lưu nhầm lịch sử vào project khác
|
||||
|
||||
`ui/chat_panel.py::_persist_session` (lưu hội thoại của một turn **chạy ngầm**, không phải conversation đang xem) gọi:
|
||||
|
||||
```python
|
||||
save_conversation(self.ctx.config.history_dir(), ...)
|
||||
```
|
||||
|
||||
`history_dir()` đọc `config._project_history_dir` — một field **dùng chung** trên `AppContext.config`, được `ui/workspace_tab.py::_load_current` ghi đè mỗi lần người dùng đổi project trong màn Workspace. Nếu một turn ở project A còn đang chạy (ví dụ Scheduled Task, hoặc user gõ câu hỏi rồi chuyển sang xem project B ngay) và người dùng đổi sang project B **trước khi** turn đó lưu xong, hội thoại của project A bị ghi nhầm vào thư mục lịch sử của project B.
|
||||
|
||||
*Sửa*: thêm `"home_history_dir"` vào dict `ctx` mà mỗi turn đã có sẵn (cùng quy ước với `home_id`/`home_messages`/`home_title` — dict này được author code gốc thiết kế đúng cho mục đích này, chỉ thiếu 1 field), chụp giá trị **tại lúc submit** thay vì đọc sống lúc lưu.
|
||||
|
||||
*Kèm 1 phát hiện phụ*: `_save_snapshot` (dùng cho conversation ĐANG XEM) đã có logic đúng từ trước để không ghi đè `project_id` của một turn nền bằng project hiện tại — chỉ riêng **thư mục lưu** là bị bỏ sót, không phải toàn bộ cơ chế bị thiếu.
|
||||
|
||||
Verify bằng test Qt offscreen thật (không phải double): `tests/integration/test_history_dir_race.py` — dựng `ChatPanel` thật, giả lập đổi project giữa lúc turn chạy, xác nhận file được lưu đúng thư mục project A.
|
||||
|
||||
---
|
||||
|
||||
## 6. Cải thiện phụ (không nằm trong yêu cầu task)
|
||||
|
||||
| Cải thiện | Ảnh hưởng |
|
||||
| :--- | :--- |
|
||||
| `core/projects.py::save_project`, `core/history.py::save_conversation/rename_conversation/set_pinned` chuyển sang ghi atomic (`infrastructure/persistence/json/atomic_write.py`) | Trước đây `path.write_text(json.dumps(...))` không atomic — crash/kill giữa lúc ghi để lại file JSON hỏng, và `load_project`/`load_conversation` coi file hỏng như "không tồn tại" ➔ **mất project hoặc hội thoại âm thầm, không báo lỗi**. Có test giả lập crash giữa lúc ghi xác nhận file cũ không bị hỏng (`tests/unit/test_atomic_write_and_repositories.py`) |
|
||||
| `McpServerConnection.is_alive()` (mới, `core/mcp_client.py`) | Nhỏ, cộng thêm — cho `McpToolSourceManager` biết một connection cached đã chết (subprocess crash) để khởi động lại, thay vì cache giữ một connection chết vô thời hạn |
|
||||
|
||||
---
|
||||
|
||||
## 7. Còn nợ & cần quyết định
|
||||
|
||||
| # | Nội dung | Người quyết |
|
||||
| :--- | :--- | :--- |
|
||||
| 1 | **Branch chưa lên được Gitea** — `git push` bị từ chối: `User permission denied for writing` (pre-receive hook). Cần cấp quyền push cho tài khoản git đang dùng trên máy này, hoặc push bằng tài khoản khác có quyền. | Admin Gitea |
|
||||
| 2 | **Xung đột file với EPIC R02 (Team Nam)**: R02-T01 giao `infrastructure/persistence/json/atomic_json_file.py`. R06-T02 cần atomic write ngay nên tạo `atomic_write.py` (tên khác, cùng thư mục) — không đụng file của Team Nam, nhưng 2 module cùng mục đích sẽ tồn tại song song cho tới khi hợp nhất. | Team Nam (khi bắt đầu R02-T01) |
|
||||
| 3 | **`WorkspaceRepository`/`ConversationRepository`/`FileWorkspaceService` chưa có call site thật** — giống tình trạng `ProviderRegistry` của Team Duy ở R03 (mục 7 #1 trong báo cáo Team Duy). Mọi nơi trong production vẫn gọi trực tiếp `core/projects.py`/`core/history.py`/`core/tools.py::execute_tool`. | Team Hoa (nối dây ở EPIC sau) |
|
||||
| 4 | **R06-T04 không sửa đúng y nguyên `ui/workspace_tab.py::_load_current` như mô tả gốc trong `plan.md`** — bug thật nằm ở điểm ĐỌC (`ui/chat_panel.py::_persist_session`), không phải điểm GHI (`_load_current` chỉ set field, tự nó không đọc lại). Đã sửa đúng điểm đọc, có test thật xác nhận. Việc đổi `_load_current` sang "đồng bộ bằng session id" như plan gốc gợi ý cần tách sâu hơn `WorkspaceTab`/`ChatPanel`, thuộc phạm vi R08 (UI/Application Separation). | Team Duy (R08) |
|
||||
| 5 | **2 test đỏ có sẵn từ trước, không do R05/R06**: `tests/test_config_security.py` × 2 (EPIC R02/Team Nam, đã ghi nhận từ báo cáo Team Duy) và `tests/unit/test_routing_wiring.py` × 2 (môi trường máy này có Ollama/llama3.1 thật + config routing cục bộ khác giả định "fresh install" của test — nghi là do máy chạy test có cấu hình routing/Ollama khác máy Team Duy dùng, cần Team Duy xác nhận lại trên máy sạch). | Team Nam (#1), Team Duy (#2) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Phạm vi chưa kiểm thử
|
||||
|
||||
Nêu rõ để tránh hiểu nhầm mức độ bảo đảm:
|
||||
|
||||
* **R05-T04 (gate cho MCP/connector) chưa test với MCP server thật** — toàn bộ test dùng `ToolSpec` giả (`_EXTRA_SPEC` trong `test_cowork_extra_tool_policy.py`), chưa có tình huống thật với `core/mcp_client.py::McpServerConnection` chạy subprocess thật.
|
||||
* **`McpToolSourceManager` (R05-T05) chưa test với subprocess MCP thật** — test dùng `_FakeConnection`, không spawn tiến trình. Đã smoke-test `AppContext.build_mcp_tools()` thật (không có server nào cấu hình → chỉ trả về ms365 local tools) nhưng chưa thử ensure/restart trên một server thật.
|
||||
* **`ui/folder_tab.py`, `ui/file_edit_dialog.py` chưa được nối vào `FileWorkspaceService` (R06-T05)** — dịch vụ tồn tại và có test unit đầy đủ, nhưng chưa xác nhận bằng cách chạy UI thật (đã mở app kiểm tra sau R05, nhưng không lặp lại cho R06's file explorer flow cụ thể).
|
||||
* **Đã mở app thật 1 lần sau khi sửa `ui/chat_panel.py` (R06-T04)** để xác nhận không crash lúc khởi động — chưa thử tay thao tác "đổi project giữa lúc chat đang trả lời" trên UI thật (chỉ verify bằng test offscreen).
|
||||
|
||||
---
|
||||
|
||||
## 9. Việc kế tiếp của Team Hoa
|
||||
|
||||
| EPIC | Nội dung | Điều kiện |
|
||||
| :--- | :--- | :--- |
|
||||
| **R07** (Scheduling & Workflow Runtime) | Tách `TaskRepository`/`ScheduleCalculator` khỏi `QTimer` (`core/task_scheduler.py`), xây `TaskApplicationService` | Phối hợp 🟣 Team Nam (Co4E Workflows) |
|
||||
| **R08** (T01 ➔ ...) | Phần Team Hoa trong tách UI (`ui/workspace_tab.py`, `ui/folder_tab.py`, `ui/schedule_task_tab.py`, `ui/dashboard_tab.py`, Graph) | Chờ R07 |
|
||||
| Nối `WorkspaceRepository`/`ConversationRepository`/`FileWorkspaceService` vào call site thật | Xem mục 7 #3 | Có thể làm sớm hơn R07/R08 nếu được yêu cầu |
|
||||
|
||||
---
|
||||
|
||||
## 10. Lịch sử commit
|
||||
|
||||
| Commit | Nội dung |
|
||||
| :--- | :--- |
|
||||
| `ae4fe72` | feat(R05): tool capability registry, unified policy gateway, MCP lifecycle manager |
|
||||
| `cf542b7` | feat(R06): workspace session snapshot, atomic persistence, history-dir race fix |
|
||||
@@ -73,6 +73,9 @@
|
||||
> * `python scripts/check_imports.py` ➔ **PASS** (0 Qt import trong `domain/`, `application/`)
|
||||
> * Mọi file mới **< 400 dòng** (lớn nhất: `domain/tools/tool_registry.py` 125 dòng). `core/tools.py` giảm từ 566 ➔ 291 dòng.
|
||||
>
|
||||
> ### 📄 BÁO CÁO CHI TIẾT
|
||||
> Xem `docs/refactor/BaoCao_TeamHoa_R05_R06.md` — kết quả từng EPIC, bằng chứng kiểm thử, 2 lỗi thật đã phát hiện (permission gate bị bỏ qua cho MCP tools, race condition lưu nhầm lịch sử), và phạm vi **chưa** kiểm thử.
|
||||
>
|
||||
> ### 🔧 TÓM TẮT R06
|
||||
> * **R06-T01**: `domain/workspaces/workspace_session.py::WorkspaceSession` — snapshot bất biến (project_id, workspace_root, sandbox_dir, allowed_paths) + `is_allowed(path)`.
|
||||
> * **R06-T02**: `infrastructure/persistence/json/{workspace_repository_impl,conversation_repository_impl}.py` bọc `core/projects.py`/`core/history.py`. **Đã sửa bug thật**: `save_project`/`save_conversation`/`rename_conversation`/`set_pinned` trước đây `path.write_text()` không atomic (crash giữa lúc ghi = file JSON hỏng, `load_project`/`load_conversation` coi file hỏng như "không tồn tại" — mất project/hội thoại âm thầm). Giờ cả 4 hàm ghi qua `infrastructure/persistence/json/atomic_write.py::write_json` (temp file + `os.replace`). Có test giả lập crash giữa lúc ghi xác nhận file cũ không bị hỏng.
|
||||
|
||||
Reference in New Issue
Block a user