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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a7e369e46c
commit
ab0d26761f
@@ -22,6 +22,7 @@ from typing import Any, Dict
|
||||
|
||||
from ..persistence.json.atomic_json_file import AtomicJsonFile
|
||||
from ..secrets.secret_store import SecretStore, provider_key
|
||||
from .schema_migration import CURRENT_VERSION, migrate
|
||||
|
||||
|
||||
class JsonConfigRepository:
|
||||
@@ -51,7 +52,14 @@ class JsonConfigRepository:
|
||||
merged = copy.deepcopy(self._defaults)
|
||||
stored = self._file.read(default=None)
|
||||
if isinstance(stored, dict):
|
||||
# Nâng cấp TRƯỚC khi trộn với mặc định: bước v1→v2 gỡ api_key khỏi
|
||||
# đĩa, mà mặc định thì không có khoá nào để gỡ.
|
||||
stored, changed = migrate(stored, secrets=self._secrets,
|
||||
path=self._file.path)
|
||||
merged = _deep_merge(merged, stored)
|
||||
if changed:
|
||||
self.data = merged
|
||||
self.save() # ghi ngay, để lần sau khỏi chuyển lại
|
||||
merged = self._env_overrides(merged)
|
||||
# Trạng thái mở khoá ms365 chỉ tồn tại lúc chạy — mỗi lần mở app đều
|
||||
# bắt đầu ở trạng thái khoá, không tin giá trị đọc từ đĩa.
|
||||
@@ -170,6 +178,7 @@ class JsonConfigRepository:
|
||||
to_write = copy.deepcopy(self.data)
|
||||
to_write["ms365"]["unlocked"] = False
|
||||
to_write.pop("_project_history_dir", None)
|
||||
to_write["schema_version"] = CURRENT_VERSION
|
||||
self._file.write(to_write)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user