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>
47 lines
2.1 KiB
Python
47 lines
2.1 KiB
Python
"""Composition Root — R08-T10.
|
|
|
|
Một chỗ duy nhất quyết định app chạy bằng những mảnh nào. Trước đây quyết định
|
|
đó nằm rải trong ``app.py::run``, lẫn với việc dựng cửa sổ; tách ra để đổi một
|
|
mảnh (ví dụ thay kho bí mật) không phải đụng vào mã giao diện.
|
|
|
|
Đây cũng là chỗ hoàn tất R02: từ đây app chạy bằng :class:`JsonConfigRepository`
|
|
chứ không còn ``config.py::AppConfig``. Hai thứ đổi thật sự:
|
|
|
|
* ghi cấu hình qua ``AtomicJsonFile`` — mất điện giữa lúc lưu không làm hỏng file
|
|
* API key nằm trong kho bí mật của hệ điều hành, không nằm trong ``config.json``
|
|
|
|
Máy không có kho bí mật (Linux headless, CI, hoặc keyring hỏng) vẫn chạy bình
|
|
thường: repository nhận ``secrets=None`` và đọc khoá thẳng từ file như cũ. 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.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from ...infrastructure.config.json_config_repository import JsonConfigRepository
|
|
from ...infrastructure.secrets.keyring_adapter import KeyringAdapter
|
|
from ...state import AppContext
|
|
|
|
|
|
def build_secret_store():
|
|
"""Kho bí mật của hệ điều hành, hoặc None nếu máy này không có.
|
|
|
|
``KeyringAdapter`` không bao giờ ném lỗi — nó tự báo ``available``. Trả về
|
|
None thay vì một adapter chết để chỗ gọi khỏi phải đoán.
|
|
"""
|
|
store = KeyringAdapter()
|
|
return store if store.available else None
|
|
|
|
|
|
def build_config(path: Path | None = None) -> JsonConfigRepository:
|
|
"""Dựng repository cấu hình đã nối sẵn kho bí mật của hệ điều hành.
|
|
|
|
Đây là Composition Root: chỗ DUY NHẤT quyết định app chạy bằng mảnh nào.
|
|
"""
|
|
return JsonConfigRepository.load(path, secrets=build_secret_store())
|
|
|
|
|
|
def build_context(path: Path | None = None) -> AppContext:
|
|
"""Dựng AppContext hoàn chỉnh — điểm vào cho ``app.run()`` và cho checker."""
|
|
return AppContext(build_config(path))
|