577b81a64113f26287e1c7206fa15d026dfd6264
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f8e22f5f5b | merge: merge origin/gamma/refactor and origin/feature/teamhoa/r05-r06 into feature/delta-team/epic-R04 | ||
|
|
bc282c71d0 |
refactor(config): AppConfig thành vỏ mỏng trên repository + vá 3 chỗ gán im lặng hỏng
config.py 623 -> 377 dòng (qua ngưỡng 400 của CASAN Check 2).
Class AppConfig 278 dòng giờ còn 30: mọi lối vào dẫn tới JsonConfigRepository.
Không xoá hẳn vì cái tên còn nằm ở 41 file — 23 checker trong tools/ và 18 file
test, trong đó có test của cả ba người. Sửa 41 chỗ trong một commit là đổi thứ
không cần đổi và làm review không đọc nổi. Giữ tên, đổi ruột.
Thêm JsonConfigRepository.from_data() cho dạng AppConfig(data=..., path=...) mà
13 file test đang dùng: dựng thẳng từ dict, không đọc đĩa, không chạy migration
trên dữ liệu test.
MỘT LỖI TÔI GÂY RA HÔM 25/08, HÔM NAY MỚI LỘ
---------------------------------------------
Lúc tráo R02 tôi có đối chiếu API và kết luận "đủ 34/34 thành viên, thay được".
Đối chiếu đó chỉ so TÊN, không so việc một property có setter hay không.
AppConfig cũ là dataclass nên `config.language = "vi"` chạy bình thường.
Repository để language là property chỉ đọc -> gán vào là AttributeError. Ba chỗ
trong app.py đang gán: đổi ngôn ngữ, đổi giao diện, đổi provider trên thanh bên.
Khó thấy vì cả ba nằm trong slot của Qt, mà Qt NUỐT ngoại lệ trong slot. Không
traceback, không thông báo — bấm đổi ngôn ngữ thì không có gì xảy ra. 709 test
đơn vị vẫn xanh suốt. Chỉ check_nav bắt được vì nó bấm thật vào combo rồi kiểm.
Thêm setter cho theme/language/active_provider, và tests/test_config_gan_duoc.py
đi ngược từ mã nguồn: quét cả repo tìm mọi chỗ `config.X = ...` rồi thử gán
thật. Đã kiểm ngược — bỏ setter đi thì 2 bài đỏ.
BẮC CẦU CHO 55 CONTROL MONITORING
----------------------------------
check_controls_alive so với mốc git
|
||
|
|
72ed3b4147 | Merge remote-tracking branch 'origin/gamma/refactor' | ||
|
|
40b12ecb15 |
refactor(monitoring): N2 - tach monitoring_tab.py, CanonicalAuditLogger, MonitoringQueryService, go circular import, sandbox matrix
- ui/monitoring_tab.py (1546 dong) tach thanh presentation/monitoring/** (container + 7 tab/card + shared helper), ui/monitoring_tab.py con lai re-export shim de app.py khong doi. - infrastructure/telemetry/audit_logger.py: CanonicalAuditLogger, core/audit_log.py thanh wrapper mong, tuong thich nguoc 100% voi schema .jsonl cu. - application/monitoring/monitoring_query_service.py: MonitoringQueryService read-only, filter/sort/pagination, khong import PySide6. - Go circular import model_pricing<->usage_tracker va agent_security<-> agent_security_alert (core/agent_security_types.py moi). - infrastructure/sandbox/sandbox_capabilities.py: SandboxCapabilityMatrix theo OS (Windows/Linux/macOS), chua dau noi vao core/sandbox_manager.py. - conftest.py: sua loi checkout khong ten cowork_local khien pytest import nham thu muc khac. - 77 test moi, 167/167 pass. QA da xac nhan UI/business logic khong doi (xem evidence/report/unified_report.html). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
2246d55286 |
feat(infra): R02 vào thật — app chạy bằng JsonConfigRepository, khoá rời khỏi config.json
Từ 21/08 tôi đã viết xong 7 file R02 với 46 test xanh, và báo là "xong R02". Báo sai: code mới nằm song song, KHÔNG một dòng nào ngoài infrastructure/ và tests/ gọi tới nó. App vẫn chạy nguyên trên config.py, 29 file dùng nó, và khoá API của người dùng vẫn nằm plaintext trong config.json suốt 4 ngày. Commit này mới là phần refactor thật. Bù 21 thành viên còn thiếu (85 dòng) ------------------------------------ JsonConfigRepository có 18/34 thành viên công khai của AppConfig nên không tráo được. Chép nguyên ngữ nghĩa 21 cái còn lại: load, ms365_*, ext_connectors, connect_external, routing_mode_for, seeded_*, mcp_servers, teams, history, structure, monitoring_visibility, model_label, ca_bundle... Giờ 40/34, không thiếu gì. Không phải thiết kế mới — chừng nào 29 file còn gọi qua ctx.config thì repository phải trả lời được đúng các câu hỏi cũ. ROUTING_MODES lấy theo bản Delta (4 chế độ, có "fallback" từ R03-T03) chứ không theo bản main cũ 3 chế độ. Chép bản cũ là routing "fallback" âm thầm rơi về "off" sau khi Delta merge, không lỗi nào báo. Composition Root (R08-T10, phần đầu) ------------------------------------- presentation/shell/bootstrap.py: một chỗ duy nhất quyết định app dựng bằng mảnh nào. app.py::run giờ gọi build_context() thay cho AppConfig.load(). Đây cũng là chỗ ráp kho bí mật vào; máy không có keyring thì secrets=None và mọi thứ chạy như cũ. Kiểm trên dữ liệu thật ---------------------- Chạy lên máy tôi, migration tự chạy đúng như thiết kế: openai_compat 39 ký tự config.json -> Windows Credential Manager ollama giá trị bù nhìn, để nguyên trong file, không đẩy vào kho schema_version 1 -> 2 sao lưu config.json.v20260825-193206.bak Sau khi bật lại app và để nó ghi cấu hình, config.json vẫn sạch: api_key rỗng, không còn chuỗi nào có hình dạng khoá. scripts/audit_security.py sạch. Tiêu chí nghiệm thu A của plan (dòng 244) — "0 lưu trữ plaintext API Key trong JSON" — tới commit này mới thật sự đạt. 632 test xanh. check_dialogs, check_nav, check_design_parity đều qua. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9d6a7be31b |
fix(infra): AtomicJsonFile — os.replace trên Windows thỉnh thoảng bị từ chối
Bắt được nhờ merge Delta: bộ test của họ chạy lâu hơn nên lộ ra một bài
của tôi chập chờn. Truy ra không phải lỗi test mà là lỗi thật trong code
chạy máy người dùng:
PermissionError: [WinError 5] Access is denied
.dem.json.l7x2a8pd.tmp -> dem.json
MoveFileEx trả ERROR_ACCESS_DENIED khi tiến trình khác đang giữ handle
lên nguồn hoặc đích — trên Windows gần như luôn là Defender hoặc Search
Indexer quét file vừa tạo, giữ vài chục mili-giây rồi nhả.
Đo được: hỏng 1 trong 7 lượt chạy 20 lần ghi, tức khoảng 1 trên 140 lần
lưu. Nghĩa là người dùng thỉnh thoảng bấm Lưu là văng lỗi, và không tài
nào tái hiện được để báo.
Thêm vòng thử lại 6 lượt, nghỉ tăng dần 20ms → 640ms. Hết lượt vẫn ném
lỗi, không nuốt lỗi quyền thật, và luôn dọn file tạm.
Hai bài test mới, đã kiểm ngược: bỏ vòng thử lại thì bài thứ nhất đỏ.
Chạy lại 30 lượt sau khi vá: 0 hỏng (trước khi vá: 4).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
dcf2e8f995 |
merge: kéo Delta epic-R04 (gồm cả R01 và R03) vào gamma/refactor
Nam chốt: không chờ Delta merge vào main, lấy sớm để va chạm nhỏ và sửa ngay, thay vì dồn một cục lúc cả hai cùng lên main. R04 chứa trọn R01 và R03 nên một lần merge là đủ cả ba: 96 file, +8260 dòng. Xung đột chỉ 5 file, đều là __init__.py add/add — hai team cùng dựng khung thư mục nên đụng docstring. Giữ docstring của Gamma (nói rõ ràng buộc "không import PySide6"), giữ mọi phần code của Delta. Riêng tests/fakes/__init__.py: bỏ hai dòng import háo hức của Delta (fake_provider, fake_tool_executor). fake_provider dùng `from providers.base import ...` — import tuyệt đối, chỉ chạy được khi cwd là gốc repo — nên nó làm đứt bài test "dùng fake mà không nạp config thật". Không ai import ở cấp package; test của Delta gọi thẳng module nên bỏ đi không ảnh hưởng họ. Đã ghi lý do vào docstring của gói. Delta cũng xoá preview-desktop và "requirements (cloud copy).txt". 430 test xanh sau merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d74c052af3 |
fix(infra): .gitignore nuốt infrastructure/secrets/ — nhánh đỏ với mọi máy trừ máy tôi
Dòng 31 ghi `secrets/`. Mẫu không neo, nên git bỏ qua MỌI thư mục tên
secrets ở mọi độ sâu — kể cả infrastructure/secrets/ vốn là mã nguồn.
Ba file ở đó chưa bao giờ lên repo. Máy tôi vẫn 150 test xanh vì pytest
đọc đĩa chứ không đọc git; ai clone sạch thì đỏ 4 file ngay lúc thu thập:
ModuleNotFoundError: No module named
'cowork_local.infrastructure.secrets'
Hiệp phát hiện, không phải tôi. Đã dựng lại bằng clone sạch vào thư mục
đặt đúng tên cowork_local để tái hiện.
Neo mẫu thành /secrets/ và thêm tests/test_no_ignored_source.py — hỏi
thẳng git chứ không hỏi đĩa, nên lần sau lỗi cùng hình dạng sẽ đỏ ngay
trên máy người viết. Đã kiểm ngược: trả lại `secrets/` thì cả ba bài đỏ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f61c5474b0 |
feat(R03): unify model routing and centralise the provider catalogue
EPIC R03 (Team Duy) — Model Providers & Routing. All six tasks done.
R03-T02 — Provider catalogue
domain/models/provider_descriptor.py ProviderDescriptor (frozen), WireProtocol, AuthKind
infrastructure/providers/provider_registry.py
thread-safe registry: id/alias lookup, dynamic
lookup by model id, adapter selection by protocol
providers/factory.py drops its own _REGISTRY table and delegates to the
registry, still raising ProviderError for callers
R03-T03 — RoutingApplicationService (pure Python, 4 modes)
application/model_routing/routing_models.py
RoutingMode (off/auto/manual/fallback),
RoutingRequest (immutable snapshot), RouteEvaluation,
RoutingOutcome
application/model_routing/routing_application_service.py
the single decision flow, reached through two narrow
ports plus a caller-supplied confirm callback, so no
Qt import is needed
application/model_routing/core_routing_adapter.py
binds the ports to core/routing and AppContext
Fallback is a new resilience mode: keep the selected model while it can serve the turn,
re-route only when it cannot. Wired end to end through config.py, state.py,
ui/routing_toggle.py and i18n.py (EN/JA/VI).
R03-T04 / T05 — Remove the duplicated routing flow
ui/chat_panel.py (#L638), ui/co4e_tab.py, ui/folder_tab.py each drop ~35 lines of copied
logic and call the shared service; the widgets now only build a RoutingRequest, host the
Manual-mode modal and render the outcome.
R03-T06 — Token usage as an event
infrastructure/telemetry/usage_sink.py UsageEvent + UsageEventSink protocol, with tracker,
in-memory and composite sinks
providers/openai_compat.py, providers/anthropic.py
publish a UsageEvent instead of writing to the
usage tracker themselves
core/usage_tracker.py adds current_context() so a sink can borrow and
restore a thread's attribution
R03-T01 — Contract tests
tests/contracts/test_providers.py parametrises over every provider in the registry: chat()
signature, canonical assistant message, normalised tool calls, response closed, tool schema
translation, ProviderError, list_models/test_connection, one UsageEvent per turn.
Test infrastructure fix (required to verify any of the above): tests/conftest.py used to put
the repository's PARENT directory on sys.path, so `import cowork_local.*` resolved against
whichever sibling folder happened to carry that name — on a dev machine, an unrelated older
checkout. The suite reported green while exercising different code. The conftest now binds
this checkout to the cowork_local name in sys.modules.
Verification
pytest tests/ 236 passed in ~1.8s (102 before this change)
scripts/check_imports.py PASS, 0 forbidden imports in domain/ and application/
new production files largest is 288 lines, all under the 400 LOC ceiling
new tests 134 (50 contract, 70 unit, 14 integration), all offline
scripts/run_quality_gate.py does not exist yet (R10-T02), so DoD item 7 was covered by
check_imports.py plus the full suite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
ab0d26761f |
feat(infra): xong R02 — Settings Facade, versioning, chuyển khoá sang keyring
R02-T03 Typed Settings Facade
Khắp nơi đang viết ctx.config.routing.get("switch_mode", "off"). Gõ sai một
chữ thì lặng lẽ nhận mặc định, không ai biết cho tới lúc tính năng "không
hiểu sao không chạy". ProviderSettings / RoutingSettings / SecuritySettings
làm sai tên là lỗi ngay, và kiểu ghi rõ nên đọc là biết confirm_timeout_sec
tính bằng giây.
Là KHUNG NHÌN lên dict sống, không phải dataclass sao chép — sửa qua đây là
sửa vào cấu hình, save() là xuống đĩa, khỏi sinh chuyện đồng bộ hai chiều.
Có raw() để ai thiếu thuộc tính thì dùng tạm, đừng vòng lại config.data.
Bắt cả trường hợp giá trị là null: file cũ hay để null, đọc ra None rồi đem
so sánh số là vỡ.
R02-T06 Schema versioning + phục hồi
config.json hôm nay không có số phiên bản, nên mọi thay đổi hình dạng phải
đoán — _migrate_connectors() đoán "có khoá office nghĩa là file cũ". Giờ:
thiếu schema_version thì coi là v1, mỗi bước là một hàm chạy tuần tự, sao
lưu trước khi nâng, và file mới hơn app thì dùng nguyên trạng chứ không đoán
ngược.
R02-T05 Chuyển API key sang kho bí mật
Là bước v1→v2. Người dùng cập nhật app, mở lên, khoá cũ tự vào keyring và
biến khỏi đĩa — có test cho đúng cảnh đó.
Hai chỗ cố tình không làm:
- Máy chưa có keyring: KHÔNG chuyển, giữ nguyên v1. Thà để khoá trong file
còn hơn xoá đi rồi người dùng mất khoá mà không hiểu vì sao.
- Giá trị "ollama" là bù nhìn (Ollama đòi có api_key nhưng bỏ qua nội
dung), đẩy vào keyring chỉ tổ rác.
Hai chuỗi test trông giống khoá thật bị CASAN Check 1 bắt — đánh dấu
"# casan: allow" kèm lý do, đúng lối thoát đã thiết kế cho cả đội.
150 test xanh (129 + 21 mới). CASAN Check 1 sạch. File mới đều dưới 200 dòng.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a7e369e46c |
feat(infra): JsonConfigRepository — R02-T02, hiện thực đường A đã chốt
Thay cho config.py::AppConfig. Hai khác biệt về hành vi, cả hai đều là thứ muốn có; mọi thứ còn lại giữ y nguyên vì đây là refactor. 1. Ghi qua AtomicJsonFile — mất điện giữa lúc lưu không còn làm hỏng cấu hình. Có test riêng ở tầng này chứ không chỉ dựa vào test của AtomicJsonFile. 2. Đường A (chốt 21/08): provider_conf() đọc khoá từ SecretStore rồi ghép vào dict trả về, còn set_api_key() ghi khoá vào kho và để chuỗi rỗng trên đĩa. Kết quả: 5 nơi đang đọc conf["api_key"] không sửa dòng nào — 3 trong đó thuộc providers/ của Team Duy — mà file JSON vẫn sạch để qua CASAN Check 1. Hai test riêng cho đúng hai vế đó. provider_conf() trả BẢN SAO. Nếu trả tham chiếu thì khoá vừa ghép vào sẽ lẫn ngược vào self.data rồi theo save() xuống đĩa — đúng thứ đường A phải tránh. Có test cho chuyện này. secrets=None thì lùi về hành vi cũ (khoá nằm trong file). Cần vậy để chuyển dần ở R02-T05 chứ không phải đổi một phát cả app, và để máy không có keyring vẫn chạy. Giữ nguyên có chủ đích: trộn sâu với mặc định, biến môi trường, và ms365.unlocked không bao giờ chạm đĩa — mỗi thứ một test. _deep_merge chép lại 6 dòng thay vì import từ config.py: file này phải sống được sau khi config.py biến mất. 129 test xanh (119 + 10 mới). CASAN Check 1 sạch. File mới: 188/102/86 dòng, đều dưới ngưỡng 400. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d6dd6a030e |
feat(infra): AtomicJsonFile + KeyringAdapter, và đổi tên platform/ vì nó che stdlib
Ngày 21/08 của làn N1 (Nam): R02-T01 và R02-T04.
--- Lỗi phải sửa trước khi làm được gì ---
Kế hoạch đặt tên một tầng là platform/. Tôi dựng đúng theo đó sáng nay, có
kiểm "platform stdlib không bị che" và báo là an toàn. Kiểm đó SAI: tôi chỉ
thử từ thư mục cha. Chạy từ gốc repo — đúng cách 26 script trong tools/ và
scripts/ được gọi — thì platform/ che khuất platform của thư viện chuẩn, và
import keyring chết ngay:
AttributeError: module 'platform' has no attribute 'system'
Nghĩa là R02-T04 không thể làm được chừng nào thư mục đó còn tên cũ. Đổi
platform/ -> adapters/. Đây là lệch khỏi plan.md và ảnh hưởng Team Hoa (họ sở
hữu platform/qt/qt_scheduler_clock.py) — đã ghi vào GammaTeam_decisions.md.
tests/test_no_stdlib_shadow.py chặn lỗi tái diễn, hai lớp: một bài so tên thư
mục gốc repo với sys.stdlib_module_names, một bài chạy tiến trình con với cwd
là gốc repo rồi import keyring thật. Dựng lại platform/ là cả hai đỏ.
--- R02-T01: AtomicJsonFile ---
config.py::save() đang gọi path.write_text(), tức là cắt file về 0 byte rồi
mới ghi. Chết giữa chừng là mất sạch cấu hình. Thay bằng: ghi file tạm cùng
thư mục -> flush + fsync -> os.replace (nguyên tử trên cả Windows và POSIX).
Test tiêm lỗi đúng như cột nghiệm thu của plan.md: cho os.replace ném lỗi
ngay bước cuối rồi khẳng định file cũ còn nguyên. Chỉ test "ghi rồi đọc lại"
thì write_text() cũ cũng qua — mà đó chính là thứ đang thay.
Phần đọc: file hỏng được dời thành .bad-<thời điểm> rồi trả mặc định. Giữ
đúng hành vi "hỏng cấu hình không chặn khởi động" của config.py, thêm phần
cứu được bản hỏng.
--- R02-T04: KeyringAdapter ---
Windows Credential Manager / macOS Keychain / Linux Secret Service. Không bao
giờ ném lỗi: máy không có kho (Linux headless, CI) thì available=False và trả
None, để tầng UI nói "chưa lưu được khoá" thay vì sập app. Test tiêm backend
giả, không đụng keyring thật của máy chạy test.
119 test xanh (102 + 17 mới). CASAN Check 1 sạch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
cf542b7416 |
feat(R06): workspace session snapshot, atomic persistence, history-dir race fix
EPIC R06 (Team Hoa) - workspace/filesystem isolation, no cross-project
mutable state.
R06-T01 domain/workspaces/workspace_session.py
WorkspaceSession - project_id/workspace_root/sandbox_dir/allowed_paths
frozen snapshot + is_allowed(path), same "capture once at submit time"
shape as R04's ConversationExecutionRequest.
R06-T02 infrastructure/persistence/json/{atomic_write,workspace_repository_impl,conversation_repository_impl}.py
Real bug fixed: core/projects.py::save_project and core/history.py's
save_conversation/rename_conversation/set_pinned did a plain
path.write_text(json.dumps(...)) - two syscalls, no atomicity. A crash
between them leaves a half-written file that load_project/load_conversation
then silently treat as "missing". All four now write through
atomic_write.write_json (temp file + os.replace). WorkspaceRepository/
ConversationRepository are thin object-shaped facades over the same
(now-atomic) functions, for future application-layer callers.
NOTE: atomic_write.py is deliberately NOT named atomic_json_file.py -
R02-T01 (Team Nam) claims that filename for the same purpose app-wide;
see the checklist for the consolidation TODO.
R06-T03 infrastructure/filesystem/execution_workspace.py
ExecutionWorkspace names the output_dir/scratch_dir split that already
exists (core/chat_agent.py's flat workspace_root/.scratch) - does not
move anything.
R06-T04 ui/chat_panel.py
The actual race: ChatPanel._persist_session (saves a BACKGROUND turn's
conversation) resolved its save directory via a live
self.ctx.config.history_dir() read at save time. ui/workspace_tab.py::
_load_current mutates that same config field on every project switch, so
a turn still running when the user switched projects got saved into the
NEW project's history folder. Fixed by adding "home_history_dir" to the
per-turn ctx dict (same "home_*" snapshot convention already used for
session id/messages/title), captured at submit time. Verified with a real
offscreen-Qt test, not just a unit double:
tests/integration/test_history_dir_race.py.
R06-T05 application/workspaces/file_workspace_service.py
FileWorkspaceService - the File Explorer / AI Editor entry point for the
same safe read/write/edit operations the agent tool loop has, by calling
core/tools.py::execute_tool directly (same dispatch, same ToolContext
containment, same audit log) rather than reimplementing any of it.
New tests: tests/unit/test_workspace_session.py,
test_atomic_write_and_repositories.py, test_execution_workspace.py,
test_file_workspace_service.py, tests/integration/test_history_dir_race.py
(29 new tests, incl. 2 real offscreen-Qt integration tests).
Suite: 283 passed, 4 pre-existing failures unrelated to R05/R06 (see
checklist). check_imports: PASS. All new files < 400 LOC.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
ae4fe72b2e |
feat(R05): tool capability registry, unified policy gateway, MCP lifecycle manager
EPIC R05 (Team Hoa) - one security/approval path for every tool call.
R05-T01 domain/tools/{tool_descriptor,tool_registry}.py
ToolCapability (READ/WRITE/EXECUTE/NETWORK, composable) + ToolDescriptor +
ToolRegistry, replacing three independently-maintained gating lists
(core/tools.py::WRITE_TOOLS, code_agent.py's WRITE_TOOLS|MS365_WRITE_TOOLS,
chat_agent.py's literal ("run_command","install_package") tuple) with one
capability lookup.
R05-T02 infrastructure/filesystem/{file_tools,command_tools,fetch_tools,tool_context}.py
core/tools.py's execute_tool if/elif chain split into per-concern modules.
core/tools.py is now a strangler-fig shim: re-exports ToolContext/ToolError,
dispatches through a {name: handler} dict built from the split modules.
core/tools.py: 566 -> 291 lines.
R05-T03 application/conversations/tool_policy_gateway.py
ToolPolicyGateway.allow(name, gate, payload) - capability-driven ALLOW vs
ask-the-gate decision. Wired into both chat_agent.py::run_cowork and
code_agent.py::run_code, replacing their separate hand-rolled checks.
Verified equivalent to the old hardcoded sets by test.
R05-T04 (behavior change, not just refactor)
MCP/connector tools (core/mcp_client.py, core/ext_connectors.py) reached
chat_agent.py via extra_executor(name, args) with NO permission check at
all. They are now tagged with a conservative default capability
(WRITE|EXECUTE|NETWORK - no MCP tool self-declares risk) and routed through
the SAME ToolPolicyGateway as built-ins. When "confirm before running
commands" is on, MCP/connector calls now prompt like run_command already
did - a real gap closed, and a user-visible change worth calling out.
R05-T05 infrastructure/mcp/mcp_source_manager.py
McpToolSourceManager extracts the connection cache/lock/start-or-skip
lifecycle out of state.py::AppContext (_mcp_connections/_conn_lock) into a
standalone, directly-testable class. AppContext.build_mcp_tools and
_ms365_builtin_connection now call ensure()/stop(); _ext_connections
(unified Connectors) is out of scope for this task and keeps its own lock.
New tests: tests/unit/test_tool_registry_and_policy.py,
test_code_agent_tool_policy.py, test_cowork_extra_tool_policy.py,
test_mcp_source_manager.py (26 new tests).
Suite: 254 passed, 4 pre-existing failures unrelated to R05 (2 EPIC R02
config-security, 2 environment-dependent routing tests - see checklist).
check_imports: PASS. All new files < 400 LOC.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
8a9ee5f875 |
chore(refactor): mục chung của Team Gamma — khung, hợp đồng, cổng CASAN
Sáu việc trong "mục chung" của bản phân công, làm trước khi ba nhánh tính năng tách ra. 1. Khung 5 tầng theo đúng đường dẫn plan.md: domain/ application/ infrastructure/ presentation/ platform/ + tests/fakes/ — 38 __init__.py. Trước đó là 0 file, mà mọi task của cả ba người đều ghi vào đây. Đã kiểm platform/ không che khuất module platform của stdlib. 2. Hợp đồng SecretStore và ConfigRepository (Protocol, chưa cài đặt) + fake chạy trong bộ nhớ. Danh sách thuộc tính không bịa: đếm 156 lời gọi ctx.config.* trong 29 file rồi lấy những cái dùng thật, xếp theo số lần. Cố ý bỏ config.data (36 lời gọi, nhiều nhất) — bê dict thô sang kiến trúc mới là bê nguyên vấn đề cũ. 3. tests/test_contracts.py — bài nghiệm thu, không phải test cho vui. Bài chính chạy tiến trình riêng và khẳng định dùng fake KHÔNG kéo theo cowork_local.config lẫn PySide6; đó là điều kiện để N2 và N3 code ngay hôm nay thay vì đợi bản thật ngày 23 và 26/08. 4. scripts/audit_security.py — CASAN Check 1, Gamma chủ trì (hạn 30/08). Viết sớm để kiểm liên tục trong lúc chuyển API key, không đợi tới ngày cổng. Lần chạy đầu ra 3 báo động giả (secret_in_output là tên quy tắc, api_key="x" là dữ liệu test) nên đã siết: ngưỡng độ dài, hằng liệt kê, hình dạng khoá i18n, và dấu "# casan: allow" làm lối thoát chuẩn. --self-test cắm 4 credential thật + 5 mẫu vô hại để chứng minh nó còn cắn được — một máy quét không tìm thấy gì chỉ có giá trị nếu chứng minh được nó biết tìm. 5. Ba check CASAN vào CI, chạy mọi PR thay vì dồn tới 30/08. Check 2 và 3 thuộc Team Hoa và Team Duy, chưa có script — bước CI bỏ qua nếu file chưa tồn tại, để thêm cổng không làm đỏ CI của hai team kia. 6. docs/refactor/GammaTeam_decisions.md — hai quyết định chờ nhóm trưởng chốt: provider_conf() còn trả api_key hay không (ảnh hưởng 5 nơi, 3 nằm ngoài team), và số phận 24 checker UI sẽ vỡ khi file bị dời. 96 test xanh (90 cũ + 6 mới). CASAN Check 1: 0 credential lộ. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
10739f19aa | breakdown folder tree for epic R01 | ||
|
|
96bec976e7 |
feat(R03): unify provider catalogue, routing decisions and usage telemetry
EPIC R03 (Team Duy) - one provider catalogue, one routing flow, one usage seam.
R03-T01 tests/contracts/test_providers.py
29 contract tests every provider must satisfy: canonical assistant message,
streamed text == returned content, reasoning never joins the answer, parsed
tool arguments, ProviderError for every failure. Real adapters exercised
offline by stubbing Provider._request.
R03-T02 domain/models/provider_descriptor.py
infrastructure/providers/provider_registry.py
Provider facts declared once (was split across providers/factory.py,
DEFAULT_CONFIG and PROVIDER_LABELS). ProviderRegistry.build() also stamps the
descriptor id onto the instance, so ollama/github_copilot/codex usage is no
longer all attributed to "openai_compat", and never mutates the caller config.
R03-T03 application/model_routing/routing_application_service.py
Pure-Python routing policy with four modes: Off, Auto, Manual and the new
Fallback (switch only AFTER the current model fails). Depends on a RoutingPort
protocol; production wires the existing core.routing engine underneath.
R03-T04/T05 ui/chat_panel.py, ui/co4e_tab.py, ui/folder_tab.py
Three near-identical routing copies (~40 lines each) replaced by a call to
ctx.routing_application() plus a confirm callback. Mode vocabulary now lives
in one place (normalize_mode/is_valid_mode) instead of four literal tuples.
R03-T06 infrastructure/telemetry/usage_sink.py
Token usage extracted from both providers into UsageEvent + UsageEventSink.
Estimation pinned against core.usage_tracker so no recorded number changes.
Also fixes a deadlock introduced while wiring AppContext: routing_application()
held _routing_lock and called routing(), which takes the same non-reentrant lock.
Suite: 186 passed, 1.22s. check_imports: PASS. All new files < 400 LOC.
2 pre-existing failures remain in test_config_security.py (EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|