docs(proofline): restore line-0008 canonical approved artifacts verbatim

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-26 05:29:56 +00:00
committed by GitHub
co-authored by genichin
parent 75406af970
commit ac3f27241a
3 changed files with 82 additions and 54 deletions
+11 -8
View File
@@ -3,17 +3,20 @@ id: "ac-0025"
status: active
---
# 최초 `reconcile_warned` strategy intent 거부 알림
# 재조정 경고에 따른 전략 주문 요청 최초 거부 알림
## Criterion
재조정 경고가 설정된 slot에서 외부 전략 buy/sell intent가 `rejected/reconcile_warned`로 최종 확정되면, `IntentStore.finalize()` 이후에만 `trex.alerts`로 `strategy_intent_rejected`/`warn` 알림을 intent_id 기준 최초 1회 발행해야 한다. 같은 `intent_id` 재요청과 처리 중 중복(`duplicate_in_flight`)은 추가 알림을 만들지 않으며, 같은 slot의 서로 다른 `intent_id`는 각각 독립 발행한다. 알림 payload는 필수 allowlist fields를 항상 포함하고 optional fields는 신뢰 가능한 값이 있을 때만 포함해야 하며, 비허용 필드·민감정보·raw exception body를 alarm/log에 포함해서는 안 된다. emitter 부재/전송 실패/optional context 조회 실패는 저장된 거부 결과와 RPC 응답, broker·slot·warning·ledger·portfolio-hub 동작을 바꾸지 않는 best-effort 경계여야 한다. daemon은 기존 공용 `AlarmEmitter`를 `IntentService`에 주입하고, emitter 없이 `IntentService`를 생성하는 기존 경로는 계속 호환되어야 한다.
`trex-core`는 해당 slot에 해제되지 않은 재조정 경고가 남아 있어 `ReconcileWarningStore.is_warned(slot_id)`가 참을 반환하고, 그 때문에 외부 전략의 새로운 매수 또는 매도 요청을 `status="rejected"`, `reason="reconcile_warned"`로 거부한 경우, 그 결과를 `IntentStore.finalize()`에 저장한 뒤 해당 `intent_id`에 대한 경고를 한 건 발행해야 한다. 경고는 `trex.alerts`로 보내며 `severity="warn"`, `code="strategy_intent_rejected"`를 사용한다. 같은 `intent_id`가 다시 들어오거나 최초 요청을 처리하는 도중 중복 요청이 들어오더라도 경고를 다시 발행해서는 안 된다. 반면, 같은 slot을 대상으로 하더라도 `intent_id`가 다르면 각각 별개의 최초 거부로 보고 경고를 발행해야 한다.
경고에는 `instance_key`, `intent_id`, `intent_type`, `reason="reconcile_warned"`, `broker_submitted=false`를 반드시 포함한다. `slot_id`, `portfolio_id`, `symbol`, `strategy_id`는 신뢰할 수 있는 값이 확인된 경우에만 포함하며, 알 수 없는 값을 추정하거나 원본 요청에서 그대로 복사해서는 안 된다. 매수 요청의 `strategy_id`와 매도 요청의 `sell_strategy_id`는 경고에서 공통 필드인 `strategy_id`로 표현한다.
정상 승인된 요청, `reconcile_warned` 이외의 사유로 거부된 요청, 최종 결과 저장에 실패한 요청, 그리고 safety-sell·kill-switch·watchdog 같은 내부 안전 경로에서는 이 경고를 발행하지 않는다. 경고 발행기가 없거나 부가 정보를 조회하지 못하거나 메시지 전송이 실패하더라도 이미 결정된 거부 결과, RPC 응답, 재조정 경고, slot 상태 및 broker·ledger·portfolio-hub 동작을 중단하거나 변경해서는 안 된다. 경고와 이 처리 경로의 로그에는 계좌 식별정보, credential, token, password, DSN, 원본 요청, 전략의 `extra`, 외부 시스템의 원본 payload 또는 예외 원문을 포함해서는 안 된다.
## Verification
- 성공 조건: warned slot의 새 buy/sell intent 각각에서 finalize 후 `strategy_intent_rejected`/`warn` 알림이 한 건 발행된다.
- dedup 조건: 같은 `intent_id` 재시도(동일 process 및 durable-store 재구성 후)에서 추가 알림이 없고, 같은 slot의 다른 `intent_id`는 각각 한 건 발행한다.
- 예외 경계: `duplicate_in_flight`, admitted, `reconcile_warning_unavailable`, `capacity_full`, validation rejection, 그 밖의 non-`reconcile_warned` rejection에서는 알림이 없어야 한다.
- 순서/실패 조건: finalize 실패 시 알림이 없어야 하며 finalize 완료 전에 publish가 실행되면 FAIL이다. optional context 조회 실패·publisher false·publisher exception·emitter 부재에서도 RPC 응답은 `rejected/reconcile_warned`를 유지해야 한다.
- payload/보안 조건: 필수 fields와 `broker_submitted=false`가 포함되고, optional fields는 확인 가능 시에만 포함된다. buy/sell strategy id는 공통 `strategy_id`로 정규화되며 비허용 필드와 민감정보·raw exception body가 alarm/log에 없어야 한다.
- wiring/회귀 조건: daemon이 공용 `AlarmEmitter`를 `IntentService`에 전달하고, emitter 없는 직접 생성 경로 및 기존 intent dedup/alarm 관련 회귀가 유지되어야 한다.
- 정상 동작: 재조정 경고가 설정된 slot에 새로운 매수 요청과 매도 요청을 각각 제출하면, 최종 거부 결과가 저장된 뒤 `strategy_intent_rejected` 경고가 `warn` 등급으로 요청마다 한 건씩 발행된다. 각 경고에는 필수 필드와 `broker_submitted=false`가 들어 있다. 매수의 `strategy_id`와 매도의 `sell_strategy_id`는 값이 있을 때 경고의 `strategy_id`로 나타나며, `slot_id`, `portfolio_id`, `symbol`은 저장소에서 확인된 값만 포함된다.
- 중복 및 제외 조건: 같은 `intent_id`를 같은 프로세스에서 반복하거나 기존 영속 intent 저장소로 서비스를 다시 구성한 뒤 반복해도 경고는 추가로 발행되지 않고 저장된 기존 결과가 그대로 반환된다. 재조정 경고가 설정된 같은 slot에 서로 다른 `intent_id` 두 건을 제출하면 경고는 두 건 발행된다. 최초 요청을 처리하는 도중 들어온 중복 요청과 `duplicate_in_flight`, 정상 승인, `reconcile_warning_unavailable`, `capacity_full`, 입력 검증 실패 등 `reconcile_warned`가 아닌 결과는 경고를 만들지 않는다.
- 처리 순서 및 장애 조건: 호출 순서를 기록하는 테스트용 저장소와 경고 발행기를 사용해 `IntentStore.finalize()`가 경고 발행보다 먼저 완료되는지 확인한다. 최종 결과 저장이 실패하면 경고를 발행하지 않는다. 경고 발행기가 없거나 실패를 반환하거나 전송 계층에 장애가 발생해도 원래의 `reconcile_warned` 거부 결과와 RPC 응답 본문은 유지된다. 이 과정에서 broker 주문 제출·취소, slot 상태 전이, 재조정 경고 설정·해제, ledger 기록 또는 portfolio-hub 쓰기가 추가로 발생하지 않는다.
- 보안 및 호환성: 원본 요청과 `extra`에 credential·token·account·DSN·exception 식별용 값을 넣더라도 경고 제목, 필드, 직렬화된 전송 내용과 출력 로그에 해당 값이나 금지된 key가 나타나지 않는다. 부가 정보를 읽지 못하거나 값이 없으면 해당 선택 필드만 생략하고 필수 필드와 거부 결과는 유지한다. 운영 daemon의 `IntentService`에는 기존 공용 `AlarmEmitter`가 연결되며, 경고 발행기 없이 `IntentService`를 직접 생성하는 기존 사용 방식도 계속 동작한다.
- 검사 방법: `tests/application/test_reconcile_warning_blocking.py` 또는 별도의 intent 경고 테스트에 매수·매도, 같은 `intent_id`와 다른 `intent_id`, 재시작을 모사할 수 있는 intent 저장소, 처리 순서, 발행 실패, 경고 필드 허용 목록과 부작용 없음 조건을 추가한다. 이어서 `tests/application/test_messaging_intent_consumer.py`, `tests/application/test_alarm_emitter.py`, daemon 연결 테스트, AC-0023·AC-0024 관련 회귀 테스트, 전체 저장소 테스트, Ruff, ProofLine validation과 `git diff --check`를 실행한다.