feat: apply rel-001 feedback cleanup

This commit is contained in:
2026-06-05 13:57:46 +09:00
parent 84de34c301
commit 2470c0a00c
28 changed files with 992 additions and 32 deletions
@@ -0,0 +1,126 @@
# REQ-034: LoginRequest 추가 필드 거부 enforcement (session no_expiry 정책 정합)
- Status: Implemented
- Type: Interface
- Priority: Medium
- Owner: Justin Ko
## Intent
- REQ-021 정책 본문이 "session에서 `no_expiry` 시도 → HTTP 422 또는 ValueError 거부"로 명시하지만, 현재 `LoginRequest` 스키마([app/schemas/auth.py:13-23](../../app/schemas/auth.py#L13-L23))가 Pydantic 기본(`extra="ignore"`)이라 외부에서 `no_expiry: true`를 추가해 보내도 silently 무시된다.
- `LoginRequest`에 `extra="forbid"` 적용으로 정책 명문과 코드 동작을 일치시켜 audit 명확성을 확보한다.
## Requirement
- `app/schemas/auth.py:LoginRequest`에 `model_config = ConfigDict(extra="forbid")`를 적용한다.
- 외부 client가 LoginRequest에 정의되지 않은 필드(`no_expiry` 포함, 기타 임의 필드)를 전송하면 FastAPI/Pydantic이 HTTP 422로 거부한다.
- `AuthService.login`([app/services/auth_service.py:47-65](../../app/services/auth_service.py#L47-L65))의 동작은 변경하지 않는다(정책 enforcement는 스키마 단계에서 처리).
- 기존 정상 client(username/password 2필드만 전송)는 영향 없음을 회귀 테스트로 확인한다.
## Acceptance Criteria
1. Given valid username/password, When `POST /auth/login` body `{"username": "...", "password": "..."}` 호출, Then HTTP 200 + 정상 TokenResponse 반환(기존 동작 회귀).
2. Given valid username/password + 추가 필드, When `POST /auth/login` body `{"username": "...", "password": "...", "no_expiry": true}` 호출, Then HTTP 422 응답 + Pydantic validation error detail에 `extra_forbidden` 표기.
3. Given valid username/password + 임의 추가 필드, When `POST /auth/login` body `{"username": "...", "password": "...", "arbitrary_field": "x"}` 호출, Then HTTP 422 응답 (extra="forbid" 일반 동작 회귀).
4. Given codebase grep `grep -n "extra.*forbid" app/schemas/auth.py`, When 본 REQ 구현 후, Then `LoginRequest` 정의 부근에 매치된다.
5. Given 본 REQ 구현 후 전체 pytest 회귀, When 실행, Then 기존 통과 케이스 모두 유지.
## Impacted Area
- Docs:
- (선택) `docs/srs/Documentation/req-021_token-expiry-and-rotation-policy.md` Notes에 "AC-4 enforcement는 REQ-034로 분리 구현" 추가 (cross-reference)
- Modules:
- `app/schemas/auth.py` — `LoginRequest.model_config = ConfigDict(extra="forbid")` 추가
- Tests:
- 신규 `tests/test_auth.py::test_login_rejects_no_expiry_extra_field` — AC-2 검증
- 신규 `tests/test_auth.py::test_login_rejects_arbitrary_extra_field` — AC-3 검증 (또는 AC-2와 단일 케이스로 합침)
## Notes
- 본 REQ는 dcy-005 FR-2 산출물. REQ-021 follow-up.
- `extra="forbid"`는 `LoginRequest` 전체에 추가 필드 거부 효과. 기존 정상 client는 username/password만 전송하므로 영향 없음. 본 워크스페이스는 개발용이라 외부 monitoring 영향 무.
- 본 REQ는 별도 REL-002로 분리 배포(dcy-005 OQ-3 결정, 2026-06-01).
- Source: dcy-005 FR-2, REQ-021 AC-5 follow-up.
## Change Log
### CHG-20260601-03
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Approved → Implemented
#### Intent
- BAT-015 confirm-batch-verification 완료(2026-06-05). AC-1~5 모두 PASSED. `LoginRequest` extra="forbid" 적용 + pytest 422 회귀 2건 신규 + 로컬 smoke 422 + extra_forbidden 확인.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- BAT-015 (release-candidate)
- Releases:
- REL-002 (예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: none
### CHG-20260601-02
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Proposed → Approved
#### Intent
- 승인 게이트 통과: 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 모두 충족, AC-1~5 구체성 확인 완료, blocker 없음.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-approved
- Follow-up Action: batch 계획 편입 (BAT-015 후보)
### CHG-20260601-01
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-005 confirm-discovery 후 draft-req 실행. dcy-005 FR-2 산출물.
#### Intent
- REQ-021 정책 본문과 LoginRequest 코드 동작 일치 (session no_expiry 거부 enforcement).
#### Acceptance Criteria Delta
- Added:
- AC-1~5 최초 작성
#### Impacted Area Delta
- Docs: (선택) docs/srs/Documentation/req-021_*.md cross-reference — 최초 지정
- Modules: app/schemas/auth.py — 최초 지정
- Tests: tests/test_auth.py — 최초 지정
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입
#### Validation Plan
- pytest TestClient로 AC-1~3 검증; grep으로 AC-4 검증
@@ -0,0 +1,128 @@
# REQ-035: GET /health 응답에 db_enabled 필드 추가
- Status: Implemented
- Type: Interface
- Priority: Medium
- Owner: Justin Ko
## Intent
- 현재 `HealthResponse` 모델([app/api/health.py:17-21](../../app/api/health.py#L17-L21))이 `status`, `service`, `version`, `checked_at` 4필드만 노출하여 외부 health probe가 DB 사용 설정 상태를 직접 관찰할 수 없다.
- REL-001 verification 체크리스트의 "GET /health → HTTP 200 + `db_enabled: true`" 요구사항과 응답 모델 gap을 닫는다.
- 본 필드는 환경변수 `PORTFOLIOHUB_DB_ENABLED` 설정값을 노출하는 것이며 actual DB connectivity ping이 아님을 baseline 문서(`docs/runtime-flows.md`)에 명문화한다.
## Requirement
- `HealthResponse` 모델에 `db_enabled: bool` 필드를 추가한다.
- `healthcheck()` 핸들러가 `settings.db_enabled`(환경변수 `PORTFOLIOHUB_DB_ENABLED` 설정값)를 `db_enabled` 필드에 채워서 응답한다.
- `docs/runtime-flows.md`에 "GET /health 응답의 `db_enabled`는 환경변수 설정값 노출이며 actual DB connectivity ping이 아니다. production-grade readiness probe는 별도 `/readiness` endpoint 분리로 후속 처리한다"는 의미 명문화 노트를 추가한다.
- `docs/interface-contract.md`에 /health 응답 schema의 `db_enabled` 필드 명세를 추가한다.
## Acceptance Criteria
1. Given PORTFOLIOHUB_DB_ENABLED=true 환경, When `GET /health` 호출, Then HTTP 200 응답 본문에 `"db_enabled": true` 필드가 포함된다.
2. Given PORTFOLIOHUB_DB_ENABLED=false 환경, When `GET /health` 호출, Then HTTP 200 응답 본문에 `"db_enabled": false` 필드가 포함된다.
3. Given 응답 본문, When `status`, `service`, `version`, `checked_at`, `db_enabled` 5필드 확인, Then 모두 노출되고 기존 4필드 동작은 회귀 통과한다.
4. Given grep `grep -n "db_enabled" docs/runtime-flows.md`, When 실행, Then "설정값 노출, actual ping 아님" 의미 노트가 매치된다.
5. Given grep `grep -n "/health.*db_enabled\|db_enabled.*health" docs/interface-contract.md`, When 실행, Then /health 응답 schema에 `db_enabled` 필드 명세가 매치된다.
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — /health probe flow 섹션에 "db_enabled는 설정값 노출, actual ping 아님" 의미 노트 추가 (baseline 문서 갱신)
- `docs/interface-contract.md` — /health 응답 schema에 `db_enabled: bool` 필드 추가
- Modules:
- `app/api/health.py` — `HealthResponse`에 `db_enabled: bool` 필드 추가 + `healthcheck()` 핸들러가 `settings.db_enabled` 채움
- Tests:
- 신규 `tests/test_app.py::test_health_exposes_db_enabled` — AC-1 검증 (PORTFOLIOHUB_DB_ENABLED=true 환경)
- (선택) 추가 케이스 — AC-2 검증 (PORTFOLIOHUB_DB_ENABLED=false 환경)
## Notes
- 본 REQ는 dcy-005 FR-3 산출물. REL-001 verification gap을 닫는 정합화.
- `db_enabled` 값은 단순 설정값 노출이며 actual DB connectivity 보장이 아니다. production-grade readiness probe(SELECT 1 ping 등)는 본 REQ 범위 밖이며 별도 후속 Discovery + `/readiness` endpoint 분리로 처리.
- 본 REQ는 별도 REL-002로 분리 배포(dcy-005 OQ-3 결정, 2026-06-01). REL-001은 현 상태(체크리스트 `db_enabled` 미노출 [~] 부분 통과)로 배포 진행.
- Source: dcy-005 FR-3, rel-001 Feedback Handoff 후보.
## Change Log
### CHG-20260601-03
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Approved → Implemented
#### Intent
- BAT-015 confirm-batch-verification 완료(2026-06-05). AC-1~5 모두 PASSED. HealthResponse 확장 + handler + pytest 3건(test_healthcheck 갱신 + 신규 2건) + baseline 문서(`docs/runtime-flows.md`) 명문화 + interface-contract.md 정합 모두 evidence 확보.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- BAT-015 (release-candidate)
- Releases:
- REL-002 (예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: 미래 production-grade readiness probe(`/readiness` endpoint 분리)는 본 REQ 범위 밖, 별도 후속 Discovery 후보.
### CHG-20260601-02
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Proposed → Approved
#### Intent
- 승인 게이트 통과: 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 모두 충족, AC-1~5 구체성 확인 완료, blocker 없음. baseline 문서(docs/runtime-flows.md) 갱신 요구가 AC-4에 반영됨.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-approved
- Follow-up Action: batch 계획 편입 (BAT-015 후보)
### CHG-20260601-01
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-005 confirm-discovery 후 draft-req 실행. dcy-005 FR-3 산출물.
#### Intent
- REL-001 verification checklist의 `/health db_enabled` gap을 닫고 응답 모델 확장 + baseline 문서(runtime-flows.md) 명문화.
#### Acceptance Criteria Delta
- Added:
- AC-1~5 최초 작성
#### Impacted Area Delta
- Docs: docs/runtime-flows.md (baseline), docs/interface-contract.md — 최초 지정
- Modules: app/api/health.py — 최초 지정
- Tests: tests/test_app.py — 최초 지정
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입
#### Validation Plan
- pytest TestClient로 AC-1~3 검증; grep으로 AC-4/AC-5 baseline 문서 명문화 검증
@@ -0,0 +1,132 @@
# REQ-033: ledger_trades.asset_id → portfolio_id 컬럼명 정합화
- Status: Implemented
- Type: Migration
- Priority: Medium
- Owner: Justin Ko
## Intent
- `ledger_trades` 테이블의 `asset_id` 컬럼이 실제로는 portfolio_id를 저장하지만 컬럼명이 의미와 불일치하여 schema reader가 assets 테이블 참조로 오해할 수 있다.
- REQ-029 구현(POST /v1/portfolios/{id}/ledger/trades 이전) 시점의 잔여 명명 정합화를 마무리한다.
## Requirement
- Alembic 신규 revision을 작성해 `ledger_trades.asset_id` 컬럼을 `portfolio_id`로 rename한다(`ALTER TABLE ledger_trades RENAME COLUMN asset_id TO portfolio_id`).
- 기존 데이터는 동일 의미(portfolio_id 저장)이므로 backfill 불요.
- `app/repositories/postgres.py`의 ledger_trades INSERT/SELECT/RETURNING SQL에서 컬럼명을 `portfolio_id`로 갱신한다.
- `app/domain/models.py`의 `LedgerTrade` dataclass에 `asset_id` 필드가 있다면 `portfolio_id`로 rename한다(있는 경우).
- `app/api/v1_portfolios.py` ledger route handler에서 응답에 `asset_id` 노출이 있다면 `portfolio_id`로 정합화한다(있는 경우).
- 마이그레이션 lock 시간을 운영 영향 < 1초로 유지하기 위해 짧은 단일 `ALTER COLUMN RENAME` 트랜잭션으로 처리한다.
## Acceptance Criteria
1. Given 신규 Alembic revision 적용 후, When `\d ledger_trades` 실행, Then `portfolio_id` 컬럼이 존재하고 `asset_id` 컬럼은 존재하지 않는다.
2. Given AC-1 환경, When 기존 `POST /v1/portfolios/{id}/ledger/trades` 호출, Then INSERT/SELECT 회귀 테스트가 모두 통과한다.
3. Given codebase grep `grep -rn "ledger_trades\..*asset_id\|asset_id.*ledger_trades" app/repositories/postgres.py app/domain/models.py app/api/v1_portfolios.py`, When 본 REQ 구현 후, Then 결과는 0건이다.
4. Given 마이그레이션 staging dry-run, When `ALTER COLUMN RENAME` 실행 시간 측정, Then 측정 시간 < 1초 (NFR-1 정합).
5. Given 본 REQ 구현 후 전체 pytest 회귀, When 실행, Then 기존 통과 케이스 모두 유지 + ledger_trades 의존 케이스 통과.
## Impacted Area
- Docs:
- `docs/data-model.md` — ledger_trades 컬럼 명세에 `portfolio_id` 반영, `asset_id` 표기 제거
- (선택) `docs/interface-contract.md` — ledger 응답에 `asset_id` 노출이 있다면 `portfolio_id`로 갱신
- Modules:
- `migrations/versions/` — 신규 revision (예: `20260601_NNNN_ledger_trades_portfolio_id_rename.py`)
- `app/repositories/postgres.py` — ledger_trades INSERT/SELECT/RETURNING 컬럼명 갱신([app/repositories/postgres.py:1284-1314](../../app/repositories/postgres.py#L1284-L1314))
- `app/domain/models.py` — `LedgerTrade` dataclass 필드명 (있다면)
- `app/api/v1_portfolios.py` — ledger route handler 응답 매핑 (있다면)
- Tests:
- 갱신 `tests/test_v1_portfolios_ledger.py` — 컬럼명 변경 회귀 검증
- (선택) 신규 마이그레이션 dry-run 회귀
## Notes
- 본 REQ는 dcy-005 FR-1 산출물. 정합화 묶음 trigger.
- 외부 client(trex-basic 등)는 HTTP API(`POST /v1/portfolios/{id}/ledger/trades`)만 호출하므로 DB 컬럼명 의존 없음 추정. 마이그레이션 전 외부 query 의존성 확인.
- 본 REQ는 별도 REL-002로 분리 배포(dcy-005 OQ-3 결정, 2026-06-01).
- Source: dcy-005 FR-1, REQ-029 Notes follow-up.
## Change Log
### CHG-20260601-03
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Approved → Implemented
#### Intent
- BAT-015 confirm-batch-verification 완료(2026-06-05). AC-2/3/5 PASSED, AC-1/4 staging Deferred(blocker 아님). 마이그레이션 + 코드/스키마/도메인/라우트/테스트/문서 정합화 모두 evidence 확보.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches:
- BAT-015 (release-candidate)
- Releases:
- REL-002 (예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: REL-002 staging 시점에 AC-1 (`\d ledger_trades` 컬럼 검증) + AC-4 (마이그레이션 lock < 1초) evidence 수집. 본 워크스페이스 ledger_trades 테이블 미생성 상태 자체는 별도 후속 항목(BAT-011 mock verification 후속) 등록 후보.
### CHG-20260601-02
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- Status: Proposed → Approved
#### Intent
- 승인 게이트 통과: 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 모두 충족, AC-1~5 구체성 확인 완료, blocker 없음.
#### Acceptance Criteria Delta
- No change
#### Impacted Area Delta
- No change
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-approved
- Follow-up Action: batch 계획 편입 (BAT-015 후보)
### CHG-20260601-01
- Date: 2026-06-01
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-005 confirm-discovery 후 draft-req 실행. dcy-005 FR-1 산출물.
#### Intent
- ledger_trades.asset_id 컬럼명을 의미에 맞게 portfolio_id로 정합화.
#### Acceptance Criteria Delta
- Added:
- AC-1~5 최초 작성
#### Impacted Area Delta
- Docs: docs/data-model.md, (선택) docs/interface-contract.md — 최초 지정
- Modules: migrations/versions/, app/repositories/postgres.py, app/domain/models.py, app/api/v1_portfolios.py — 최초 지정
- Tests: tests/test_v1_portfolios_ledger.py — 최초 지정
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정 — REL-002 분리 예정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; staging 환경에서 ALTER COLUMN RENAME dry-run으로 lock 시간 측정
#### Validation Plan
- pytest로 AC-1~5 검증 + staging dry-run AC-4 측정
+7 -4
View File
@@ -74,7 +74,7 @@
## Next Requirement ID
- Current: `REQ-033`
- Current: `REQ-036`
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
## Requirement Register
@@ -113,11 +113,14 @@
| REQ-030 | portfolio tag 적용/해제/조회 v1 라우트 | Interface | Implemented | Medium | Justin Ko | [req-030](./Interface/req-030_v1-portfolios-tags-crud.md) |
| REQ-031 | portfolio comment CRUD v1 라우트 | Interface | Implemented | Medium | Justin Ko | [req-031](./Interface/req-031_v1-portfolios-comments-crud.md) |
| REQ-032 | portfolio v1 baseline 문서 갱신 | Documentation | Implemented | Medium | Justin Ko | [req-032](./Documentation/req-032_portfolio-v1-baseline-docs-update.md) |
| REQ-033 | ledger_trades.asset_id → portfolio_id 컬럼명 정합화 | Migration | Implemented | Medium | Justin Ko | [req-033](./Migration/req-033_ledger-trades-asset-id-to-portfolio-id-rename.md) |
| REQ-034 | LoginRequest 추가 필드 거부 enforcement | Interface | Implemented | Medium | Justin Ko | [req-034](./Interface/req-034_login-request-extra-forbid-enforcement.md) |
| REQ-035 | GET /health 응답에 db_enabled 필드 추가 | Interface | Implemented | Medium | Justin Ko | [req-035](./Interface/req-035_health-response-db-enabled-field.md) |
## Recent Change Log Summary
- 2026-06-05: REQ-033/034/035 Approved → Implemented (CHG-20260601-03 각각) — BAT-015 confirm-batch-verification 완료(2026-06-05). 전체 372 passed + 로컬 smoke 5건 통과. REQ-033 AC-1/AC-4 실 DB evidence는 REL-002 staging 이연(blocker 아님). BAT-015 status: release-candidate.
- 2026-06-01: REQ-033/034/035 Proposed → Approved (CHG-20260601-02 각각) — confirm-req 무인자 일괄 모드 실행, 승인 게이트(필수 필드/AC 구체성/Change Log) 모두 충족, blocker 없음. dcy-005 산출물 3건 모두 Approved.
- 2026-06-01: REQ-033/034/035 신규 (Proposed) — dcy-005(rel-001-feedback-cleanup) draft-req 산출물. REQ-033 ledger_trades.asset_id → portfolio_id 컬럼명 정합화 (Migration), REQ-034 LoginRequest extra="forbid" enforcement (Interface, REQ-021 follow-up), REQ-035 HealthResponse db_enabled 필드 추가 + docs/runtime-flows.md baseline 갱신 (Interface). 3건 모두 REL-002 분리 배포 예정(dcy-005 OQ-3).
- 2026-06-01: REQ-028 Approved → Implemented (CHG-20260601-02) — CHG-20260601-01 augmentation에 대한 verification evidence 확보. pytest 368 PASSED + 로컬 smoke로 GET /v1/portfolios 응답에 `display_name` 노출 확인. BAT-009 verification.md AC-13 매핑 누적. REL-001 실 배포 환경 smoke는 배포 시 최종 확인.
- 2026-06-01: REQ-028 Implemented → Approved (CHG-20260601-01) — GET /v1/portfolios 응답 schema에 `display_name` 필드 추가 augmentation. portfolio가 참조하는 asset의 display_name을 join하여 UI/CLI 단일 호출 표시 가능. AC-13 신설, Portfolio domain model + PortfolioItem schema + repo join 보강. BAT-009/BAT-014 verification 재실행 필요.
- 2026-05-24: REQ-025 / REQ-026 / REQ-027 / REQ-028 / REQ-032 Approved → Implemented (CHG-20260524-02 각각) — BAT-014 verification 완료. `PortfolioItem.symbol` 응답 계약이 코드(`app/schemas/v1.py`, `app/api/v1_portfolios.py`), 테스트(`tests/test_v1_portfolios_crud.py`, `tests/test_v1_portfolios_list.py`), baseline 문서(`docs/interface-contract.md`)에 반영됨. BAT-014는 release-candidate로 승격되었고 BAT-009/BAT-010의 관련 response-contract evidence는 superseded.
- 2026-05-24: REQ-025 / REQ-026 / REQ-027 / REQ-028 / REQ-032 Implemented → Approved (CHG-20260524-01 각각) — trex-orbit 팀 이슈(`docs/issues/20260524_portfolio-hub-ticket-symbol-in-portfolios.md`) 후속. `PortfolioItem.symbol` 응답 계약 추가 및 `GET /v1/portfolios` 의 asset join 기반 응답(`portfolios JOIN assets` → `assets.symbol`)을 명문화. BAT-009 / BAT-010 및 REL-001의 기존 response-contract evidence는 부분 무효화되어 follow-up BAT-014에서 재검증 필요.
- 2026-05-21: REQ-005 Approved → Implemented (CHG-20260521-03) — BAT-013 confirm-batch-verification 완료. `append_asset_tag_id`/`remove_asset_tag_id` 메서드 신규 + 라우트 호출 교체 + 회귀 테스트 7건 추가. assets 테이블 mutation 정합화 evidence 확보. 전체 369 passed (BAT-012 baseline 362 → +7). BAT-004의 AC-1/AC-5 evidence는 BAT-013에서 superseded. CLI 동일 mismatch는 follow-up REQ 후보.