Files
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## 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>
2026-08-31 05:15:13 +00:00

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))