feat(BAT-005): add broker credential CLI command with persistence and tests
- Add CredentialService for managing broker credentials - Implement credential CLI commands (create, list, get, update, delete) - Add postgres repository support for credential operations - Add comprehensive test coverage for credential CLI - Update docs/batches/index.md and docs/srs/index.md with BAT-005 - Update docs/project-structure.md to reflect credential service
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# BAT-005 Implementation Log
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-005
|
||||
- Profile: batch-lite
|
||||
- Completed: 2026-05-03
|
||||
|
||||
## 구현 내역
|
||||
|
||||
### 1. `app/repositories/postgres.py` — `delete_broker_credential` 추가
|
||||
|
||||
```python
|
||||
def delete_broker_credential(self, user_id: str, broker: str) -> bool:
|
||||
with self._connect().cursor() as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM broker_credentials WHERE user_id = %s AND broker = %s",
|
||||
(user_id, broker),
|
||||
)
|
||||
return cur.rowcount > 0
|
||||
```
|
||||
|
||||
### 2. `app/cli/commands/credential.py` — 신규 생성
|
||||
|
||||
- `credential_app = typer.Typer(help="Broker credential management commands.")`
|
||||
- `_with_service(ctx, callback)`: repository 생성 → execute_command → finally close. RuntimeError도 캐치해 stdout 출력 후 exit 1.
|
||||
- `set` 명령: `--user`, `--broker` (기본값 `kis`), `--account-ref`, `--app-key`, `--app-secret`, `--force`
|
||||
- `list` 명령: `--user` — encrypted_payload 제외 dict 리스트 출력
|
||||
- `remove` 명령: `--user`, `--broker`, `--force` — delete_broker_credential 호출
|
||||
|
||||
### 3. `app/cli/main.py` — `credential_app` 등록
|
||||
|
||||
```python
|
||||
from app.cli.commands.credential import credential_app
|
||||
...
|
||||
app.add_typer(credential_app, name="credential")
|
||||
```
|
||||
|
||||
### 4. `tests/test_cli.py`
|
||||
|
||||
- `FakeRepo.__init__`: `_broker_credentials: dict[tuple[str, str], BrokerCredential] = {}` 추가
|
||||
- `FakeRepo.upsert/list/get/delete_broker_credential` 메서드 추가
|
||||
- `_settings_with_enc_key()`, `build_runner_with_enc_key()` 헬퍼 추가
|
||||
- 신규 테스트 5개 추가
|
||||
|
||||
### 5. `docs/project-structure.md` — CLI 모듈 목록 갱신
|
||||
|
||||
- `app/cli/` 섹션에 `credential` 명령 그룹 추가
|
||||
- primary runtime 설명에 `broker credentials` 추가
|
||||
|
||||
## 설계 결정
|
||||
|
||||
- dict 리스트 출력은 `render_output`의 타입 매칭을 우회해 `console.print(item)` 직접 호출 (기존 render_output이 등록된 타입만 처리함)
|
||||
- `RuntimeError`(`CREDENTIAL_ENCRYPTION_KEY` 미설정)는 `_with_service` 헬퍼에서 캐치해 stdout으로 출력 후 exit 1 처리
|
||||
@@ -0,0 +1,45 @@
|
||||
# BAT-005 Index
|
||||
|
||||
- Status: release-candidate
|
||||
- Batch: BAT-005
|
||||
- Profile: batch-lite
|
||||
- Created: 2026-05-03
|
||||
|
||||
## Included REQ
|
||||
|
||||
| REQ | 제목 | Type | Priority |
|
||||
|-----|------|------|----------|
|
||||
| REQ-015 | CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) | Interface | Medium |
|
||||
|
||||
## Source Discovery
|
||||
|
||||
- minor-change fast path (Discovery 없음)
|
||||
|
||||
## Scope Summary
|
||||
|
||||
- CLI `pfhub credential set/list/remove` 명령 그룹 신규 추가
|
||||
- `app/cli/commands/credential.py` 신규 생성 (`credential_app`)
|
||||
- `app/cli/main.py`에 `credential_app` 등록
|
||||
- `app/repositories/postgres.py`에 `delete_broker_credential` 메서드 추가
|
||||
- 기존 `broker_credentials` 테이블 / `upsert_broker_credential` / `list_broker_credentials` 재사용
|
||||
- 기존 Fernet 암호화 인프라 (`app/core/encryption.py`) 재사용
|
||||
- DB 스키마 변경 없음, HTTP API 변경 없음
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/project-structure.md` — CLI 모듈 목록에 `credential.py` 추가
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` (신규)
|
||||
- `app/cli/main.py` (`credential_app` 등록)
|
||||
- `app/repositories/postgres.py` (`delete_broker_credential` 추가)
|
||||
- Tests:
|
||||
- `tests/test_cli.py` — credential 명령 통합 테스트 추가
|
||||
|
||||
## Stage Documents
|
||||
|
||||
| Stage | File | Status |
|
||||
|-------|------|--------|
|
||||
| Planning | [planning.md](./planning.md) | confirmed |
|
||||
| Implementation | [implementation.md](./implementation.md) | completed |
|
||||
| Verification | [verification.md](./verification.md) | passed |
|
||||
@@ -0,0 +1,73 @@
|
||||
# BAT-005 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-005
|
||||
- Profile: batch-lite
|
||||
|
||||
## Included REQ
|
||||
|
||||
- REQ-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- HTTP API 엔드포인트 추가 (별도 REQ 대상)
|
||||
- 브로커 인증 정보 복호화 출력 (보안 정책상 CLI에서 plain text 노출 금지)
|
||||
- 브로커 종류 확장 (현재는 `kis` 기본값만 처리)
|
||||
|
||||
## 구현 순서
|
||||
|
||||
### 1단계: repository 메서드 추가
|
||||
|
||||
- `app/repositories/postgres.py`
|
||||
- `delete_broker_credential(user_id: str, broker: str) -> bool` 추가
|
||||
- 레코드 존재 시 삭제 후 `True`, 미존재 시 `False` 반환
|
||||
|
||||
### 2단계: CLI 명령 모듈 신규 생성
|
||||
|
||||
- `app/cli/commands/credential.py`
|
||||
- `credential_app = typer.Typer(help="Broker credential management commands.")`
|
||||
- `_with_service(ctx, callback)` 헬퍼: repository 생성 → callback 실행 → finally close
|
||||
- `set` 명령: `--user`, `--broker` (기본값 `kis`), `--account-ref`, `--app-key`, `--app-secret`
|
||||
- `get_user_by_username` → `None`이면 `ValueError`
|
||||
- `settings.require_credential_encryption_key()` → Fernet 암호화
|
||||
- `upsert_broker_credential` 호출
|
||||
- `list` 명령: `--user`
|
||||
- `get_user_by_username` → `None`이면 `ValueError`
|
||||
- `list_broker_credentials` 호출, `encrypted_payload` 제외 출력
|
||||
- `remove` 명령: `--user`, `--broker`, `--force`
|
||||
- `get_user_by_username` → `None`이면 `ValueError`
|
||||
- `_confirm(force, ...)` 확인 후 `delete_broker_credential` 호출
|
||||
|
||||
### 3단계: CLI 등록
|
||||
|
||||
- `app/cli/main.py`
|
||||
- `from app.cli.commands.credential import credential_app` import 추가
|
||||
- `app.add_typer(credential_app, name="credential")` 등록
|
||||
|
||||
### 4단계: 문서 갱신
|
||||
|
||||
- `docs/project-structure.md` — CLI 모듈 목록에 `credential.py` 항목 추가
|
||||
|
||||
### 5단계: 테스트
|
||||
|
||||
- `tests/test_cli.py`
|
||||
- `test_cli_credential_set_creates_record` (신규): alice 생성 후 set 실행 → list에 표시
|
||||
- `test_cli_credential_list_hides_payload` (신규): list 출력에 `encrypted_payload` 미포함 확인
|
||||
- `test_cli_credential_set_with_unknown_user_exits_error` (신규): 미존재 user → exit code 1
|
||||
- `test_cli_credential_remove_deletes_record` (신규): set 후 remove → list에서 사라짐
|
||||
- `test_cli_credential_set_requires_encryption_key` (신규): CREDENTIAL_ENCRYPTION_KEY 미설정 → exit code 1
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Internal:
|
||||
- `broker_credentials` 테이블 (migration 0014 — 이미 존재)
|
||||
- `upsert_broker_credential`, `list_broker_credentials`, `get_broker_credential` (이미 존재)
|
||||
- `app/core/encryption.py` — `encrypt_payload` (이미 존재)
|
||||
- `app/core/config.py` — `settings.require_credential_encryption_key()` (이미 존재)
|
||||
- `_confirm`, `execute_command`, `render_output` (기존 CLI 헬퍼 — 이미 존재)
|
||||
- External: 없음
|
||||
|
||||
## Design Gate
|
||||
|
||||
- Design Required Before Implementation: no
|
||||
- Reason: 기존 `_with_service` 헬퍼 패턴 재사용 (`user.py` 동일 구조). 구조/런타임 흐름 변경 없음.
|
||||
@@ -0,0 +1,61 @@
|
||||
# BAT-005 Verification
|
||||
|
||||
- Status: passed
|
||||
- Batch: BAT-005
|
||||
- Profile: batch-lite
|
||||
- Verified: 2026-05-03
|
||||
|
||||
## REQ Coverage
|
||||
|
||||
### REQ-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
|
||||
|
||||
| AC # | 기준 | 검증 방법 | 결과 |
|
||||
|------|------|-----------|------|
|
||||
| AC-1 | alice 사용자 + 유효 enc key → `credential set` 실행 시 broker_credentials 레코드 생성(upsert) | `test_cli_credential_set_creates_record` | ✅ passed |
|
||||
| AC-2 | `credential list` 출력에 broker, account_ref, credential_version 포함 / encrypted_payload 미포함 | `test_cli_credential_list_hides_payload` | ✅ passed |
|
||||
| AC-3 | 존재하지 않는 username → exit code 1 + 에러 메시지 | `test_cli_credential_set_with_unknown_user_exits_error` | ✅ passed |
|
||||
| AC-4 | `credential remove --force` 후 list에서 사라짐 | `test_cli_credential_remove_deletes_record` | ✅ passed |
|
||||
| AC-5 | CREDENTIAL_ENCRYPTION_KEY 미설정 → exit code 1 + 에러 메시지 포함 | `test_cli_credential_set_requires_encryption_key` | ✅ passed |
|
||||
|
||||
## Test Evidence
|
||||
|
||||
### 실행 명령
|
||||
```bash
|
||||
python -m pytest tests/test_cli.py -q
|
||||
```
|
||||
|
||||
### 결과
|
||||
```
|
||||
49 passed in 1.49s
|
||||
```
|
||||
|
||||
- 신규 테스트 5개 모두 passed
|
||||
- 기존 테스트 44개 regression 없음
|
||||
|
||||
### 신규 테스트 목록
|
||||
| 테스트 | AC |
|
||||
|--------|----|
|
||||
| `test_cli_credential_set_creates_record` | AC-1 |
|
||||
| `test_cli_credential_list_hides_payload` | AC-2 |
|
||||
| `test_cli_credential_set_with_unknown_user_exits_error` | AC-3 |
|
||||
| `test_cli_credential_remove_deletes_record` | AC-4 |
|
||||
| `test_cli_credential_set_requires_encryption_key` | AC-5 |
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|-----------|
|
||||
| `app/cli/commands/credential.py` | 신규 생성 — `credential_app`, `set`/`list`/`remove` 명령 |
|
||||
| `app/cli/main.py` | `credential_app` import 및 `app.add_typer(credential_app, name="credential")` 등록 |
|
||||
| `app/repositories/postgres.py` | `delete_broker_credential(user_id, broker) -> bool` 메서드 추가 |
|
||||
| `tests/test_cli.py` | `FakeRepo.upsert/list/get/delete_broker_credential`, 신규 테스트 5개 추가 |
|
||||
| `docs/project-structure.md` | CLI 명령 그룹 목록에 `credential` 추가 |
|
||||
|
||||
## Baseline Document Sync
|
||||
|
||||
- `docs/project-structure.md`: `app/cli/` 섹션에 `credential` 명령 그룹 추가 완료
|
||||
- 구조/런타임 흐름/인터페이스 계약 변경 없음 → `runtime-flows.md`, `interface-contract.md` 갱신 불필요
|
||||
|
||||
## Residual Risk
|
||||
|
||||
- 없음
|
||||
@@ -11,4 +11,5 @@
|
||||
| BAT-001 | standard | release-candidate | REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009, REQ-010 | dcy-002 | [bat-001_20260502_trex-basic-v1-api-contract](./bat-001_20260502_trex-basic-v1-api-contract/) |
|
||||
| BAT-002 | batch-lite | release-candidate | REQ-012 | minor-change | [bat-002_20260502_service-token-rotate-cli](./bat-002_20260502_service-token-rotate-cli/) |
|
||||
| BAT-003 | batch-lite | release-candidate | REQ-013 | dcy-003 | [bat-003_20260502_portfolio-list-user-required](./bat-003_20260502_portfolio-list-user-required/) |
|
||||
| BAT-004 | batch-lite | release-candidate | REQ-014 | minor-change | [bat-004_20260502_portfolio-create-user-required](./bat-004_20260502_portfolio-create-user-required/) |
|
||||
| BAT-004 | batch-lite | release-candidate | REQ-014 | minor-change | [bat-004_20260502_portfolio-create-user-required](./bat-004_20260502_portfolio-create-user-required/) |
|
||||
| BAT-005 | batch-lite | release-candidate | REQ-015 | minor-change | [bat-005_20260503_broker-credential-cli](./bat-005_20260503_broker-credential-cli/) |
|
||||
@@ -27,7 +27,7 @@
|
||||
- Type: mixed
|
||||
- Planned Entry Points:
|
||||
- uvicorn app.main:app :: serve the authenticated HTTP API, OpenAPI docs, and startup bootstrap hooks
|
||||
- pfhub :: run operational CLI commands for health, users, portfolios, strategies, and service tokens
|
||||
- pfhub :: run operational CLI commands for health, users, portfolios, strategies, service tokens, and broker credentials
|
||||
- alembic upgrade head :: apply ordered database schema migrations for persistent environments
|
||||
|
||||
## Purpose
|
||||
@@ -87,6 +87,7 @@
|
||||
- All v1 endpoints require service-token authentication (token_source=db, token_type=service).
|
||||
- app/cli/
|
||||
- Defines the Typer entrypoint, command groups, output helpers, and operator-facing command flows.
|
||||
- Command groups: `portfolio`, `strategy`, `user`, `service-token`, `credential` (broker credential management), and health commands (`health-check`, `init-db`, `migrate-db`).
|
||||
- app/core/
|
||||
- Owns configuration loading, auth guards, security helpers, and shared error handling.
|
||||
- `encryption.py`: Fernet-based credential payload encrypt/decrypt helpers. Reads `CREDENTIAL_ENCRYPTION_KEY` env var via `Settings.require_credential_encryption_key()`.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# req-015: CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
|
||||
|
||||
- Status: Approved
|
||||
- Type: Interface
|
||||
- Priority: Medium
|
||||
- Owner: repository-maintainers
|
||||
|
||||
## Intent
|
||||
- 운영자가 CLI(`pfhub credential`)를 통해 KIS 브로커 인증 정보를 안전하게 등록, 조회, 삭제할 수 있어야 한다.
|
||||
- `broker_credentials` 테이블(migration 0014)과 기존 Fernet 암호화 인프라(`app/core/encryption.py`, `CREDENTIAL_ENCRYPTION_KEY`)를 재사용해 CLI 레이어만 추가한다.
|
||||
|
||||
## Requirement
|
||||
- 시스템은 `pfhub credential set --user <username> --broker <broker> --account-ref <ref> --app-key <key> --app-secret <secret>` 명령을 제공해야 한다. 이 명령은 `{"app_key": ..., "app_secret": ...}` JSON을 Fernet으로 암호화한 뒤 `broker_credentials` 테이블에 upsert한다.
|
||||
- 시스템은 `pfhub credential list --user <username>` 명령을 제공해야 한다. 이 명령은 사용자의 브로커 인증 정보 목록을 반환하되, `encrypted_payload`는 출력하지 않는다.
|
||||
- 시스템은 `pfhub credential remove --user <username> --broker <broker> [--force]` 명령을 제공해야 한다. 이 명령은 해당 인증 정보 레코드를 삭제한다.
|
||||
- 존재하지 않는 사용자를 지정하면 에러 메시지와 함께 exit code 1로 종료해야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given alice 사용자와 유효한 `CREDENTIAL_ENCRYPTION_KEY`가 설정된 환경에서, When `pfhub credential set --user alice --broker kis --account-ref REF --app-key KEY --app-secret SECRET`을 실행하면, Then `broker_credentials` 테이블에 암호화된 payload로 레코드가 생성(또는 갱신)된다.
|
||||
2. Given `broker_credentials`에 alice의 kis 레코드가 있을 때, When `pfhub credential list --user alice`를 실행하면, Then broker, account_ref, credential_version, last_rotated_at이 출력되고 `encrypted_payload`는 포함되지 않는다.
|
||||
3. Given 존재하지 않는 username을 지정할 때, When `pfhub credential set --user unknown ...`을 실행하면, Then 에러 메시지와 함께 exit code 1로 종료된다.
|
||||
4. Given alice의 kis 인증 정보가 있을 때, When `pfhub credential remove --user alice --broker kis --force`를 실행하면, Then 해당 레코드가 삭제되고 `pfhub credential list --user alice`에서 더 이상 표시되지 않는다.
|
||||
5. Given `CREDENTIAL_ENCRYPTION_KEY`가 설정되지 않은 환경에서, When `pfhub credential set ...`을 실행하면, Then 설정 미비 에러 메시지와 함께 exit code 1로 종료된다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: `docs/project-structure.md` (CLI 모듈 목록 갱신)
|
||||
- Modules:
|
||||
- `app/cli/commands/credential.py` (신규 — `credential_app`, `set`, `list`, `remove` 명령)
|
||||
- `app/cli/main.py` (`credential_app` 등록 추가)
|
||||
- `app/repositories/postgres.py` (`delete_broker_credential` 메서드 추가)
|
||||
- Tests: `tests/test_cli.py` (credential 명령 통합 테스트 추가)
|
||||
|
||||
## Notes
|
||||
- `broker_credentials` 테이블 DDL과 `upsert_broker_credential`, `list_broker_credentials`, `get_broker_credential` 메서드는 migration 0014에서 이미 구현됨. `delete_broker_credential` 메서드는 이번 REQ에서 추가.
|
||||
- `app/core/encryption.py`의 `encrypt_payload(key, plaintext)` 사용.
|
||||
- 기존 CLI 패턴(`_with_service` 헬퍼, `execute_command`, `render_output`, `_confirm`) 동일하게 적용.
|
||||
- `--broker` 기본값은 `kis`.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260503-02
|
||||
- Date: 2026-05-03
|
||||
- Author: repository-maintainers
|
||||
|
||||
#### Change Summary
|
||||
- REQ-015 Approved 전환 — minor-change fast path (단일 저위험 REQ, CLI 레이어만 추가, 구조/런타임 영향 없음)
|
||||
|
||||
#### Intent
|
||||
- Acceptance Criteria 5개 모두 구현 판단에 충분하며 blocking assumption 없음
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added: none
|
||||
- Updated: none
|
||||
- Removed: none
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs: none
|
||||
- Modules: none
|
||||
- Tests: none
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: BAT-005 (batch-lite)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: run-batch-implementation 진행
|
||||
|
||||
#### Validation Plan
|
||||
- BAT-005 verification 통과 확인
|
||||
|
||||
---
|
||||
|
||||
### CHG-20260503-01
|
||||
- Date: 2026-05-03
|
||||
- Author: repository-maintainers
|
||||
|
||||
#### Change Summary
|
||||
- REQ-015 최초 작성 — CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
|
||||
|
||||
#### Intent
|
||||
- 운영자가 KIS 브로커 인증 정보를 CLI로 관리할 수 있도록 Interface 레이어 추가
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- 위 Acceptance Criteria 1~5 전체
|
||||
- Updated: none
|
||||
- Removed: none
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs: `docs/project-structure.md`
|
||||
- Modules: `app/cli/commands/credential.py` (신규), `app/cli/main.py`, `app/repositories/postgres.py`
|
||||
- Tests: `tests/test_cli.py`
|
||||
|
||||
#### Delivery Trace Delta
|
||||
- Batches: 미정 (BAT-005 예정)
|
||||
- Releases: 미정
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current
|
||||
- Follow-up Action: 없음
|
||||
|
||||
#### Validation Plan
|
||||
- `tests/test_cli.py`에 credential 명령 통합 테스트 작성 후 `pytest tests/test_cli.py` 전체 통과 확인
|
||||
+4
-1
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-015`
|
||||
- Current: `REQ-016`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -95,9 +95,12 @@
|
||||
| REQ-012 | service-token rotate CLI 명령 구현 | Interface | Approved | Medium | repository-maintainers | [REQ-012](./Interface/req-012_service-token-rotate-cli.md) |
|
||||
| REQ-013 | CLI `portfolio list --user` 필수화 및 소유자 기반 조회 | Interface | Implemented | Medium | repository-maintainers | [REQ-013](./Interface/req-013_portfolio-list-user-required.md) |
|
||||
| REQ-014 | CLI `portfolio create --user` 필수화 및 소유자 설정 | Interface | Implemented | Medium | repository-maintainers | [REQ-014](./Interface/req-014_portfolio-create-user-required.md) |
|
||||
| REQ-015 | CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리) | Interface | Approved | Medium | repository-maintainers | [REQ-015](./Interface/req-015_broker-credential-cli.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-05-03: REQ-015 Approved 전환 — minor-change fast path (단일 저위험 REQ)
|
||||
- 2026-05-03: REQ-015 Proposed 생성 — CLI `credential` 명령 그룹 구현 (브로커 인증 정보 관리)
|
||||
- 2026-05-02: REQ-014 Implemented 전환 — BAT-004 verification evidence 확인 (44 passed)
|
||||
- 2026-05-02: REQ-014 Approved 직행 (minor-change) — CLI `portfolio create --user` 필수화 및 소유자 설정
|
||||
- 2026-05-02: REQ-013 Implemented 전환 — BAT-003 verification evidence 확인 (41 passed)
|
||||
|
||||
Reference in New Issue
Block a user