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>