docs(srs): REQ-027 CHG-04 — correct portfolio-hub read contract for release

Align REQ-027 and BAT-008 verification/design with the live portfolio-hub
API discovered during release prep: whoami via /v1/user_info, {portfolios}
envelope, client-side managed_by_daemon_id filter, http:// scheme default,
and strategy fields deferred (None/{}). Records the re-verification evidence
(new contract tests + live e2e) so BAT-008 can bundle into the first release
with non-stale verification. REQ-027 stays Implemented.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 10:41:30 +09:00
co-authored by Claude Opus 4.7
parent 515af7a068
commit e24de29335
4 changed files with 67 additions and 14 deletions
@@ -27,7 +27,7 @@
2. **masking 합성 순서**: `pending(slot 상태) OR warning(정합 실패) OR pause(수동)` → 셋 중 하나라도 true 면 마스킹. 적용은 view 집합 산출 후 균일 적용. 우선순위: 슬롯 overlay > portfolio-hub 보유 여부. 2. **masking 합성 순서**: `pending(slot 상태) OR warning(정합 실패) OR pause(수동)` → 셋 중 하나라도 true 면 마스킹. 적용은 view 집합 산출 후 균일 적용. 우선순위: 슬롯 overlay > portfolio-hub 보유 여부.
3. **daemon_id 도출**: `PortfolioHubReadPort` 초기화 시 portfolio-hub `GET /v1/auth/whoami` 1회 호출 → `daemon_id` 추출·캐시. `TREX_PORTFOLIO_HUB_TOKEN` 자격증명 재사용(REQ-027 A-1). 3. **daemon_id 도출**: `PortfolioHubReadPort` 초기화 시 portfolio-hub `GET /v1/user_info` 1회 호출 → 응답 `daemon_id` 추출·캐시. `TREX_PORTFOLIO_HUB_TOKEN` 자격증명 재사용(REQ-027 A-1). (정정: REQ-027 CHG-04 — `/v1/auth/whoami` 부재로 `/v1/user_info` 로 변경)
4. **strategy_key 필터**: 요청 payload 의 `strategy_key`(str) 를 자산의 `active_strategy_id` 와 **대소문자 구분 정확 일치**(`==`)로 필터링. 생략 시 필터 없음. 4. **strategy_key 필터**: 요청 payload 의 `strategy_key`(str) 를 자산의 `active_strategy_id` 와 **대소문자 구분 정확 일치**(`==`)로 필터링. 생략 시 필터 없음.
@@ -5,11 +5,11 @@
## Acceptance Mapping ## Acceptance Mapping
- REQ-027: - REQ-027 (계약 정정 CHG-04 반영):
- AC-1: stub + HTTP MockTransport 통합테스트에서 `PortfolioItem` 항목당 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 필드 + `price` 부재 확인. - AC-1: HTTP MockTransport 통합테스트에서 `{portfolios:[…]}` envelope 파싱 + `managed_by_daemon_id` 클라이언트측 필터 + `PortfolioItem` 항목 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 필드 + `price` 부재 확인. 실 list 응답에 전략 필드 없으면 None/`{}` 기본값.
- AC-2: 결정론 stub 가 네트워크 없이 고정 universe 반환 확인. - AC-2: 결정론 stub 가 네트워크 없이 고정 universe 반환 확인.
- AC-3: 도메인 import 스윕(`tests/domain/test_domain_import_sweep.py`)이 `pika`/`httpx`/`adapters.*` 0 import 회귀 가드. - AC-3: 도메인 import 스윕(`tests/domain/test_domain_import_sweep.py`)이 `pika`/`httpx`/`adapters.*` 0 import 회귀 가드.
- AC-4: HTTP MockTransport 가 `/v1/auth/whoami` 호출 + Bearer 토큰 헤더 + `daemon_id` 도출 확인. `TREX_DAEMON_ID` env 1차 출처 없음(코드 review). - AC-4: HTTP MockTransport 가 `/v1/user_info` 호출 + Bearer 토큰 헤더 + `daemon_id` 도출 확인. 스킴 없는 base URL → `http://` 기본 적용. `TREX_DAEMON_ID` env 1차 출처 없음(코드 review).
- REQ-028: - REQ-028:
- AC-1: envelope 응답이 정확히 `{assets, view, available_slots, degraded, reason, message}` 키 집합 + `assets` 가 항상 list(null 금지) 확인. - AC-1: envelope 응답이 정확히 `{assets, view, available_slots, degraded, reason, message}` 키 집합 + `assets` 가 항상 list(null 금지) 확인.
@@ -48,10 +48,10 @@
### Test ### Test
- 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" TREX_POSTGRESQL_TEST_DSN=$(grep TREX_POSTGRESQL_DSN .env | cut -d= -f2-) .venv/bin/python -m pytest -q` - 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" TREX_POSTGRESQL_TEST_DSN=$(grep TREX_POSTGRESQL_DSN .env | cut -d= -f2-) .venv/bin/python -m pytest -q`
- 결과: `185 passed, 1 skipped` (KIS sandbox token rate-limit 1건만 skip; 그 외 PG/RabbitMQ 게이트 모두 통과). - 결과: `185 passed, 1 skipped` (KIS sandbox token rate-limit 1건만 skip; 그 외 PG/RabbitMQ 게이트 모두 통과).
- REQ-027 AC-1: `tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_scoped_by_managed_by_daemon_id` + `test_stub_read.py::test_stub_returns_fixed_universe_without_network`. - REQ-027 AC-1: `tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_parses_envelope_and_filters_by_daemon` + `::test_get_portfolios_includes_items_without_daemon_field` + `test_stub_read.py::test_stub_returns_fixed_universe_without_network`.
- REQ-027 AC-2: `tests/adapters/portfolio/test_stub_read.py::test_stub_returns_fixed_universe_without_network` + `test_stub_items_are_mutable_for_tests`. - REQ-027 AC-2: `tests/adapters/portfolio/test_stub_read.py::test_stub_returns_fixed_universe_without_network` + `test_stub_items_are_mutable_for_tests`.
- REQ-027 AC-3: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` (BAT-007 부터 유지, `httpx`/`pika`/`adapters.*` 금지 prefix 회귀 가드). - REQ-027 AC-3: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` (BAT-007 부터 유지, `httpx`/`pika`/`adapters.*` 금지 prefix 회귀 가드).
- REQ-027 AC-4: `tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id` (URL/Bearer 헤더/응답 디코드 + daemon_id 추출). - REQ-027 AC-4: `tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id` (`/v1/user_info` URL/Bearer 헤더/응답 디코드 + daemon_id 추출) + `::test_base_url_without_scheme_defaults_to_http`.
- REQ-028 AC-1: `tests/application/test_snapshot_handler.py::test_envelope_shape_returned` + `tests/application/test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null`. - REQ-028 AC-1: `tests/application/test_snapshot_handler.py::test_envelope_shape_returned` + `tests/application/test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null`.
- REQ-028 AC-2: `tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials` + `tests/application/test_snapshot_selection.py::test_degraded_signal_in_envelope`. 실 daemon 운영 검증에서 portfolio-hub 부재 환경에서 같은 응답 발생 확인. - REQ-028 AC-2: `tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials` + `tests/application/test_snapshot_selection.py::test_degraded_signal_in_envelope`. 실 daemon 운영 검증에서 portfolio-hub 부재 환경에서 같은 응답 발생 확인.
- REQ-028 AC-3: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`. - REQ-028 AC-3: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`.
@@ -89,6 +89,15 @@
- `.venv/bin/ruff check trex_core tests` → `All checks passed!`. - `.venv/bin/ruff check trex_core tests` → `All checks passed!`.
- 실 RabbitMQ + PostgreSQL evidence: `127.0.0.1:5672` admin/admin + 로컬 PG 로 daemon 기동 후 snapshot RPC (view=all/buyable/held + strategy_key), pause/resume RPC, 응답 envelope 키/필드 검증 모두 통과. portfolio-hub 부재 환경에서 `degraded=true reason=portfolio_hub_unavailable` 정상 노출. - 실 RabbitMQ + PostgreSQL evidence: `127.0.0.1:5672` admin/admin + 로컬 PG 로 daemon 기동 후 snapshot RPC (view=all/buyable/held + strategy_key), pause/resume RPC, 응답 envelope 키/필드 검증 모두 통과. portfolio-hub 부재 환경에서 `degraded=true reason=portfolio_hub_unavailable` 정상 노출.
### Re-verification Addendum (2026-06-06, REQ-027 CHG-04 — release 선행 정리)
- 사유: release-candidate 확정 이후 실 portfolio-hub API 실측으로 read 계약 불일치 발견 → 어댑터/테스트 정정(commit 43bad24). 본 batch verification 을 정정 계약 기준으로 재확인.
- 정정 요지: whoami `/v1/auth/whoami`→`/v1/user_info`; list envelope `{items}`→`{portfolios,total}`; 토큰 스코프 → 클라이언트측 `managed_by_daemon_id` 필터; base URL 스킴 누락 시 `http://` 보정; 실 `PortfolioItem` 전략 필드 부재 → None/`{}`(이월).
- 재실행 결과:
- `.venv/bin/python -m pytest tests/adapters/portfolio/test_hub_read.py -q` → `6 passed`.
- `.venv/bin/python -m pytest -q` (infra env 미설정 invocation) → `165 passed, 23 skipped` (PG/RabbitMQ/KIS 게이트 skip). `ruff check trex_core tests` → `All checks passed!`.
- 실 portfolio-hub e2e (portfolio-hub#9 user_info 500 수정 반영 후): `PortfolioHubReadAdapter.whoami()`→`daemon_id=b5084542-…`, `get_portfolios()`→삼성전자(005930) 1건, `SnapshotScheduler.run_once()`→`degraded=None`, universe 1건, warning 없음.
- 영향 결론: 현재 코드가 실 계약과 일치하고 모든 AC evidence 가 정정 테스트명으로 재연결됨. REQ-027 Implemented 유지, BAT-008 release-candidate 유지. Ready for Release 변동 없음.
## Result ## Result
- Ready for Release: true - Ready for Release: true
- Blocking Issues: - Blocking Issues:
@@ -9,16 +9,16 @@
- 전략 모듈에 제공할 종목 스냅샷의 원천인 portfolio-hub universe(자신이 관리하는 portfolio 집합)를 trex-core 가 read 할 경로를 만든다. dcy-004 write 어댑터/자격증명을 재사용하고 도메인 순수성을 유지한다. - 전략 모듈에 제공할 종목 스냅샷의 원천인 portfolio-hub universe(자신이 관리하는 portfolio 집합)를 trex-core 가 read 할 경로를 만든다. dcy-004 write 어댑터/자격증명을 재사용하고 도메인 순수성을 유지한다.
## Requirement ## Requirement
- `PortfolioHubReadPort.get_portfolios(daemon_id)` application 포트 + portfolio-hub read 어댑터(`managed_by_daemon_id` 스코프 조회, portfolio-hub `list_portfolios_v1`) + 결정론 stub 를 제공한다. - `PortfolioHubReadPort.get_portfolios(daemon_id)` application 포트 + portfolio-hub read 어댑터(portfolio-hub `GET /v1/portfolios`, `list_portfolios_v1`) + 결정론 stub 를 제공한다. `/v1/portfolios` 는 토큰 스코프 조회라 `managed_by_daemon_id` 쿼리 파라미터가 없으므로, 어댑터는 응답 항목의 `managed_by_daemon_id == daemon_id` 로 **클라이언트측 필터**한다. (정정: CHG-04)
- 어댑터는 dcy-004 의 portfolio-hub 자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 재사용한다. - 어댑터는 dcy-004 의 portfolio-hub 자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 재사용한다. base URL 에 스킴이 없으면 `http://` 를 기본 적용한다.
- `daemon_id` 는 portfolio-hub 자격증명/인증 토큰에서 도출한다(단일 진실원). 별도 `TREX_DAEMON_ID` 설정값을 1차 출처로 두지 않으며, 필요 시 whoami 1회로 도출·캐시한다. - `daemon_id` 는 portfolio-hub 자격증명/인증 토큰에서 도출한다(단일 진실원). 별도 `TREX_DAEMON_ID` 설정값을 1차 출처로 두지 않으며, `GET /v1/user_info` 응답의 `daemon_id` 로 1회 도출·캐시한다. (정정: CHG-04, `/v1/auth/whoami` 부재)
- 각 universe 항목은 symbol, asset_id(=portfolio_id), cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. 전략파라미터는 trex-core 가 해석하지 않고 통과시킨다. - 각 universe 항목은 symbol, asset_id(=portfolio_id), cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. 전략파라미터는 trex-core 가 해석하지 않고 통과시킨다. 단, 실 `/v1/portfolios`(`PortfolioItem`)는 `active_strategy_id`/`strategy_params` 를 제공하지 않아 두 필드는 None/`{}` 로 채워진다(실데이터 채움 이월 — CHG-04).
## Acceptance Criteria ## Acceptance Criteria
1. Given daemon 자격증명/토큰, When `get_portfolios` 를 호출, Then `managed_by_daemon_id == 토큰 daemon_id` 인 portfolio 집합을 항목당 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 형태로 반환하고 가격 필드는 없다. 1. Given daemon 토큰, When `get_portfolios` 를 호출, Then 토큰 스코프 응답을 `managed_by_daemon_id == daemon_id` 로 필터해 portfolio 집합을 항목당 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 형태로 반환하고 가격 필드는 없다. (실 list 응답에 전략 필드가 없으면 None/`{}` — CHG-04)
2. Given 결정론 stub 어댑터, When 호출, Then 네트워크 없이 고정된 universe 를 반환한다. 2. Given 결정론 stub 어댑터, When 호출, Then 네트워크 없이 고정된 universe 를 반환한다.
3. Given import 스윕, When `trex_core/domain` 을 검사, Then portfolio-hub/네트워크 의존이 0 이다(NFR-1). 3. Given import 스윕, When `trex_core/domain` 을 검사, Then portfolio-hub/네트워크 의존이 0 이다(NFR-1).
4. Given `TREX_DAEMON_ID` 미설정, When daemon 기동·universe 조회, Then 토큰에서 도출한 daemon_id 로 동작한다(OQ-7). 4. Given `TREX_DAEMON_ID` 미설정, When daemon 기동·universe 조회, Then `GET /v1/user_info` 응답에서 도출한 daemon_id 로 동작한다(OQ-7, CHG-04).
## Impacted Area ## Impacted Area
- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/interface-contract.md - Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/interface-contract.md
@@ -26,7 +26,8 @@
- Tests: stub read 통합테스트, 도메인 import 스윕, daemon_id 토큰 도출 - Tests: stub read 통합테스트, 도메인 import 스윕, daemon_id 토큰 도출
## Notes ## Notes
- 가정: portfolio-hub `list_portfolios_v1`(`GET`)이 `managed_by_daemon_id=auth.daemon_id` 로 스코프 조회를 제공하고 `PortfolioItem` 에 cash_balance/asset_quantity 포함(confirm-discovery 에서 검증). 전략파라미터는 portfolio-hub `strategy_params` JSONB(dcy-005 A-5). - (정정, CHG-04) 라이브 portfolio-hub 실측 계약: `GET /v1/user_info` → `UserInfoResponse{daemon_id,…}`; `GET /v1/portfolios` → `PortfolioListResponse{portfolios:[PortfolioItem], total}` (토큰 스코프, `managed_by_daemon_id` 쿼리 없음 → 클라이언트측 필터). `PortfolioItem` 은 cash_balance/asset_quantity/managed_by_daemon_id 는 포함하나 `active_strategy_id`/`strategy_params` 는 미포함(detail 의 `strategy_set` 에만 존재). 구 가정(`/v1/auth/whoami`, `{items:[…]}`, `managed_by_daemon_id` 쿼리 스코프)은 폐기.
- 전략 필드 부재는 단일 전략(orb-v1, `strategy_key` 미사용) 단계에서 무해(REQ-029 CHG-04). 2번째 전략 도입 시 strategy_set 보강 또는 별도 change-req 로 해소.
- NFR-1(도메인 순수성)을 본 REQ 의 AC-3 로 흡수한다. - NFR-1(도메인 순수성)을 본 REQ 의 AC-3 로 흡수한다.
## Change Log ## Change Log
@@ -133,3 +134,46 @@
- AC-2: tests/adapters/portfolio/test_stub_read.py 전체. - AC-2: tests/adapters/portfolio/test_stub_read.py 전체.
- AC-3: tests/domain/test_domain_import_sweep.py. - AC-3: tests/domain/test_domain_import_sweep.py.
- AC-4: tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id. - AC-4: tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id.
### CHG-20260606-04
- Date: 2026-06-06
- Author: Justin Ko
#### Change Summary
- 실 portfolio-hub API 와의 계약 불일치를 정정: whoami 엔드포인트는 `/v1/auth/whoami`(부재) → **`GET /v1/user_info`**, list 응답 envelope 는 `{items:[…]}` → **`{portfolios:[…], total}`**, `/v1/portfolios` 는 토큰 스코프라 `managed_by_daemon_id` 쿼리 파라미터가 없어 **클라이언트측 필터**로 전환. 실 `PortfolioItem` 에 `active_strategy_id`/`strategy_params` 가 없어 None/`{}` 기본값으로 통과(전략 필드 실데이터 채움은 이월). 어댑터/테스트는 이미 정정 구현·재검증됨.
#### Intent
- 가정 기반(confirm-discovery 미검증)으로 작성됐던 read 계약을 라이브 API 실측에 맞춰 정정하고, 정정 구현의 재검증 evidence(신규 테스트 + 실 e2e)를 연결한다.
#### Acceptance Criteria Delta
- Added: none
- Updated:
- AC-1: list 조회는 토큰 스코프 + 클라이언트측 `managed_by_daemon_id` 필터로 동작. 반환 항목은 동일 필드 형태(가격 없음)를 유지하되, 실 `/v1/portfolios`(`PortfolioItem`)가 `active_strategy_id`/`strategy_params` 를 제공하지 않아 해당 두 필드는 None/`{}` 로 채워진다(실데이터 채움은 이월 — REQ-029 CHG-04, detail `strategy_set` 보강 또는 별도 change-req).
- AC-4: 토큰 daemon_id 도출은 `GET /v1/user_info` 응답의 `daemon_id` 로 한다(`/v1/auth/whoami` 아님).
- Removed: none
#### Impacted Area Delta
- Docs:
- 본 REQ Requirement/AC-1/AC-4/Notes 정정
- BAT-008 design.md/verification.md 계약 정정 반영(같은 변경 묶음)
- Modules:
- trex_core/adapters/portfolio/hub_read.py (정정 구현 완료, commit 43bad24)
- Tests:
- tests/adapters/portfolio/test_hub_read.py (실 계약으로 갱신, commit 43bad24)
#### Delivery Trace Delta
- Batches:
- BAT-008 (release-candidate) — 계약 정정 반영, verification 재확인 대상
- Releases:
- none (첫 릴리즈 묶음 대상)
#### Revalidation Impact
- Reverification Needed: yes — 완료. 정정 어댑터에 대해 신규 contract 테스트 통과 + 실 portfolio-hub e2e(whoami→get_portfolios→scheduler run_once `degraded=false`, 삼성전자 1건 수신) 확인.
- Existing Implementation Invalidated: no — 현재 코드가 실 계약과 일치(정정 후). 무효화된 것은 구 verification 산출물의 테스트명/엔드포인트 기술뿐이며 BAT-008 verification 갱신으로 동기화.
- Status Recommendation: Implemented 유지.
- Follow-up Action: 전략 필드 실데이터 채움(2번째 전략 도입 시) 재검토. portfolio-hub#9(user_info 500) 수정 완료로 daemon_id 도출 가능.
#### Validation Plan
- AC-1: tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_parses_envelope_and_filters_by_daemon + ::test_get_portfolios_includes_items_without_daemon_field + test_stub_read.py.
- AC-4: ::test_whoami_returns_token_daemon_id (`/v1/user_info` 단언) + ::test_base_url_without_scheme_defaults_to_http.
- 실 e2e: SnapshotScheduler.run_once() against live portfolio-hub → degraded=None, universe 1건.
+1 -1
View File
@@ -117,8 +117,8 @@
## Recent Change Log Summary ## Recent Change Log Summary
- 2026-06-06 :: REQ-027 CHG-04 — 실 portfolio-hub 계약 정정(whoami `/v1/user_info`, `{portfolios}` envelope, 클라이언트측 daemon 필터, 전략 필드 None/`{}` 이월). 정정 구현 재검증 완료·Implemented 유지. (change-req)
- 2026-06-06 :: REQ-029 CHG-04 — orb-v1 소비 참고 사항 추가(strategy_key 없이 view=buyable, 전략 필드 부재 무해). 문서 전용·Implemented 유지. (change-req) - 2026-06-06 :: REQ-029 CHG-04 — orb-v1 소비 참고 사항 추가(strategy_key 없이 view=buyable, 전략 필드 부재 무해). 문서 전용·Implemented 유지. (change-req)
- 2026-06-06 :: REQ-027~029, REQ-031~033 Approved → Implemented — BAT-008 release-candidate verification evidence 기반. REQ-030 은 AC-1 evidence 확보·AC-2/AC-3 portfolio-hub#8 배포 대기로 Approved 유지. (confirm-req-implemented) - 2026-06-06 :: REQ-027~029, REQ-031~033 Approved → Implemented — BAT-008 release-candidate verification evidence 기반. REQ-030 은 AC-1 evidence 확보·AC-2/AC-3 portfolio-hub#8 배포 대기로 Approved 유지. (confirm-req-implemented)
- 2026-06-06 :: REQ-027~033 Proposed → Approved — dcy-005 confirmed, 전체 승인 게이트 통과. REQ-030 옵션 D 구현은 portfolio-hub#8 배포 후 착수. (confirm-req) - 2026-06-06 :: REQ-027~033 Proposed → Approved — dcy-005 confirmed, 전체 승인 게이트 통과. REQ-030 옵션 D 구현은 portfolio-hub#8 배포 후 착수. (confirm-req)
- 2026-06-06 :: REQ-027~033 최초 생성(Proposed) — dcy-005 FR-1~FR-9 분해(NFR-1 → REQ-027 흡수, NFR-2 → REQ-030 흡수, NFR-3 → REQ-028 흡수). (draft-req) - 2026-06-06 :: REQ-027~033 최초 생성(Proposed) — dcy-005 FR-1~FR-9 분해(NFR-1 → REQ-027 흡수, NFR-2 → REQ-030 흡수, NFR-3 → REQ-028 흡수). (draft-req)
- 2026-06-05 :: REQ-021~026 Approved → Implemented — BAT-007 release-candidate verification evidence 기반. (confirm-req-implemented)