feat(config): config 디렉터리 기반 설정 관리 (--config-path/--env-file, trex-orb-v1 정합, BAT-026)
REQ-048/010 change-req. `--config` 를 이름으로 해석 + `--config-path <dir>` (기본 ./config) + resolve_config_toml(슬래시 포함 값은 하위호환 명시 경로). `.env` 를 config-dir 결정적 해석(--env-file 우선, 없으면 <config_dir>/.env, CWD 무관) + config/.env.example + .gitignore config/* 허용목록. 운영자 CLI 도 --config-path/--env-file 동일 적용. from_env() legacy 루트 .env 폴백 유지로 완전 하위호환. 단위 23 + 전체 375 passed, ruff clean, 라이브 스모크(name·path 동일). REQ-048/010 → Implemented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+7
-4
@@ -4,8 +4,11 @@ __pycache__/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
/.env
|
/.env
|
||||||
|
**/.env
|
||||||
/.vscode/
|
/.vscode/
|
||||||
# BAT-013 / REQ-048: per-instance TOML configs hold instance_key + portfolio_hub_token
|
# REQ-048/010 CHG-20260609-01 (trex-orb-v1 aligned): config/ holds real per-instance
|
||||||
# (secrets) — never commit. The tracked example below is exempt.
|
# TOML configs + the shared .env (secrets) — never commit. Only example templates
|
||||||
/config/*.toml
|
# (*.example.toml, .env.example) are tracked.
|
||||||
!/config/instance.example.toml
|
/config/*
|
||||||
|
!/config/*.example.toml
|
||||||
|
!/config/.env.example
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# trex-core shared infrastructure + secrets (12-factor, REQ-010 CHG-20260609-01).
|
||||||
|
#
|
||||||
|
# These are SHARED across all per-instance TOML configs in this directory (same
|
||||||
|
# DB / broker / portfolio-hub / RabbitMQ), so they live here instead of the
|
||||||
|
# per-instance TOML.
|
||||||
|
#
|
||||||
|
# Resolution (trex_core.runtime.config):
|
||||||
|
# config_dir = --config-path (default ./config)
|
||||||
|
# --env-file <path> wins, else <config_dir>/.env
|
||||||
|
# (standalone reads with no config fall back to legacy CWD-relative .env)
|
||||||
|
# Real exported environment variables ALWAYS take precedence over this file.
|
||||||
|
# Use --config-path <abs-dir> to stay CWD-independent when launching elsewhere.
|
||||||
|
#
|
||||||
|
# Copy this template to `.env` in the same config dir and fill in:
|
||||||
|
# cp config/.env.example config/.env
|
||||||
|
|
||||||
|
# --- Core (required) ---
|
||||||
|
# psycopg DSN for the PostgreSQL single-writer store.
|
||||||
|
TREX_POSTGRESQL_DSN=dbname=trex_core host=/var/run/postgresql
|
||||||
|
|
||||||
|
# --- Daemon (optional, defaults shown) ---
|
||||||
|
TREX_TOTAL_SLOTS=10
|
||||||
|
TREX_EXECUTION_INTERVAL_SEC=1.0
|
||||||
|
|
||||||
|
# --- Broker selection (REQ-011/012/013) ---
|
||||||
|
# 'paper' (deterministic test double, default) | 'kis' (real KIS Open API)
|
||||||
|
TREX_BROKER=paper
|
||||||
|
|
||||||
|
# --- Paper broker tuning (only when TREX_BROKER=paper; for e2e scenarios) ---
|
||||||
|
# TREX_PAPER_FILL_AFTER_POLLS=0
|
||||||
|
# TREX_PAPER_QTY_PER_POLL=
|
||||||
|
# TREX_PAPER_REJECT_SYMBOLS=
|
||||||
|
|
||||||
|
# --- KIS broker (only when TREX_BROKER=kis) ---
|
||||||
|
# 모의투자(sandbox): https://openapivts.koreainvestment.com:29443
|
||||||
|
# 실계좌(live): https://openapi.koreainvestment.com:9443 (requires TREX_KIS_ALLOW_LIVE=true)
|
||||||
|
TREX_KIS_BROKER_BASE_URL=https://openapivts.koreainvestment.com:29443
|
||||||
|
TREX_KIS_BROKER_TIMEOUT=5
|
||||||
|
TREX_KIS_BROKER_APP_KEY=
|
||||||
|
TREX_KIS_BROKER_APP_SECRET=
|
||||||
|
TREX_KIS_BROKER_ACCOUNT_REF=12345678-01
|
||||||
|
TREX_KIS_ALLOW_LIVE=false
|
||||||
|
TREX_KIS_REDIS_URL=
|
||||||
|
|
||||||
|
# --- portfolio-hub ledger (REQ-017/018/019, BAT-006) ---
|
||||||
|
# portfolio_hub_token is per-instance and lives in the instance TOML, not here.
|
||||||
|
TREX_PORTFOLIO_HUB_BASE_URL=
|
||||||
|
TREX_PORTFOLIO_HUB_TIMEOUT=5
|
||||||
|
TREX_LEDGER_INTERVAL_SEC=1.0
|
||||||
|
|
||||||
|
# --- Snapshot scheduler (REQ-030, BAT-008) ---
|
||||||
|
TREX_SNAPSHOT_SCHEDULE_KST=07:30
|
||||||
|
TREX_SNAPSHOT_TICK_SEC=60
|
||||||
|
|
||||||
|
# --- RabbitMQ transport (REQ-022, BAT-007) ---
|
||||||
|
TREX_RABBITMQ_URL=
|
||||||
|
TREX_RABBITMQ_TIMEOUT=5
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# BAT-026: config 디렉터리 기반 설정 관리 — `--config-path`/`--env-file` + config-dir `.env` 공용화 (trex-orb-v1 정합)
|
||||||
|
|
||||||
|
- Status: release-candidate
|
||||||
|
- Profile: batch-lite
|
||||||
|
- Source Discovery: 없음 (trex-orb-v1 설정 관리 정합 — change-req 주도)
|
||||||
|
- Included REQ: REQ-048 (CHG-20260609-01), REQ-010 (CHG-20260609-01)
|
||||||
|
- Excluded REQ: 없음
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- trex-orb-v1의 config 디렉터리 기반 설정 관리(`--config <name>` + `--config-path <dir>` + config-dir `.env`)를 trex-core 에 이식한다.
|
||||||
|
- 핵심: `resolve_config_toml(name, config_path)`(이름→`<dir>/<name>.toml`, `.toml` 자동 부착, **슬래시 포함 값은 하위호환 명시 경로**)(REQ-048), `.env` 를 config-dir 기준 결정적 해석(`--env-file` 우선 → `<config_dir>/.env`)으로 변경 + `config/.env.example` + `.gitignore` `config/*` 허용목록(REQ-010). 데몬·운영자 CLI 양쪽에 `--config-path`/`--env-file` 적용.
|
||||||
|
- 안전: `from_env()` 의 legacy `.env` 로드(루트 `.env` 폴백)를 유지 → config/.env 부재 시에도 기존 루트 `.env` 로 동작(완전 하위호환). config/.env 존재 시 먼저 로드돼 우선(`override=False`).
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
- In Scope:
|
||||||
|
- `resolve_config_toml` + `DEFAULT_CONFIG_DIR` (REQ-048)
|
||||||
|
- `_load_dotenv(config_path, env_file)` config-dir 결정적 해석 + legacy 폴백 (REQ-010)
|
||||||
|
- 데몬 `main` argparse `--config-path`/`--env-file` + `from_config(toml_path, env_file)`
|
||||||
|
- 운영자 CLI `--config-path`/`--env-file` + `_resolve_instance` 통일
|
||||||
|
- `.gitignore` `config/*` 허용목록, `config/.env.example` 신규
|
||||||
|
- Out of Scope:
|
||||||
|
- 실제 `.env` → `config/.env` 물리 이동(운영 배포 절차 — 루트 `.env` 폴백으로 무중단)
|
||||||
|
- TOML/env 레이어링 정책(키 우선순위) 변경 — 기존 유지
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
- Planning: [planning.md](./planning.md)
|
||||||
|
- Design: none (batch-lite with no architecture impact)
|
||||||
|
- Implementation: [verification.md](./verification.md) 에 구현 요약 포함 (batch-lite)
|
||||||
|
- Verification: [verification.md](./verification.md)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Profile **batch-lite**: 국소 변경(config.py/daemon.py/cli.py + .gitignore + config 예제). 신규 런타임 흐름·계약 필드 없음(인자·해석 규칙 추가). `--config` 의미 확장은 인터페이스 영향이라 정식 change-req(REQ-048/010 CHG-20260609-01) 경유 → **Design Gate: no**.
|
||||||
|
- REQ-048/010 은 CHG-20260609-01 로 **Approved**(신규 AC 미구현). 본 batch release-candidate + confirm-req-implemented 로 **Implemented** 복귀.
|
||||||
|
- 발견 근거: 사용자 요청(trex-orb-v1 설정 관리 형태 정합).
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# BAT-026 Planning
|
||||||
|
|
||||||
|
- Status: confirmed
|
||||||
|
- Batch: BAT-026
|
||||||
|
- Profile: batch-lite
|
||||||
|
|
||||||
|
## Included REQ
|
||||||
|
- REQ-048 (CHG-20260609-01) — `--config` 이름 해석 + `--config-path` + `resolve_config_toml`
|
||||||
|
- REQ-010 (CHG-20260609-01) — config-dir `.env` 결정적 해석 + `--env-file` + `config/.env.example` + `.gitignore`
|
||||||
|
|
||||||
|
## Delivery Plan
|
||||||
|
1. `config.py`: `DEFAULT_CONFIG_DIR` + `resolve_config_toml(name, config_path)`(슬래시 포함=명시 경로 하위호환). `_load_dotenv(config_path=None, env_file=None)` — `--env-file` 우선 → `<config_dir>/.env` → (config 컨텍스트 없으면) legacy `find_dotenv(usecwd=True)` 폴백. `from_config(toml_path, env_file=None)` 가 config-dir `.env` 선로드 후 `from_env()`.
|
||||||
|
2. `daemon.py main`: `--config-path`/`--env-file` argparse + `resolve_config_toml` + `from_config(path, env_file)`.
|
||||||
|
3. `cli.py`: `_add_common` 에 `--config-path`/`--env-file`; `_resolve_instance` 가 `resolve_config_toml` 경유; `main` 이 from_env 전 config-dir `.env` 선로드.
|
||||||
|
4. `.gitignore` `config/*` 허용목록(`*.example.toml`/`.env.example`), `config/.env.example` 신규.
|
||||||
|
5. 테스트: resolve_config_toml(이름/.toml/config-path/경로형), _load_dotenv(env-file/config-dir/override/부재/CWD무관), daemon·cli 인자.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Internal: `from_env()` 의 legacy `.env` 로드 유지가 하위호환의 핵심.
|
||||||
|
- External: 없음.
|
||||||
|
|
||||||
|
## Design Gate
|
||||||
|
- Design Required Before Implementation: no
|
||||||
|
- Reason: 국소 변경(3개 런타임 파일 + .gitignore + config 예제). 신규 계약 필드/런타임 흐름 없음. `--config` 의미 확장은 change-req(CHG-20260609-01)로 이미 정합.
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
1. config.py/daemon.py/cli.py + .gitignore + config/.env.example 구현.
|
||||||
|
2. 단위(resolve/dotenv/인자) green + 전체 회귀 + ruff.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
- R-1: config/.env 부재 시 키 미로드로 기동 실패. 대응: `from_env()` legacy 루트 `.env` 폴백 유지 → 무중단(완전 하위호환).
|
||||||
|
- R-2: `--config` 경로형 사용처 회귀. 대응: 슬래시 포함 값 명시 경로 폴백 + 회귀 테스트.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# BAT-026 Verification
|
||||||
|
|
||||||
|
- Status: approved
|
||||||
|
- Batch: BAT-026
|
||||||
|
- Profile: batch-lite
|
||||||
|
|
||||||
|
## Acceptance Mapping
|
||||||
|
|
||||||
|
### REQ-048 (CHG-20260609-01) — `--config` 이름 해석 + `--config-path`
|
||||||
|
| AC | Evidence |
|
||||||
|
|----|----------|
|
||||||
|
| AC-7: `--config <name> [--config-path <dir>]` → `<dir or ./config>/<name>.toml`(.toml 자동 부착) | `tests/runtime/test_config_resolution.py::test_resolve_name_default_dir` / `::test_resolve_appends_toml` / `::test_resolve_with_config_path` → PASS |
|
||||||
|
| AC-8: 슬래시 포함(경로형) 값은 명시 경로로 그대로 사용(하위호환) | `::test_resolve_path_form_passthrough` → PASS. 라이브: `trex-core universe --config config/test.toml` 정상(name 형태와 동일 결과) |
|
||||||
|
| AC-9: 운영자 CLI 도 `--config-path` 수용, `_resolve_instance` 동일 규칙 | `::test_cli_resolve_instance_honors_config_path` → PASS. 라이브: `--config test --config-path config` → instance=test 라우팅 |
|
||||||
|
|
||||||
|
### REQ-010 (CHG-20260609-01) — config-dir `.env` + `--env-file`
|
||||||
|
| AC | Evidence |
|
||||||
|
|----|----------|
|
||||||
|
| AC-1(갱신): `<config_dir>/.env`(또는 `--env-file`) 로드 → dsn 반영 | `::test_dotenv_loaded_from_config_dir_cwd_independent` → PASS |
|
||||||
|
| AC-2: OS env 우선(`override=False`) | `::test_dotenv_os_env_takes_precedence` + 기존 `test_config_dotenv.py::test_os_env_takes_precedence_over_dotenv` → PASS |
|
||||||
|
| AC-3: `.env` 부재 시 회귀 없음 | `::test_dotenv_absent_is_noop` + 기존 `test_no_dotenv_uses_os_env_only` → PASS |
|
||||||
|
| AC-4: `.gitignore` `config/*` 무시 + `*.example.toml`/`.env.example` 추적 | `test_instance_config.py::test_gitignore_excludes_instance_toml`(갱신) → PASS |
|
||||||
|
| AC-6: CWD 무관 결정적 로드 | `::test_dotenv_loaded_from_config_dir_cwd_independent`(다른 CWD 로 chdir 후 config-dir `.env` 로드) → PASS |
|
||||||
|
| AC-7: `--env-file` 우선 | `::test_dotenv_env_file_wins` → PASS |
|
||||||
|
|
||||||
|
## 하위호환 (R-1/R-2)
|
||||||
|
- `from_env()` 의 legacy `.env` 로드(루트 `.env` CWD 폴백) 유지 → config/.env 부재에도 기존 루트 `.env` 로 동작. 기존 `test_config_dotenv.py` 6건 전부 green(무회귀).
|
||||||
|
- 경로형 `--config` 무중단: 라이브 스모크에서 `--config config/test.toml` 와 `--config test --config-path config` 가 **byte 동일 스냅샷** 반환(instance=test).
|
||||||
|
|
||||||
|
## 검증 명령/결과
|
||||||
|
- 단위(config): `pytest tests/runtime/test_config_resolution.py test_config_dotenv.py test_instance_config.py -q` → **23 passed**.
|
||||||
|
- 전체 회귀: `pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → **375 passed, 35 skipped, 1 deselected**.
|
||||||
|
- lint: `ruff check`(config.py/daemon.py/cli.py + 신규/갱신 테스트) → All checks passed.
|
||||||
|
- 라이브 스모크: 실행 중 test 데몬에 `trex-core universe --config test --config-path config --json` 와 `--config config/test.toml --json` → 동일 응답(instance=test, 라우팅·.env 폴백 정상).
|
||||||
|
|
||||||
|
## Baseline 문서 동기화
|
||||||
|
- `config/.env.example` 신규(공유 인프라 키 + config-dir 해석 헤더). `docs/project-structure.md` 설정 계층 서술은 후속 정합(흐름 변화 없음 — 인자/해석 규칙 추가) — 구조 영향 없음(Design Gate: no).
|
||||||
|
|
||||||
|
## 결과 요약
|
||||||
|
- REQ-048 AC-7/8/9 + REQ-010 AC-1/2/3/4/6/7 전부 단위 PASS + 라이브 스모크 무회귀.
|
||||||
|
- **Ready for Release: true.**
|
||||||
|
|
||||||
|
## 미검증/한계
|
||||||
|
- 데몬 진입점(`daemon.py:main`)은 `# pragma: no cover`(프로세스 진입점) — `resolve_config_toml`/`from_config(env_file)` 단위 + CLI 라이브 스모크로 동등 경로 커버.
|
||||||
|
- 실제 `.env` → `config/.env` 물리 이동은 운영 배포 절차(루트 `.env` 폴백으로 무중단) — 본 batch 범위 밖.
|
||||||
@@ -33,3 +33,4 @@
|
|||||||
| BAT-023 | standard | released | REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) | dcy-018 | [bat-023](./bat-023_20260608_ledger-cash-position-writeback-option-d/) |
|
| BAT-023 | standard | released | REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) | dcy-018 | [bat-023](./bat-023_20260608_ledger-cash-position-writeback-option-d/) |
|
||||||
| BAT-024 | batch-lite | release-candidate | REQ-032 (CHG-20260609-01) | dcy-016 (issue #17) | [bat-024](./bat-024_20260609_operator-enter-forced-close-at/) |
|
| BAT-024 | batch-lite | release-candidate | REQ-032 (CHG-20260609-01) | dcy-016 (issue #17) | [bat-024](./bat-024_20260609_operator-enter-forced-close-at/) |
|
||||||
| BAT-025 | standard | release-candidate | REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01) | dcy-019 (issue #18) | [bat-025](./bat-025_20260609_slot-entry-count-and-closed-reallocation/) |
|
| BAT-025 | standard | release-candidate | REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01) | dcy-019 (issue #18) | [bat-025](./bat-025_20260609_slot-entry-count-and-closed-reallocation/) |
|
||||||
|
| BAT-026 | batch-lite | release-candidate | REQ-048 (CHG-20260609-01), REQ-010 (CHG-20260609-01) | 없음 (trex-orb-v1 정합) | [bat-026](./bat-026_20260609_config-dir-env-and-config-path/) |
|
||||||
|
|||||||
@@ -10,18 +10,21 @@
|
|||||||
|
|
||||||
## Requirement
|
## Requirement
|
||||||
- `DaemonConfig.from_env()` 가 `os.environ` 을 읽기 전에 `.env`(있으면)를 로드한다. python-dotenv 를 의존성에 추가하고, `.env` 는 `.gitignore` 에 등록해 커밋되지 않게 하며, `.env.example` 를 참고용으로 제공한다. `.env` 가 없거나 dotenv 미설치여도 기존 동작(os.environ 단독)을 유지한다(회귀 없음).
|
- `DaemonConfig.from_env()` 가 `os.environ` 을 읽기 전에 `.env`(있으면)를 로드한다. python-dotenv 를 의존성에 추가하고, `.env` 는 `.gitignore` 에 등록해 커밋되지 않게 하며, `.env.example` 를 참고용으로 제공한다. `.env` 가 없거나 dotenv 미설치여도 기존 동작(os.environ 단독)을 유지한다(회귀 없음).
|
||||||
|
- (CHG-20260609-01, trex-orb-v1 정합) `.env` 해석은 **config 디렉터리 기준 결정적**이다 — `--env-file <path>` 우선, 없으면 `<config_dir>/.env`(TOML 옆). CWD 의존 상향 탐색(`find_dotenv(usecwd=True)`)을 사용하지 않는다. `.env` 는 `config/.env` 로 공용화하고 `config/.env.example` 를 제공하며, `.gitignore` 는 `config/*` 를 무시하되 `*.example.toml`/`.env.example` 만 추적한다. `override=False`(실제 OS env 우선)는 유지한다.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
1. Given CWD 에 `.env`(`TREX_POSTGRESQL_DSN=...`)가 있고 해당 OS env 가 미설정, When `DaemonConfig.from_env()`, Then `.env` 값이 로드되어 `dsn` 에 반영된다.
|
1. (CHG-20260609-01 갱신) Given `<config_dir>/.env`(또는 `--env-file`)에 `TREX_POSTGRESQL_DSN=...` 가 있고 해당 OS env 가 미설정, When 설정 로드, Then `.env` 값이 로드되어 `dsn` 에 반영된다.
|
||||||
2. Given OS env 와 `.env` 에 같은 키가 다른 값으로 존재, When `from_env()`, Then OS env 값이 우선한다(.env 가 덮어쓰지 않음).
|
2. Given OS env 와 `.env` 에 같은 키가 다른 값으로 존재, When 로드, Then OS env 값이 우선한다(.env 가 덮어쓰지 않음, `override=False`).
|
||||||
3. Given `.env` 가 없음, When `from_env()`, Then 기존처럼 `os.environ` 만으로 동작한다(회귀 없음).
|
3. Given `.env` 가 없음, When 로드, Then 기존처럼 `os.environ` 만으로 동작한다(회귀 없음).
|
||||||
4. Given 저장소, When `.gitignore` 확인, Then `.env` 가 무시 대상이며 `.env.example` 은 추적된다.
|
4. Given 저장소, When `.gitignore` 확인, Then `config/*` 가 무시 대상이며 `config/*.example.toml`·`config/.env.example` 은 추적된다.
|
||||||
5. Given `broker=paper` 이고 `.env`/OS env 에 `TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS` 가 설정, When `DaemonConfig.from_env()` 후 `_build_broker`, Then paper 더블이 해당 시나리오(지연/부분/거부, REQ-011 AC-2)로 구성된다. 미설정이면 `PaperBroker()` 기본(즉시 전량 체결·거부 없음)과 byte-for-byte 동일하다(회귀 없음).
|
5. Given `broker=paper` 이고 `.env`/OS env 에 `TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS` 가 설정, When `DaemonConfig.from_env()` 후 `_build_broker`, Then paper 더블이 해당 시나리오(지연/부분/거부, REQ-011 AC-2)로 구성된다. 미설정이면 `PaperBroker()` 기본과 동일(회귀 없음).
|
||||||
|
6. (CHG-20260609-01) Given CWD 를 바꿔 실행, When 설정 로드, Then `<config_dir>/.env` 가 CWD 와 무관하게 결정적으로 로드된다(상향 탐색 없음).
|
||||||
|
7. (CHG-20260609-01) Given `--env-file <path>`, When 로드, Then `<config_dir>/.env` 대신 그 파일이 로드된다(우선).
|
||||||
|
|
||||||
## Impacted Area
|
## Impacted Area
|
||||||
- Docs: 없음(baseline 구조/인터페이스/런타임 흐름 변경 없음 — minor-change)
|
- Docs: (CHG-20260609-01) `config/.env.example`(신규, 공유 인프라 키), `.env`→`config/.env` 이동
|
||||||
- Modules: pyproject.toml(python-dotenv), trex_core/runtime/config.py(load_dotenv, PaperBrokerConfig), trex_core/runtime/daemon.py(_build_broker paper 주입), .gitignore, .env.example(신규 + paper 튜닝 키)
|
- Modules: pyproject.toml(python-dotenv), trex_core/runtime/config.py(load_dotenv→`_load_dotenv(config_path, env_file)` config-dir 해석, PaperBrokerConfig), trex_core/runtime/daemon.py(_build_broker paper 주입, `--env-file`), cli.py(`--env-file`), .gitignore(config/* 허용목록), .env.example(paper 튜닝 키)
|
||||||
- Tests: tests/runtime/test_config_dotenv.py (로드/우선순위/부재 회귀, paper 튜닝 기본/파싱)
|
- Tests: tests/runtime/test_config_dotenv.py (로드/우선순위/부재 회귀, paper 튜닝 기본/파싱, config-dir·`--env-file`·CWD 무관)
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
- minor-change fast path: 단일 저위험 Configuration REQ, 구조/인터페이스/런타임 흐름 영향 없음(기동 시 env 소스 추가뿐). batch-lite 로 처리, design 불요.
|
- minor-change fast path: 단일 저위험 Configuration REQ, 구조/인터페이스/런타임 흐름 영향 없음(기동 시 env 소스 추가뿐). batch-lite 로 처리, design 불요.
|
||||||
@@ -235,3 +238,81 @@
|
|||||||
|
|
||||||
#### Validation Plan
|
#### Validation Plan
|
||||||
- BAT-009 verification evidence 재참조로 충분.
|
- BAT-009 verification evidence 재참조로 충분.
|
||||||
|
|
||||||
|
### CHG-20260609-01
|
||||||
|
|
||||||
|
- Date: 2026-06-09
|
||||||
|
- Author: Justin Ko
|
||||||
|
|
||||||
|
#### Change Summary
|
||||||
|
|
||||||
|
- (change-req, trex-orb-v1 설정 관리 정합) `.env` 해석을 **config 디렉터리 기준 결정적**으로 변경한다 — `--env-file <path>` 가 우선, 없으면 **`<config_dir>/.env`**(TOML 옆). 기존 `find_dotenv(usecwd=True)`(CWD 의존 상향 탐색)를 제거해 실행 위치와 무관하게 동일 `.env` 를 로드한다. `.env` 를 repo 루트 → **`config/.env`** 로 이동(공용화), `config/.env.example` 신설, `.gitignore` 를 `config/*` 무시 + `*.example.toml`/`.env.example` 만 추적하는 형태로 전환. `override=False`(실제 OS env 우선)는 유지. `config_dir` 는 REQ-048 CHG-20260609-01 의 `--config-path` 로 제공된다.
|
||||||
|
|
||||||
|
#### Intent
|
||||||
|
|
||||||
|
- 어디서 실행하든 동일한 **공유 인프라 `.env`**(DSN/RabbitMQ/KIS/portfolio-hub 등)를 결정적으로 로드한다. 다중 인스턴스가 같은 broker/DSN/hub 를 공유하도록 `.env` 를 인스턴스 TOML 과 분리해 config 디렉터리에 공용화(12-factor).
|
||||||
|
|
||||||
|
#### Acceptance Criteria Delta
|
||||||
|
|
||||||
|
- Added:
|
||||||
|
- AC-6: `.env` 는 `<config_dir>/.env`(TOML 옆)에서 **CWD 와 무관하게** 결정적으로 로드된다. dotenv 미설치/`.env` 부재 시 no-op(회귀 없음).
|
||||||
|
- AC-7: `--env-file <path>` 가 주어지면 `<config_dir>/.env` 대신 그 파일을 로드한다(우선).
|
||||||
|
- AC-8: `.gitignore` 는 `config/*` 를 무시하되 `config/*.example.toml` 과 `config/.env.example` 은 추적한다(실제 `.env`/인스턴스 TOML 비커밋).
|
||||||
|
- Updated:
|
||||||
|
- AC-1: "CWD 의 `.env`" → "`<config_dir>/.env`"(또는 `--env-file`) 로 로드 소스 변경.
|
||||||
|
- Removed: none
|
||||||
|
|
||||||
|
#### Impacted Area Delta
|
||||||
|
|
||||||
|
- Modules: `trex_core/runtime/config.py`(`_load_dotenv(config_path, env_file)` — config-dir 결정적 해석, `find_dotenv(usecwd=True)` 제거), `trex_core/runtime/daemon.py`·`cli.py`(`--env-file` 인자 전달)
|
||||||
|
- Docs: `config/.env.example`(신규, 공유 인프라 키), `.gitignore`(config/* 허용목록), `.env`(루트→`config/.env` 이동)
|
||||||
|
- Tests: `tests/runtime/test_config_dotenv.py`(config-dir 로드/`--env-file` 우선/CWD 무관/`override=False` 회귀/부재 no-op)
|
||||||
|
|
||||||
|
#### Delivery Trace Delta
|
||||||
|
|
||||||
|
- Batches: 신규 batch-lite(REQ-048 CHG-20260609-01 과 동일 배치)
|
||||||
|
- Releases: none
|
||||||
|
|
||||||
|
#### Revalidation Impact
|
||||||
|
|
||||||
|
- Reverification Needed: yes (신규 batch 에서 AC-6/7/8 + AC-1 변경 검증)
|
||||||
|
- Existing Implementation Invalidated: yes (`.env` 해석 위치가 CWD → config-dir 로 변경되어 기존 루트 `.env` 의존 동작이 바뀜 — 운영 배포 시 `.env` 이동 필요)
|
||||||
|
- Status Recommendation: Implemented → Approved (신규/변경 AC 미구현 — batch 재검증 후 Implemented 복귀)
|
||||||
|
- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verification → confirm-req-implemented
|
||||||
|
|
||||||
|
#### Validation Plan
|
||||||
|
|
||||||
|
- 신규 BAT: `<config_dir>/.env` 결정적 로드(CWD 변경해도 동일), `--env-file` 우선, OS env 우선(`override=False`) 회귀, `.env` 부재 no-op 단위. `.gitignore` config/* 허용목록 가드.
|
||||||
|
|
||||||
|
### CHG-20260609-02
|
||||||
|
|
||||||
|
- Date: 2026-06-09
|
||||||
|
- Author: Justin Ko
|
||||||
|
|
||||||
|
#### Change Summary
|
||||||
|
|
||||||
|
- Status 변경: Approved → Implemented. BAT-026 release-candidate verification evidence 기반(CHG-20260609-01 AC-1/2/3/4/6/7).
|
||||||
|
|
||||||
|
#### Acceptance Criteria Delta
|
||||||
|
|
||||||
|
- Added/Updated/Removed: 없음
|
||||||
|
|
||||||
|
#### Impacted Area Delta
|
||||||
|
|
||||||
|
- Docs/Modules/Tests: 없음
|
||||||
|
|
||||||
|
#### Delivery Trace Delta
|
||||||
|
|
||||||
|
- Batches: BAT-026 (release-candidate)
|
||||||
|
- Releases: none
|
||||||
|
|
||||||
|
#### Revalidation Impact
|
||||||
|
|
||||||
|
- Reverification Needed: no (BAT-026 에서 완료)
|
||||||
|
- Existing Implementation Invalidated: no
|
||||||
|
- Status Recommendation: Implemented
|
||||||
|
- Follow-up Action: 운영 배포 시 `.env`→`config/.env` 이동(루트 `.env` 폴백으로 무중단). release 단계 별도 진행.
|
||||||
|
|
||||||
|
#### Validation Plan
|
||||||
|
|
||||||
|
- tests/runtime/test_config_resolution.py(config-dir `.env`/`--env-file`/CWD 무관/override) + test_config_dotenv.py 6건 무회귀 + `.gitignore` 가드. 전체 375 passed, ruff clean.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
- `config.py` 에 `DaemonInstanceConfig` dataclass(`instance_key: str`, `portfolio_hub_token: str`)와 `from_toml(path)` 로더를 추가한다(stdlib `tomllib` 사용).
|
- `config.py` 에 `DaemonInstanceConfig` dataclass(`instance_key: str`, `portfolio_hub_token: str`)와 `from_toml(path)` 로더를 추가한다(stdlib `tomllib` 사용).
|
||||||
- `DaemonConfig` 구성 시 `--config <path>.toml` 의 TOML 값을 `from_env()` 결과와 병합하는 팩토리(예: `from_config(toml_path)`)를 추가한다. `portfolio_hub_token` 은 TOML 값을 사용해 `PortfolioHubConfig.token` 에 주입한다.
|
- `DaemonConfig` 구성 시 `--config <path>.toml` 의 TOML 값을 `from_env()` 결과와 병합하는 팩토리(예: `from_config(toml_path)`)를 추가한다. `portfolio_hub_token` 은 TOML 값을 사용해 `PortfolioHubConfig.token` 에 주입한다.
|
||||||
- 데몬 기동 진입점은 `--config` 를 **필수 인자**로 받는다. 인자 없이 기동하면 오류 메시지를 출력하고 비정상 종료(exit code ≠ 0)한다. 기본값 fallback 없음(OQ-2 Answered).
|
- 데몬 기동 진입점은 `--config` 를 **필수 인자**로 받는다. 인자 없이 기동하면 오류 메시지를 출력하고 비정상 종료(exit code ≠ 0)한다. 기본값 fallback 없음(OQ-2 Answered).
|
||||||
|
- (CHG-20260609-01, trex-orb-v1 정합) `--config` 는 **이름**으로 받고 `--config-path <dir>`(기본 `./config`)로 config 디렉터리를 분리한다. `resolve_config_toml(name, config_path)` 가 `<config_dir>/<name>.toml` 로 해석하며 `.toml` 을 자동 부착한다. **하위호환**: 슬래시 포함(경로형) `--config` 값은 명시 경로로 그대로 사용한다. 운영자 CLI(`trex-core`)도 `--config-path` 를 동일 규칙으로 수용한다.
|
||||||
- `instance_key` 와 `portfolio_hub_token` 은 `.env`/OS env 에서 읽지 않는다. TOML 미존재/필수 필드 누락 시 명확한 오류로 종료한다.
|
- `instance_key` 와 `portfolio_hub_token` 은 `.env`/OS env 에서 읽지 않는다. TOML 미존재/필수 필드 누락 시 명확한 오류로 종료한다.
|
||||||
- `.gitignore` 에 인스턴스 TOML 경로 패턴(예: `*.toml` 또는 `config/*.toml`)을 추가해 설정 파일이 커밋되지 않게 한다. 토큰/instance_key 는 로그에 평문 출력하지 않는다(NFR-4).
|
- `.gitignore` 에 인스턴스 TOML 경로 패턴(예: `*.toml` 또는 `config/*.toml`)을 추가해 설정 파일이 커밋되지 않게 한다. 토큰/instance_key 는 로그에 평문 출력하지 않는다(NFR-4).
|
||||||
|
|
||||||
@@ -25,14 +26,18 @@
|
|||||||
4. Given TOML 에 필수 필드(`instance_key`) 누락, When `from_toml`/`from_config`, Then 명확한 오류로 실패한다(조용한 기본값 금지).
|
4. Given TOML 에 필수 필드(`instance_key`) 누락, When `from_toml`/`from_config`, Then 명확한 오류로 실패한다(조용한 기본값 금지).
|
||||||
5. Given 저장소, When `.gitignore` 확인, Then 인스턴스 TOML 경로 패턴이 무시 대상이다.
|
5. Given 저장소, When `.gitignore` 확인, Then 인스턴스 TOML 경로 패턴이 무시 대상이다.
|
||||||
6. Given 데몬 기동 로그, When 출력 확인, Then `portfolio_hub_token` 평문이 로그에 노출되지 않는다.
|
6. Given 데몬 기동 로그, When 출력 확인, Then `portfolio_hub_token` 평문이 로그에 노출되지 않는다.
|
||||||
|
7. (CHG-20260609-01) Given `--config <name> [--config-path <dir>]`, When `resolve_config_toml`, Then `<dir or ./config>/<name>.toml` 로 해석되고 `.toml` 이 없으면 자동 부착된다.
|
||||||
|
8. (CHG-20260609-01) Given 슬래시 포함(경로형) `--config` 값, When 해석, Then `--config-path` 와 무관하게 명시 경로로 그대로 사용된다(하위호환).
|
||||||
|
9. (CHG-20260609-01) Given 운영자 CLI 가 `--config-path` 와 함께 호출, When `_resolve_instance`, Then 동일 `resolve_config_toml` 규칙으로 TOML 을 찾아 instance_key 를 해석한다.
|
||||||
|
|
||||||
## Impacted Area
|
## Impacted Area
|
||||||
|
|
||||||
- Docs:
|
- Docs:
|
||||||
- `docs/project-structure.md` — 설정 파일 계층(TOML + env) (REQ-051 처리)
|
- `docs/project-structure.md` — 설정 파일 계층(TOML + env) (REQ-051 처리)
|
||||||
- Modules:
|
- Modules:
|
||||||
- `trex_core/runtime/config.py` — `DaemonInstanceConfig` + `from_toml` + `from_config` 병합
|
- `trex_core/runtime/config.py` — `DaemonInstanceConfig` + `from_toml` + `from_config` 병합, (CHG-20260609-01) `resolve_config_toml`/`DEFAULT_CONFIG_DIR`
|
||||||
- 데몬 진입점(`trex_core/runtime/daemon.py:main` 또는 `__main__`) — `--config` 필수 파싱(argparse)
|
- 데몬 진입점(`trex_core/runtime/daemon.py:main` 또는 `__main__`) — `--config` 필수 파싱(argparse), (CHG-20260609-01) `--config-path`
|
||||||
|
- 운영자 CLI `trex_core/runtime/cli.py` — (CHG-20260609-01) `--config-path` + `_resolve_instance` 통일
|
||||||
- `.gitignore` — 인스턴스 TOML 패턴
|
- `.gitignore` — 인스턴스 TOML 패턴
|
||||||
- Tests:
|
- Tests:
|
||||||
- `tests/runtime/` — TOML 로더 파싱/필수 인자/토큰 주입/누락 오류
|
- `tests/runtime/` — TOML 로더 파싱/필수 인자/토큰 주입/누락 오류
|
||||||
@@ -186,3 +191,81 @@
|
|||||||
#### Validation Plan
|
#### Validation Plan
|
||||||
|
|
||||||
- BAT-018: from_toml 기본값(KST/00:00) + TOML 값 from_config 전파 단위.
|
- BAT-018: from_toml 기본값(KST/00:00) + TOML 값 from_config 전파 단위.
|
||||||
|
|
||||||
|
### CHG-20260609-01
|
||||||
|
|
||||||
|
- Date: 2026-06-09
|
||||||
|
- Author: Justin Ko
|
||||||
|
|
||||||
|
#### Change Summary
|
||||||
|
|
||||||
|
- (change-req, trex-orb-v1 설정 관리 정합) `--config` 를 **경로 대신 이름**으로 받고 `--config-path <dir>`(기본 `./config`)로 config 디렉터리를 분리한다. `resolve_config_toml(name, config_path)` 추가 — `<config_dir>/<name>.toml`(`.toml` 자동 부착). **하위호환**: 슬래시 포함(경로형) `--config` 값은 명시 경로로 그대로 사용(기존 운영 기동 스크립트 무중단). 운영자 CLI(`trex-core`)에도 `--config-path` 를 적용해 인스턴스 라우팅을 동일 규칙으로 통일. 데몬·CLI 진입점 argparse 갱신.
|
||||||
|
|
||||||
|
#### Intent
|
||||||
|
|
||||||
|
- trex-orb-v1 과 동일한 config 디렉터리 기반 설정 관리로 통일 — CWD 독립·이름 기반 인스턴스 선택. 공유 `.env`(REQ-010 CHG-20260609-01)의 해석 기준 디렉터리(`config_dir`)를 `--config-path` 로 제공한다.
|
||||||
|
|
||||||
|
#### Acceptance Criteria Delta
|
||||||
|
|
||||||
|
- Added:
|
||||||
|
- AC-7: `--config <name> [--config-path <dir>]` → `<dir or ./config>/<name>.toml` 로 해석되고 `.toml` 확장자가 없으면 자동 부착된다(`resolve_config_toml`).
|
||||||
|
- AC-8: 슬래시를 포함한(경로형) `--config` 값은 `--config-path` 와 무관하게 **명시 경로**로 그대로 사용된다(하위호환 — 기존 `--config config/test.toml` 무중단).
|
||||||
|
- AC-9: 운영자 CLI(`trex-core`)도 `--config-path` 를 수용하며, `_resolve_instance` 가 동일 `resolve_config_toml` 규칙으로 TOML 을 찾는다.
|
||||||
|
- Updated:
|
||||||
|
- AC-2(필수 인자)는 유지 — `--config` 는 여전히 필수다(이름 또는 경로).
|
||||||
|
- Removed: none
|
||||||
|
|
||||||
|
#### Impacted Area Delta
|
||||||
|
|
||||||
|
- Modules: `trex_core/runtime/config.py`(`resolve_config_toml`, `DEFAULT_CONFIG_DIR`), `trex_core/runtime/daemon.py`(`main` argparse `--config-path`), `trex_core/runtime/cli.py`(`--config-path` + `_resolve_instance` 통일)
|
||||||
|
- Docs: `docs/project-structure.md`(설정 파일 계층 — config 디렉터리/이름 해석), `config/instance.example.toml`
|
||||||
|
- Tests: `tests/runtime/`(resolve_config_toml: 이름/.toml 부착/config-path/경로형 폴백, 데몬·CLI `--config-path` 라우팅)
|
||||||
|
|
||||||
|
#### Delivery Trace Delta
|
||||||
|
|
||||||
|
- Batches: 신규 batch-lite(REQ-010 CHG-20260609-01 과 동일 배치)
|
||||||
|
- Releases: none
|
||||||
|
|
||||||
|
#### Revalidation Impact
|
||||||
|
|
||||||
|
- Reverification Needed: yes (신규 batch 에서 AC-7/8/9 검증)
|
||||||
|
- Existing Implementation Invalidated: no (경로형 `--config` 는 하위호환 유지, 이름 해석은 additive) — 단 신규 AC 미구현
|
||||||
|
- Status Recommendation: Implemented → Approved (신규 AC-7/8/9 미구현 — batch 재검증 후 Implemented 복귀)
|
||||||
|
- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verification → confirm-req-implemented
|
||||||
|
|
||||||
|
#### Validation Plan
|
||||||
|
|
||||||
|
- 신규 BAT: `resolve_config_toml("test")`→`config/test.toml`, `("test.toml")`→`config/test.toml`, `("prod","/etc/trex")`→`/etc/trex/prod.toml`, 경로형(`config/test.toml`)→그대로 단위. 데몬·CLI `--config-path` 기동/라우팅 단위. 기존 경로형 `--config` 회귀.
|
||||||
|
|
||||||
|
### CHG-20260609-02
|
||||||
|
|
||||||
|
- Date: 2026-06-09
|
||||||
|
- Author: Justin Ko
|
||||||
|
|
||||||
|
#### Change Summary
|
||||||
|
|
||||||
|
- Status 변경: Approved → Implemented. BAT-026 release-candidate verification evidence 기반(CHG-20260609-01 AC-7/8/9).
|
||||||
|
|
||||||
|
#### Acceptance Criteria Delta
|
||||||
|
|
||||||
|
- Added/Updated/Removed: 없음
|
||||||
|
|
||||||
|
#### Impacted Area Delta
|
||||||
|
|
||||||
|
- Docs/Modules/Tests: 없음
|
||||||
|
|
||||||
|
#### Delivery Trace Delta
|
||||||
|
|
||||||
|
- Batches: BAT-026 (release-candidate)
|
||||||
|
- Releases: none
|
||||||
|
|
||||||
|
#### Revalidation Impact
|
||||||
|
|
||||||
|
- Reverification Needed: no (BAT-026 에서 완료)
|
||||||
|
- Existing Implementation Invalidated: no
|
||||||
|
- Status Recommendation: Implemented
|
||||||
|
- Follow-up Action: release 단계 별도 진행
|
||||||
|
|
||||||
|
#### Validation Plan
|
||||||
|
|
||||||
|
- tests/runtime/test_config_resolution.py(resolve_config_toml/경로형 폴백/CLI `--config-path` 라우팅) + 라이브 스모크(name·path 형태 동일 결과). 전체 375 passed, ruff clean.
|
||||||
|
|||||||
+1
-1
@@ -139,8 +139,8 @@
|
|||||||
|
|
||||||
## Recent Change Log Summary
|
## Recent Change Log Summary
|
||||||
|
|
||||||
|
- 2026-06-09 :: REQ-048/010 change-req→Implemented (CHG-20260609-01/02, trex-orb-v1 설정 관리 정합, BAT-026): config 디렉터리 기반 설정 관리 도입 — `--config` 를 이름으로 해석 + `--config-path <dir>`(기본 `./config`) + `resolve_config_toml`(슬래시 포함 값은 하위호환 명시 경로)(REQ-048), `.env` 를 config-dir 결정적 해석(`--env-file` 우선, 없으면 `<config_dir>/.env`, CWD 무관)으로 변경 + `config/.env.example` + `.gitignore` `config/*` 허용목록(REQ-010). 운영자 CLI 도 `--config-path`/`--env-file` 동일 적용. `from_env()` legacy 루트 `.env` 폴백 유지로 완전 하위호환(`.env` 이동은 운영 배포 시 무중단). batch-lite(BAT-026) 단위 23 + 전체 375 passed, ruff clean, 라이브 스모크(name·path 동일 결과) → 둘 다 **Implemented**. (change-req/confirm-req-implemented)
|
||||||
- 2026-06-09 :: REQ-002/003/006/007/009 change-req (CHG-20260609-01, dcy-019/issue #18, BAT-025): slot-scoped `entry_count` 추가 + closed 슬롯 직접 재할당. daily_slot/`Slot` 에 `entry_count`(기본 0) 필드 + `EXPECTED_SLOT_FIELDS` + 멱등 마이그레이션 008(REQ-002/006), 상태 머신 `closed→pending_buy` 신규 전이(8→9 base, ALLOWED_TRANSITIONS 14→15)(REQ-002), `_apply_transition_fields` closed→pending_buy clean-slate + 모든 `→pending_buy` 시 entry_count +1(REQ-002), `find_allocatable_slot`(idle∪closed, `entry_count→updated_at→slot_id`) selector + in-memory 동등(REQ-006), `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status(REQ-007), 테스트 계약 정합(REQ-003), baseline 2문서(data-model/runtime-flows) 정합(REQ-009). 기존 `daily_symbol_entries`(symbol-scoped) 무관·미변경. 전부 change-req(신규 REQ 없음, OQ-1/2/3). 단위 366 passed(+KIS sandbox 1건은 live .env URL 로 사전 환경 실패·무관), ruff clean. PG 게이트 마이그레이션/드리프트는 라이브 runtime DSN 미사용(파괴적 truncate 회피)·test DSN 자동 검증. 전부 **Implemented** 유지. (change-req)
|
- 2026-06-09 :: REQ-002/003/006/007/009 change-req (CHG-20260609-01, dcy-019/issue #18, BAT-025): slot-scoped `entry_count` 추가 + closed 슬롯 직접 재할당. daily_slot/`Slot` 에 `entry_count`(기본 0) 필드 + `EXPECTED_SLOT_FIELDS` + 멱등 마이그레이션 008(REQ-002/006), 상태 머신 `closed→pending_buy` 신규 전이(8→9 base, ALLOWED_TRANSITIONS 14→15)(REQ-002), `_apply_transition_fields` closed→pending_buy clean-slate + 모든 `→pending_buy` 시 entry_count +1(REQ-002), `find_allocatable_slot`(idle∪closed, `entry_count→updated_at→slot_id`) selector + in-memory 동등(REQ-006), `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status(REQ-007), 테스트 계약 정합(REQ-003), baseline 2문서(data-model/runtime-flows) 정합(REQ-009). 기존 `daily_symbol_entries`(symbol-scoped) 무관·미변경. 전부 change-req(신규 REQ 없음, OQ-1/2/3). 단위 366 passed(+KIS sandbox 1건은 live .env URL 로 사전 환경 실패·무관), ruff clean. PG 게이트 마이그레이션/드리프트는 라이브 runtime DSN 미사용(파괴적 truncate 회피)·test DSN 자동 검증. 전부 **Implemented** 유지. (change-req)
|
||||||
- 2026-06-09 :: REQ-032 change-req→Implemented (CHG-20260609-01/02, issue #17, BAT-024): `trex-core enter` 에 `--forced-close-at <ISO8601>` (절대시간) 옵션 추가 — 운영자가 매수 진입 명령 하나로 시간 기반 자동청산(forced_close) 시각 지정. CLI 가 buy intent payload 의 기존 `forced_close_at` 필드에 매핑(계약 REQ-007/021·도메인 REQ-066·forced-close 루프 REQ-037 이미 구현·불변, CLI 노출 갭만 보완). 잘못된 형식 제출 전 거부, 미지정 시 미포함(하위호환). 상대시간(`--hold-minutes`) 미채택. AC-5/6 신규. batch-lite(BAT-024) 단위 검증 → **Implemented**. 단위 353 passed, ruff clean. (change-req/confirm-req-implemented)
|
- 2026-06-09 :: REQ-032 change-req→Implemented (CHG-20260609-01/02, issue #17, BAT-024): `trex-core enter` 에 `--forced-close-at <ISO8601>` (절대시간) 옵션 추가 — 운영자가 매수 진입 명령 하나로 시간 기반 자동청산(forced_close) 시각 지정. CLI 가 buy intent payload 의 기존 `forced_close_at` 필드에 매핑(계약 REQ-007/021·도메인 REQ-066·forced-close 루프 REQ-037 이미 구현·불변, CLI 노출 갭만 보완). 잘못된 형식 제출 전 거부, 미지정 시 미포함(하위호환). 상대시간(`--hold-minutes`) 미채택. AC-5/6 신규. batch-lite(BAT-024) 단위 검증 → **Implemented**. 단위 353 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||||
- 2026-06-08 :: REQ-017/019/029 change-req + REQ-030 Approved→Implemented (dcy-018, BAT-023): ledger write 응답의 권위 cash/quantity 를 portfolio 스냅샷에 write-back(option D 활성화) — 매수/매도 직후 "수량 live·현금 stale" 비대칭 제거. `record_trade`→`LedgerWriteOutcome{result, *_after}`+어댑터 `cash_balance_after`/`asset_quantity_after` 파싱(new+duplicate)(REQ-017), `LedgerWriter` SnapshotStore write-back best-effort+`pending_cash_delta`(REQ-019), 추정 현금 오버레이(`cash_estimated` 라벨)+슬롯 오버레이 유지(REQ-029, OQ-1 B/OQ-4 i), option D 활성화로 REQ-030 Approved→Implemented. 전부 change-req(신규 REQ 없음, OQ-3 a). portfolio-hub#8(`*_after`) 동작 중. 단위 350 passed, 인프라 게이트 385 passed, ruff clean. (change-req/confirm-req-implemented)
|
- 2026-06-08 :: REQ-017/019/029 change-req + REQ-030 Approved→Implemented (dcy-018, BAT-023): ledger write 응답의 권위 cash/quantity 를 portfolio 스냅샷에 write-back(option D 활성화) — 매수/매도 직후 "수량 live·현금 stale" 비대칭 제거. `record_trade`→`LedgerWriteOutcome{result, *_after}`+어댑터 `cash_balance_after`/`asset_quantity_after` 파싱(new+duplicate)(REQ-017), `LedgerWriter` SnapshotStore write-back best-effort+`pending_cash_delta`(REQ-019), 추정 현금 오버레이(`cash_estimated` 라벨)+슬롯 오버레이 유지(REQ-029, OQ-1 B/OQ-4 i), option D 활성화로 REQ-030 Approved→Implemented. 전부 change-req(신규 REQ 없음, OQ-3 a). portfolio-hub#8(`*_after`) 동작 중. 단위 350 passed, 인프라 게이트 385 passed, ruff clean. (change-req/confirm-req-implemented)
|
||||||
- 2026-06-08 :: REQ-028 + REQ-032 change-req→Implemented (CHG-20260608-01/02 / -03/04, dcy-016 후속, BAT-022): `trex-core slots` 가 universe `assets`(portfolio)를 슬롯 상태로 오인 표시하던 갭 정정(10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후 슬롯 상태 read 표면 부재가 근인). snapshot envelope 에 read-only per-slot `slots[]`(canonical `slot_to_dict`: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/.../portfolio_id) additive 추가(REQ-028 AC-4, strategy `assets[]` 불변), `trex-core slots` 가 `slots[]`(실제 슬롯 상태) 표시·`--status` 슬롯 status 필터·`universe` 는 `assets[]` 유지(REQ-032). BAT-007 stub/BAT-008 envelope 슬롯 스키마 단일화. 신규 코드 데몬 + 실 인프라 운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). 단위 338 passed, ruff clean. pytest 인프라 게이트는 Human Approval Memo 수용. 둘 다 **Implemented**. (change-req/confirm-req-implemented)
|
- 2026-06-08 :: REQ-028 + REQ-032 change-req→Implemented (CHG-20260608-01/02 / -03/04, dcy-016 후속, BAT-022): `trex-core slots` 가 universe `assets`(portfolio)를 슬롯 상태로 오인 표시하던 갭 정정(10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후 슬롯 상태 read 표면 부재가 근인). snapshot envelope 에 read-only per-slot `slots[]`(canonical `slot_to_dict`: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/.../portfolio_id) additive 추가(REQ-028 AC-4, strategy `assets[]` 불변), `trex-core slots` 가 `slots[]`(실제 슬롯 상태) 표시·`--status` 슬롯 status 필터·`universe` 는 `assets[]` 유지(REQ-032). BAT-007 stub/BAT-008 envelope 슬롯 스키마 단일화. 신규 코드 데몬 + 실 인프라 운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). 단위 338 passed, ruff clean. pytest 인프라 게이트는 Human Approval Memo 수용. 둘 다 **Implemented**. (change-req/confirm-req-implemented)
|
||||||
- 2026-06-08 :: REQ-034/021/037/038/039/032/042 change-req (dcy-017, BAT-021): 전략 이탈로 슬롯이 워치독(안전망) 소유로 넘어간 상태를 가시화(issue #12). snapshot RPC 응답에 holding 슬롯 워치독 상태(expired/last_seen) read-only 동봉(best-effort) + `WatchdogStore.last_seen`(REQ-034/021), `trex-core slots` WATCHDOG-OWNED 표시(REQ-032), safety-sell 워치독 소유 진입(WARN)/이탈(INFO)·발동(forced_close·stop_loss=WARN, take_profit·market_close=INFO) 로그+in-process 카운터(REQ-037/038/039), baseline 2문서 정합(REQ-042). best-effort(청산 무차단), Redis 미채택. 전부 change-req(신규 REQ 없음, OQ-3). 단위 335 passed, 인프라 게이트 369 passed, ruff clean. (change-req/confirm-req-implemented)
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""config-dir resolution + config-dir/.env loading (REQ-048/010 CHG-20260609-01)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from trex_core.runtime.config import _load_dotenv, resolve_config_toml
|
||||||
|
|
||||||
|
_K = "TREX_TEST_RESOLVE_KEY"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _clean(monkeypatch):
|
||||||
|
monkeypatch.delenv(_K, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
# --- resolve_config_toml (REQ-048 AC-7/8) ---------------------------------- #
|
||||||
|
def test_resolve_name_default_dir():
|
||||||
|
assert resolve_config_toml("test") == os.path.join("config", "test.toml")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_appends_toml():
|
||||||
|
assert resolve_config_toml("test.toml") == os.path.join("config", "test.toml")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_with_config_path():
|
||||||
|
assert resolve_config_toml("prod", "/etc/trex") == os.path.join("/etc/trex", "prod.toml")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_path_form_passthrough():
|
||||||
|
# AC-8: a slash-bearing value is an explicit path (backward-compat).
|
||||||
|
assert resolve_config_toml("config/test.toml") == "config/test.toml"
|
||||||
|
assert resolve_config_toml("config/test.toml", "/etc/trex") == "config/test.toml"
|
||||||
|
|
||||||
|
|
||||||
|
# --- _load_dotenv config-dir resolution (REQ-010 AC-6/7) ------------------- #
|
||||||
|
def test_dotenv_loaded_from_config_dir_cwd_independent(tmp_path, monkeypatch):
|
||||||
|
cfgdir = tmp_path / "cfg"
|
||||||
|
cfgdir.mkdir()
|
||||||
|
(cfgdir / ".env").write_text(f"{_K}=from_config_dir\n")
|
||||||
|
toml = str(cfgdir / "inst.toml")
|
||||||
|
# chdir somewhere with no .env to prove CWD-independence.
|
||||||
|
other = tmp_path / "elsewhere"
|
||||||
|
other.mkdir()
|
||||||
|
monkeypatch.chdir(other)
|
||||||
|
_load_dotenv(toml)
|
||||||
|
assert os.environ[_K] == "from_config_dir"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dotenv_env_file_wins(tmp_path, monkeypatch):
|
||||||
|
cfgdir = tmp_path / "cfg"
|
||||||
|
cfgdir.mkdir()
|
||||||
|
(cfgdir / ".env").write_text(f"{_K}=from_config_dir\n")
|
||||||
|
explicit = tmp_path / "explicit.env"
|
||||||
|
explicit.write_text(f"{_K}=from_env_file\n")
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
_load_dotenv(str(cfgdir / "inst.toml"), env_file=str(explicit))
|
||||||
|
assert os.environ[_K] == "from_env_file"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dotenv_os_env_takes_precedence(tmp_path, monkeypatch):
|
||||||
|
cfgdir = tmp_path / "cfg"
|
||||||
|
cfgdir.mkdir()
|
||||||
|
(cfgdir / ".env").write_text(f"{_K}=from_config_dir\n")
|
||||||
|
monkeypatch.setenv(_K, "from_os_env")
|
||||||
|
_load_dotenv(str(cfgdir / "inst.toml"))
|
||||||
|
assert os.environ[_K] == "from_os_env" # override=False
|
||||||
|
|
||||||
|
|
||||||
|
def test_dotenv_absent_is_noop(tmp_path):
|
||||||
|
# config dir has no .env -> no crash, key stays unset.
|
||||||
|
_load_dotenv(str(tmp_path / "inst.toml"))
|
||||||
|
assert _K not in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
# --- CLI --config-path instance routing (REQ-048 AC-9) --------------------- #
|
||||||
|
def test_cli_resolve_instance_honors_config_path(tmp_path):
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from trex_core.runtime.cli import _resolve_instance
|
||||||
|
|
||||||
|
cfgdir = tmp_path / "cfg"
|
||||||
|
cfgdir.mkdir()
|
||||||
|
(cfgdir / "inst.toml").write_text('instance_key = "inst-xyz"\n')
|
||||||
|
args = SimpleNamespace(instance=None, config="inst", config_path=str(cfgdir))
|
||||||
|
assert _resolve_instance(args) == "inst-xyz"
|
||||||
@@ -83,7 +83,9 @@ def test_config_required_cli_exits_nonzero():
|
|||||||
|
|
||||||
|
|
||||||
def test_gitignore_excludes_instance_toml():
|
def test_gitignore_excludes_instance_toml():
|
||||||
"""AC-5: .gitignore excludes instance TOML but keeps the example."""
|
"""AC-5 (CHG-20260609-01): .gitignore ignores config/* but keeps example
|
||||||
|
templates (*.example.toml covers instance.example.toml) and .env.example."""
|
||||||
gi = (_REPO / ".gitignore").read_text()
|
gi = (_REPO / ".gitignore").read_text()
|
||||||
assert "/config/*.toml" in gi
|
assert "/config/*" in gi
|
||||||
assert "!/config/instance.example.toml" in gi
|
assert "!/config/*.example.toml" in gi
|
||||||
|
assert "!/config/.env.example" in gi
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ from zoneinfo import ZoneInfo
|
|||||||
|
|
||||||
from trex_core.adapters.messaging import topology
|
from trex_core.adapters.messaging import topology
|
||||||
from trex_core.adapters.portfolio.pause_store import PostgreSQLPauseStore
|
from trex_core.adapters.portfolio.pause_store import PostgreSQLPauseStore
|
||||||
from trex_core.runtime.config import DaemonConfig, DaemonInstanceConfig
|
from trex_core.runtime.config import (
|
||||||
|
DaemonConfig,
|
||||||
|
DaemonInstanceConfig,
|
||||||
|
_load_dotenv,
|
||||||
|
resolve_config_toml,
|
||||||
|
)
|
||||||
|
|
||||||
_DEFAULT_TIMEOUT = 10.0
|
_DEFAULT_TIMEOUT = 10.0
|
||||||
|
|
||||||
@@ -42,7 +47,8 @@ def _resolve_instance(args) -> str:
|
|||||||
if getattr(args, "instance", None):
|
if getattr(args, "instance", None):
|
||||||
return args.instance
|
return args.instance
|
||||||
if getattr(args, "config", None):
|
if getattr(args, "config", None):
|
||||||
return DaemonInstanceConfig.from_toml(args.config).instance_key
|
toml = resolve_config_toml(args.config, getattr(args, "config_path", None))
|
||||||
|
return DaemonInstanceConfig.from_toml(toml).instance_key
|
||||||
return topology.DEFAULT_INSTANCE_KEY
|
return topology.DEFAULT_INSTANCE_KEY
|
||||||
|
|
||||||
|
|
||||||
@@ -248,8 +254,12 @@ def _fail(message: str, as_json: bool) -> None:
|
|||||||
# arg parsing
|
# arg parsing
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
def _add_common(p) -> None:
|
def _add_common(p) -> None:
|
||||||
p.add_argument("--config", help="per-instance TOML (canonical instance routing)")
|
p.add_argument("--config", help="per-instance TOML name (resolved within --config-path) or path")
|
||||||
|
p.add_argument("--config-path", dest="config_path", default=None,
|
||||||
|
help="config directory (default ./config) for --config + .env")
|
||||||
p.add_argument("--instance", help="instance key (shortcut; overrides --config)")
|
p.add_argument("--instance", help="instance key (shortcut; overrides --config)")
|
||||||
|
p.add_argument("--env-file", dest="env_file", default=None,
|
||||||
|
help="explicit .env path; overrides <config-dir>/.env")
|
||||||
p.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
p.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
||||||
p.add_argument("--timeout", type=float, default=_DEFAULT_TIMEOUT)
|
p.add_argument("--timeout", type=float, default=_DEFAULT_TIMEOUT)
|
||||||
|
|
||||||
@@ -313,6 +323,15 @@ def main(argv: list[str] | None = None, *, rpc=None, confirm=None) -> int:
|
|||||||
correlation_id, timeout) -> dict`, `confirm(prompt) -> bool`.
|
correlation_id, timeout) -> dict`, `confirm(prompt) -> bool`.
|
||||||
"""
|
"""
|
||||||
args = _build_parser().parse_args(argv)
|
args = _build_parser().parse_args(argv)
|
||||||
|
# REQ-010 CHG-20260609-01: load the config-dir .env (or --env-file) before
|
||||||
|
# reading env. from_env() also runs the legacy root-.env fallback; the
|
||||||
|
# config-dir load wins (loaded first, override=False).
|
||||||
|
_toml = (
|
||||||
|
resolve_config_toml(args.config, getattr(args, "config_path", None))
|
||||||
|
if getattr(args, "config", None)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
_load_dotenv(_toml, getattr(args, "env_file", None))
|
||||||
cfg = DaemonConfig.from_env()
|
cfg = DaemonConfig.from_env()
|
||||||
confirm = confirm or _default_confirm
|
confirm = confirm or _default_confirm
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,57 @@ from dataclasses import dataclass, replace
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
def _load_dotenv() -> None:
|
DEFAULT_CONFIG_DIR = "config"
|
||||||
"""Best-effort load of a .env file into os.environ (REQ-010).
|
|
||||||
|
|
||||||
Real OS environment variables take precedence over .env (override=False),
|
|
||||||
matching 12-factor expectations. If python-dotenv is not installed or no
|
def resolve_config_toml(name: str, config_path: Optional[str] = None) -> str:
|
||||||
.env exists, this is a no-op (no regression).
|
"""Resolve `--config <name>` within a config directory (REQ-048 CHG-20260609-01).
|
||||||
|
|
||||||
|
config_dir = `--config-path` if given, else `./config`. `<name>` selects the
|
||||||
|
TOML inside it; a missing `.toml` extension is appended.
|
||||||
|
|
||||||
|
Backward-compat: if `name` contains a path separator (e.g. `config/test.toml`),
|
||||||
|
it is treated as an explicit path and used as-is so existing ops scripts that
|
||||||
|
pass a path keep working.
|
||||||
|
|
||||||
|
resolve_config_toml("test") -> config/test.toml
|
||||||
|
resolve_config_toml("test.toml") -> config/test.toml
|
||||||
|
resolve_config_toml("prod", "/etc/trex") -> /etc/trex/prod.toml
|
||||||
|
resolve_config_toml("config/test.toml") -> config/test.toml (path-form)
|
||||||
|
"""
|
||||||
|
if os.sep in name or (os.altsep and os.altsep in name):
|
||||||
|
return name # explicit path (backward-compat)
|
||||||
|
config_dir = config_path or DEFAULT_CONFIG_DIR
|
||||||
|
filename = name if name.endswith(".toml") else f"{name}.toml"
|
||||||
|
return os.path.join(config_dir, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_dotenv(config_path: Optional[str] = None, env_file: Optional[str] = None) -> None:
|
||||||
|
"""Best-effort load of a .env file into os.environ (REQ-010 / CHG-20260609-01).
|
||||||
|
|
||||||
|
Resolution (trex-orb-v1 aligned, deterministic & CWD-independent given a
|
||||||
|
config_path): `env_file` (explicit) wins, else `<config_dir>/.env` next to the
|
||||||
|
TOML. With neither (standalone `from_env()`), fall back to legacy CWD-relative
|
||||||
|
`find_dotenv` so existing root `.env` setups keep working (backward-compat).
|
||||||
|
|
||||||
|
Real OS env vars always take precedence (`override=False`). python-dotenv
|
||||||
|
missing or the resolved file absent => no-op (no regression).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from dotenv import find_dotenv, load_dotenv
|
from dotenv import load_dotenv
|
||||||
except ImportError: # pragma: no cover - optional at runtime
|
except ImportError: # pragma: no cover - optional at runtime
|
||||||
return
|
return
|
||||||
|
if env_file:
|
||||||
|
if os.path.isfile(env_file):
|
||||||
|
load_dotenv(env_file, override=False)
|
||||||
|
return
|
||||||
|
if config_path:
|
||||||
|
path = os.path.join(os.path.dirname(os.path.abspath(config_path)), ".env")
|
||||||
|
if os.path.isfile(path):
|
||||||
|
load_dotenv(path, override=False)
|
||||||
|
return
|
||||||
|
# No config context: legacy CWD-relative discovery (backward-compat fallback).
|
||||||
|
from dotenv import find_dotenv
|
||||||
load_dotenv(find_dotenv(usecwd=True), override=False)
|
load_dotenv(find_dotenv(usecwd=True), override=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -252,13 +292,18 @@ class DaemonConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_config(toml_path: str) -> "DaemonConfig":
|
def from_config(toml_path: str, env_file: Optional[str] = None) -> "DaemonConfig":
|
||||||
"""Build config from env + a per-instance TOML file (REQ-048 / BAT-013).
|
"""Build config from env + a per-instance TOML file (REQ-048 / BAT-013).
|
||||||
|
|
||||||
`instance_key` and `portfolio_hub_token` come from TOML (not .env).
|
`instance_key` and `portfolio_hub_token` come from TOML (not .env).
|
||||||
TOML `portfolio_hub_token` overrides the (env-derived) PortfolioHubConfig
|
TOML `portfolio_hub_token` overrides the (env-derived) PortfolioHubConfig
|
||||||
token when a portfolio-hub base_url is configured.
|
token when a portfolio-hub base_url is configured.
|
||||||
|
|
||||||
|
REQ-010 CHG-20260609-01: the config-dir `.env` (next to the TOML, or
|
||||||
|
`--env-file`) is loaded first so it wins over the legacy root `.env`
|
||||||
|
fallback loaded inside `from_env()` (both `override=False`).
|
||||||
"""
|
"""
|
||||||
|
_load_dotenv(toml_path, env_file)
|
||||||
base = DaemonConfig.from_env()
|
base = DaemonConfig.from_env()
|
||||||
inst = DaemonInstanceConfig.from_toml(toml_path)
|
inst = DaemonInstanceConfig.from_toml(toml_path)
|
||||||
pf = base.portfolio_hub
|
pf = base.portfolio_hub
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ from trex_core.application.preallocation_scheduler import DailyPreallocationSche
|
|||||||
from trex_core.application.snapshot_scheduler import SnapshotScheduler
|
from trex_core.application.snapshot_scheduler import SnapshotScheduler
|
||||||
from trex_core.application.watchdog import WatchdogStore
|
from trex_core.application.watchdog import WatchdogStore
|
||||||
from trex_core.domain.slot import SlotService
|
from trex_core.domain.slot import SlotService
|
||||||
from trex_core.runtime.config import DaemonConfig
|
from trex_core.runtime.config import DaemonConfig, resolve_config_toml
|
||||||
|
|
||||||
_log = logging.getLogger(__name__)
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -375,11 +375,21 @@ def main() -> None: # pragma: no cover - process entrypoint
|
|||||||
parser = argparse.ArgumentParser(prog="trex-core-daemon")
|
parser = argparse.ArgumentParser(prog="trex-core-daemon")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config", required=True,
|
"--config", required=True,
|
||||||
help="per-instance TOML config path (instance_key, portfolio_hub_token). "
|
help="per-instance TOML name (resolved within --config-path) or explicit "
|
||||||
"Required — no default (REQ-048 / dcy-009 OQ-2).",
|
"path. Required — no default (REQ-048 / dcy-009 OQ-2).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config-path", default=None,
|
||||||
|
help="config directory (default ./config) for --config name resolution "
|
||||||
|
"and the matching .env (REQ-048/010 CHG-20260609-01).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--env-file", default=None,
|
||||||
|
help="explicit .env path; overrides <config-dir>/.env (REQ-010 CHG-20260609-01).",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
config = DaemonConfig.from_config(args.config)
|
toml_path = resolve_config_toml(args.config, args.config_path)
|
||||||
|
config = DaemonConfig.from_config(toml_path, env_file=args.env_file)
|
||||||
Daemon(config).run()
|
Daemon(config).run()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user