diff --git a/.proofline/criteria/ac-0022.md b/.proofline/criteria/ac-0022.md index d7568f1..54d5f67 100644 --- a/.proofline/criteria/ac-0022.md +++ b/.proofline/criteria/ac-0022.md @@ -9,9 +9,7 @@ status: active `trex.slots.req.`의 **전략용 slots 응답에 포함되는** `holding` 슬롯(AC-0023에 따라 C-case 수량 불일치로 제외된 slot 제외)의 `qty`는 요청한 instance에 결합된 KIS account broker holdings(`hldg_qty`)를 authoritative source로 사용해야 한다. `daily_slot.qty`와 portfolio-hub 집계값은 각각 `slot_quantity`, `asset_quantity` 진단 필드로 분리 유지한다. broker 수량을 확보하지 못했거나 freshness 허용시간을 초과하면 core/hub mirror fallback 없이 `qty=null`, `qty_source="unavailable"`, `broker_quantity_available=false`, `broker_quantity_captured_at=null`을 반환해 전략이 fail-closed하도록 해야 한다. confirmed broker zero는 `qty=0`, `qty_source="broker"`로 unavailable과 구분되어야 한다. 이 read path는 주문 submit/cancel, ledger write, slot lifecycle mutation을 유발하지 않아야 한다. -## Scope note - -AC-0023에 따라 C-case(`case_code=C`) 수량 불일치로 전략용 slots RPC에서 제외된 slot에는 이 AC의 broker-authoritative `qty` 응답 형식을 적용하지 않는다. AC-0023 제외가 AC-0022 broker-qty 형식보다 우선한다. +**Scope note:** AC-0023에 따라 C-case(`case_code=C`) 수량 불일치로 전략용 slots RPC에서 제외된 slot에는 이 AC의 broker-authoritative `qty` 응답 형식을 적용하지 않는다. AC-0023 제외가 AC-0022 broker-qty 형식보다 우선한다. ## Verification diff --git a/.proofline/criteria/ac-0024.md b/.proofline/criteria/ac-0024.md new file mode 100644 index 0000000..8a084e3 --- /dev/null +++ b/.proofline/criteria/ac-0024.md @@ -0,0 +1,18 @@ +--- +id: "ac-0024" +status: active +--- + +# Reconciliation 수량 불일치 탐지·변경·해소 알림 + +## Criterion + +`trex-core`는 B-case(core slot quantity와 broker quantity 불일치)를 stable slot별로, C-case(같은 instance·symbol의 portfolio-hub `asset_quantity` 합계와 broker quantity 불일치)를 `instance_key + symbol`별로 하나의 durable notification lifecycle로 추적해야 한다. 새 mismatch와 동일 identity의 case별 비교 quantity tuple 변경은 sanitized `trex.alerts` detected/changed event를 각각 한 번 발행하고, 성공한 publish fingerprint를 process restart 뒤에도 보존하여 unchanged scheduler tick을 억제해야 한다. B-case는 operator `sync`, C-case는 operator `verify`의 authoritative durable warning clear에서만 resolved event를 한 번 발행해야 하며 scheduler 관찰만으로 recovery를 발행해서는 안 된다. Event는 lifecycle state, `instance_key`, `symbol`, `case_code`, broker quantity와 case별 비교 quantity를 포함하고 account number/hash, credential, token, DSN, raw payload와 exception 본문을 포함해서는 안 된다. Publish 성공 뒤에만 fingerprint를 전진시키고 publish 또는 notification-state persistence 실패는 reconciliation, warning set/clear, sync/verify, scheduler와 slot lifecycle을 중단하거나 바꾸지 않아야 한다. Resolved publish 실패 시 notification state가 writable이면 pending transition을 durable하게 보존하여 후속 retry opportunity에서 재발행한다. Notification-state persistence 자체가 실패하면 authoritative warning clear를 유지하고 event 유실 가능성을 sanitized log로 남긴다. + +## Verification + +- 성공 조건: 새로운 B-case slot mismatch는 B detected event 한 건, 새로운 C-case symbol aggregate mismatch는 portfolio 수와 무관하게 C detected event 한 건을 만든다. 동일 fingerprint의 반복 tick은 추가 event를 만들지 않고 quantity tuple 변경은 changed event 한 건을 만든다. process restart 모사 뒤 unchanged mismatch는 억제되며, B `sync`와 C aggregate `verify` clear는 각 identity의 resolved event 한 건만 만든다. +- publish 실패 조건: transport failure에서는 detected/changed/resolved fingerprint가 전진하지 않아야 한다. detected/changed는 다음 동일 detection에서 재시도하고, resolved는 notification state가 writable한 경우 warning clear를 유지하면서 pending transition을 durable하게 보존해 후속 retry opportunity에서 재시도해야 한다. Notification-state persistence 실패도 business path를 중단하지 않아야 하며 resolved event 유실 가능성을 credential·DSN·raw exception 없는 log로 남겨야 한다. +- 경계 조건: C-case 동일 symbol의 여러 portfolio/slot이 중복 event를 만들거나, scheduler의 단순 quantity 일치가 durable warning clear·resolved event를 만들거나, B slot 간 fingerprint가 섞이거나, restart 뒤 unchanged event가 재발행되거나, publish 전 fingerprint 기록으로 event가 유실되면 FAIL이다. +- 보안·비변경 조건: 모든 lifecycle event의 allowlist fields와 rendered payload에 account number/hash, credential, token, DSN, raw KIS/portfolio-hub payload 및 exception 본문이 없어야 한다. 알림 전후 broker call mutation, portfolio-hub write, slot lifecycle/quantity mutation과 기존 AC-0023 blocking/clear semantics 변화가 없어야 한다. +- 검사 방법: in-memory alarm transport와 restart 가능한 notification-state fixture로 B/C 최초 탐지, C multi-portfolio aggregation, unchanged suppression, quantity change, restart, publish failure retry, B sync resolution, C verify resolution, duplicate clear와 sensitive-field absence를 검증한다. focused reconciliation·warning·alarm tests, persistence migration/round-trip tests와 전체 repository regression 및 Ruff를 실행한다. diff --git a/.proofline/identities.json b/.proofline/identities.json index ec1879e..efe7dfe 100644 --- a/.proofline/identities.json +++ b/.proofline/identities.json @@ -1,5 +1,5 @@ { "schema_version": 1, - "next_line_number": 7, - "next_ac_number": 24 + "next_line_number": 8, + "next_ac_number": 25 } diff --git a/.proofline/lines/line-0007/dcy-0007.md b/.proofline/lines/line-0007/dcy-0007.md new file mode 100644 index 0000000..b0c77a6 --- /dev/null +++ b/.proofline/lines/line-0007/dcy-0007.md @@ -0,0 +1,60 @@ +--- +id: "dcy-0007" +status: confirmed +--- + +# Reconciliation 수량 불일치 알림 + +## Problem + +`trex-core`는 broker와 core slot의 보유수량이 다른 B-case, portfolio-hub와 broker의 종목별 보유수량 합계가 다른 C-case를 reconciliation warning으로 기록한다. 그러나 두 mismatch 경로는 `AlarmEmitter.publish_alarm()`을 호출하지 않아 `trex.alerts` event가 만들어지지 않고, `trex-notifier`가 운영자에게 Telegram 알림을 전달할 수 없다. 현재 scheduler가 같은 mismatch를 반복 탐지하므로 단순 publish 추가는 scheduler tick마다 중복 메시지를 만들 수 있으며, 수량이 바뀌거나 mismatch가 해소된 사실도 별도 lifecycle event로 구분되지 않는다. + +기대 상태는 B/C mismatch의 최초 탐지와 material quantity change를 정제된 `trex.alerts` event로 알리고, 기존 warning 해소 권한에 따라 mismatch가 실제로 해소되면 recovery/resolved event를 정확히 한 번 알리는 것이다. 알림 상태나 publish 실패가 reconciliation, warning persistence·clear, slot lifecycle 또는 broker/portfolio-hub 수량을 변경하거나 중단해서는 안 된다. + +## Evidence + +- 원본 요청: GitHub issue #64 `https://github.com/fourblocks2020/trex-core/issues/64`는 B/C mismatch의 최초 탐지·material change·해소 알림, unchanged warning 억제, 정제된 instance key·symbol·case code·비교 수량, best-effort publish와 B/C·변경·해소 테스트를 요구한다. issue는 2026-08-23 생성되었고 조사 시점에 comment 없이 open 상태였다. +- 조사 기준 `trex-core` revision은 `24a64b55d17263ade8d59f30af50d2a0e5220eae`이며 working tree는 Line 시작 전 clean이었다. issue #64 또는 reconciliation alarm을 참조하는 open/all PR 검색 결과는 없었다. +- `trex_core/application/reconciliation.py:100-199`의 `reconcile_core_holdings()`는 `alarm_emitter`를 받지만 B-case에서는 structured WARNING log와 `warn_store.set_warned(case_code="B")`만 수행한다. 같은 함수의 A-case broker-flat 경로만 `core_holding_broker_flat` alarm을 발행한다. +- `trex_core/application/reconciliation.py:202-280`의 `reconcile_positions()`는 portfolio-hub `asset_quantity`를 symbol별로 합산해 broker 수량과 비교하고 C-case warning을 저장하지만 `alarm_emitter` parameter나 publish 호출이 없다. +- `trex_core/application/snapshot_scheduler.py:306-334`는 C-case `reconcile_positions()`에 emitter를 전달하지 않고 B-case `reconcile_core_holdings()`에만 emitter를 전달한다. 두 reconciliation 호출의 예외는 scheduler를 중단하지 않도록 각각 swallow·log된다. +- `trex_core/application/alarm_emitter.py:35-93`은 allowlist된 caller fields로 `trex.alerts` envelope를 만들고 `TransportUnavailableError`와 그 밖의 exception을 swallow하는 best-effort 경계를 이미 제공한다. +- `trex_core/application/portfolio.py:356-446`, `trex_core/adapters/portfolio/reconcile_warning_store.py:14-98`, migration `016_create_slot_reconcile_warning.sql`은 active warning을 slot별 durable row로 저장하고 동일 slot의 case/reason을 upsert한다. 현재 contract에는 alert fingerprint, last-notified quantity, resolved event 또는 publish 상태가 없고 `clear_warned()`는 row를 삭제한다. +- active AC-0023은 `instance_key + symbol` 기준 C-case 합계 비교, 전략 RPC 제외, intent 차단과 authoritative 합계 재검증 후 C-case warning clear를 소유한다. active AC-0021은 별도 `ledger_skip`의 deduplicated unresolved/resolved alert lifecycle을 소유하지만 reconciliation mismatch는 범위에 포함하지 않는다. active AC-0012의 dedupe는 미확정 cancel alarm에 한정된다. +- `portfolio-hub` canonical source revision `5d6a0ad013900668930fe947f342e1ad1824edaa`의 `app/schemas/v1.py:302-338`은 `PortfolioItem.asset_quantity`와 portfolio list response를 정의하고, `app/api/v1_portfolios.py:315-363`은 service token의 daemon ownership으로 portfolio 목록을 반환한다. 이는 C-case가 소비하는 portfolio별 수량 source와 instance 경계를 확인한다. 해당 checkout에서 `uv run python -m pytest -o addopts='' -q tests/test_v1_portfolios_list.py tests/test_v1_portfolios_daemon_assignment.py`는 `37 passed, 3 warnings`였다. +- 현재 baseline focused 검증 `uv run pytest -q tests/application/test_reconcile_warning_blocking.py tests/application/test_alarm_emitter.py`는 `58 passed`였다. 기존 tests는 B/C warning·logging과 generic alarm envelope를 각각 검증하지만 B/C alarm, unchanged suppression, quantity change와 resolved event를 결합해 검증하지 않는다. +- Line scaffold preflight 전에 upstream AC-0022의 추가 `## Scope note` heading 때문에 ProofLine 0.9.1 validation이 실패했다. 문구를 바꾸지 않고 heading만 inline scope note로 정규화한 뒤 `proofline validate`와 `proofline line init --dry-run`이 통과했다. 이는 새 capability와 무관한 `housekeeping`이다. + +## Scope + +- AC admission은 reconciliation mismatch의 운영자-visible notification lifecycle을 독립적으로 검증할 장기 active criterion을 `create`하는 후보이며 next allocator 기준 예상 ID는 AC-0024다. AC-0023의 blocking·clear authority나 AC-0021의 ledger-skip alert 의미를 확장 해석해 `satisfy`하지 않는다. +- B-case core-slot↔broker quantity mismatch와 C-case portfolio-hub symbol aggregate↔broker mismatch의 새 탐지에서 `AlarmEmitter`를 통해 sanitized `trex.alerts` event를 발행한다. +- alert dedupe identity는 B-case에서 slot별, C-case에서 `instance_key + symbol`별이다. 동일한 active mismatch의 반복 scheduler tick은 Telegram message로 이어질 event를 재발행하지 않는다. 동일 identity의 case별 비교 quantity tuple이 바뀌면 material change event를 한 번 발행한다. +- recovery/resolved event는 기존 durable warning clear 경로인 B-case operator `sync`, C-case operator `verify`의 symbol aggregate 재검증에서만 발행한다. scheduler의 수량 일치 관찰은 durable warning을 자동 clear하거나 recovery를 발행하지 않는다. +- unchanged suppression은 process restart 뒤에도 유지하는 durable dedupe다. event fingerprint는 alarm publish가 성공한 뒤에만 기록하며 publish 실패 시 reconciliation을 중단하지 않고 다음 detection에서 다시 시도할 수 있어야 한다. +- event에는 최소 `instance_key`, `symbol`, `case_code`, broker quantity와 case별 비교 quantity를 포함하되 account number/hash, credential, token, DSN, raw broker/portfolio-hub payload와 exception 본문은 포함하지 않는다. +- alarm publish 실패는 best-effort로 swallow되어 detection, warning persistence·clear, scheduler 진행과 slot 상태에 영향을 주지 않는다. +- repository-local tests에서 B/C 최초 탐지, unchanged suppression, 비교 수량 변경, resolution, publish failure, 민감 필드 부재를 독립적으로 검증한다. + +## Out of Scope + +- B/C mismatch 판정식, `case_code` 의미, AC-0023의 C-case 전략 RPC 제외·intent 차단과 기존 warning clear authority 변경 +- broker, core slot 또는 portfolio-hub 수량의 자동 보정과 operator `sync`/`verify` 승인 절차 변경 +- A-case broker-flat 및 기존 alarm inventory의 의미 변경 +- `trex-notifier` 자체의 Telegram routing·message rendering 구현, RabbitMQ topology 변경, production credential·배포·실계좌 검증 +- push, PR, merge, release, issue closure와 production migration + +## Risks and Unknowns + +- slot별 warning row를 그대로 event identity로 사용하면 C-case의 동일 `instance_key + symbol`에 속한 여러 portfolio/slot이 Telegram 알림을 중복 생성할 수 있다. 반대로 symbol 단일 identity는 B-case에서 여러 slot의 독립 수량 차이를 잃을 수 있다. +- 현재 warning upsert는 `updated_at`과 reason을 매 tick 갱신하고 clear 시 row를 삭제하므로, 단순 timestamp 비교로는 unchanged suppression이나 restart-safe resolved event를 증명할 수 없다. +- event fingerprint를 alarm publish 성공 뒤에만 기록해야 하므로 publish 성공 판정과 durable dedupe write 사이의 failure window에서 duplicate 가능성이 있다. 이 경계는 reconciliation transaction과 결합하지 않으면서 retry 가능한 at-least-once 성질을 보존하도록 구현·검증해야 한다. +- C-case 해소는 AC-0023에 따라 symbol aggregate 재검증과 C-case-only clear가 필요하다. scheduler의 process-local `SnapshotStore.warning` clear를 durable recovery authority로 오인하면 premature recovery event가 발생할 수 있다. + +### Confirmed Decisions + +- B-case dedupe identity는 slot별이고 C-case dedupe identity는 `instance_key + symbol`별이다. +- recovery/resolved authority는 기존 durable clear 경로(B=`sync`, C=`verify`)로 제한한다. +- material change는 동일 identity의 case별 비교 quantity tuple 변경이다. +- unchanged suppression은 restart-safe durable dedupe이며 event fingerprint는 publish 성공 뒤에만 기록한다. 실패한 publish는 business path를 중단하지 않고 다음 detection에서 재시도한다. +- 요청자 genichin이 2026-08-25 위 결정을 명시하고 dcy-0007 승인을 요청했다. Open Questions는 모두 canonical Scope와 Risks에 반영되어 해소되었다. diff --git a/.proofline/lines/line-0007/line-0007.md b/.proofline/lines/line-0007/line-0007.md new file mode 100644 index 0000000..2491e92 --- /dev/null +++ b/.proofline/lines/line-0007/line-0007.md @@ -0,0 +1,3 @@ +--- +id: "line-0007" +--- diff --git a/.proofline/lines/line-0007/req-0007.md b/.proofline/lines/line-0007/req-0007.md new file mode 100644 index 0000000..2572fbe --- /dev/null +++ b/.proofline/lines/line-0007/req-0007.md @@ -0,0 +1,50 @@ +--- +id: "req-0007" +status: approved +discovery: "dcy-0007" +criteria: + create: + - "ac-0024" + update: [] + retire: [] + satisfy: [] +--- + +# Reconciliation 수량 불일치 알림 + +## Objective + +`trex-core`가 B-case(core slot↔broker)와 C-case(portfolio-hub symbol aggregate↔broker) 수량 불일치의 최초 탐지, material quantity change와 authoritative resolution을 중복 폭주 없이 `trex.alerts`에 발행하게 한다. 알림 lifecycle은 process restart 뒤에도 이어지고 publish 실패는 다음 detection에서 재시도할 수 있어야 하며, reconciliation·warning·slot·broker·portfolio-hub의 기존 동작과 권한을 중단하거나 변경하지 않는다. + +## Scope + +- B-case mismatch는 stable slot identity별로 추적하고, C-case mismatch는 `instance_key + symbol`별로 추적한다. C-case의 동일 symbol에 여러 portfolio/slot이 있어도 하나의 aggregate mismatch lifecycle로 취급한다. +- B-case 비교 quantity tuple은 core slot quantity와 broker quantity이고, C-case 비교 quantity tuple은 같은 instance·symbol의 portfolio-hub `asset_quantity` 합계와 broker quantity다. +- 이전에 성공적으로 알린 fingerprint가 없는 active mismatch는 detected event를 발행한다. 같은 identity와 같은 quantity tuple의 scheduler 반복 탐지는 event를 재발행하지 않는다. +- active mismatch의 case별 quantity tuple이 바뀌면 changed event를 한 번 발행하고 성공한 새 fingerprint를 durable하게 보존한다. +- B-case recovery/resolved event는 operator `sync`가 broker-authoritative quantity로 warning을 clear하는 경로에서만 발행한다. C-case recovery/resolved event는 operator `verify`가 `instance_key + symbol` aggregate 일치를 재검증하고 C-case warning을 clear하는 경로에서만 발행한다. +- scheduler가 일치 quantity를 관찰하거나 process-local warning을 clear하는 것만으로 durable warning을 자동 clear하거나 recovery event를 발행하지 않는다. +- detected·changed·resolved event는 lifecycle state를 구분하고 `instance_key`, `symbol`, `case_code`, broker quantity 및 case별 비교 quantity를 포함한다. B-case identity에는 stable slot identifier를 포함할 수 있으나 account number/hash, credential, token, DSN, raw external payload 또는 exception 본문은 포함하지 않는다. +- successful publish 뒤에만 해당 lifecycle fingerprint를 durable하게 기록한다. detected/changed publish가 실패하면 다음 동일 detection에서 재시도한다. resolved publish가 실패해도 warning clear는 진행하며, notification state가 writable이면 pending resolved transition을 보존하여 후속 retry opportunity에서 재시도한다. +- 기존 `AlarmEmitter`와 `trex.alerts` envelope·routing contract를 재사용하고 새 event가 `trex-notifier`의 기존 alarm 소비 경계에 도달하게 한다. +- B/C 최초 탐지, C-case 다중 portfolio 단일 event, unchanged suppression, quantity change, restart, B sync resolution, C verify resolution, publish failure retry와 sensitive field 부재를 repository-local automated tests로 검증한다. + +## Constraints + +- AC-0023의 C-case `instance_key + symbol` aggregate 판정, strategy RPC 제외, intent 차단, C-case-only clear와 operator authority를 완화하거나 대체하지 않는다. +- B/C warning의 `case_code`, mismatch 판정식, slot lifecycle 및 broker/portfolio-hub quantity는 알림을 위해 변경하지 않는다. +- notification state는 restart-safe하고 replay-safe한 persistence로 관리해야 하며 기존 migration runner의 반복 적용을 견뎌야 한다. +- publish success와 durable fingerprint write 사이의 failure window에서는 at-least-once delivery에 따른 duplicate가 가능하지만, fingerprint를 publish 시도 전에 기록하여 event를 영구 유실시키는 방식은 허용하지 않는다. +- alarm publish 및 notification-state persistence failure는 reconciliation, warning set/clear, operator sync/verify와 scheduler를 abort시키지 않는 best-effort 경계여야 한다. resolved publish가 실패하고 notification state가 writable이면 pending transition을 보존하지만, notification-state persistence 자체가 실패하면 authoritative warning clear를 되돌리거나 지연하지 않으며 해당 event 유실 가능성을 sanitized log로 남긴다. +- 기존 alarm consumer와 wire compatibility를 유지하며 RabbitMQ topology 또는 `trex-notifier` message rendering 변경을 요구하지 않는다. +- credential, account 식별정보, token, DSN 및 raw KIS/portfolio-hub payload를 test fixture, event, log 또는 evidence에 기록하지 않는다. +- Normative Design Documents: 없음. 이 REQ와 AC-0024가 normative specification을 소유한다. + +## Non-Goals + +- B/C mismatch 판정, warning persistence·blocking·clear policy 또는 AC-0023 수정 +- scheduler 관찰에 의한 automatic warning clear 또는 recovery 발행 +- broker/core/portfolio-hub quantity 자동 보정과 operator `sync`/`verify` 승인 절차 변경 +- A-case broker-flat 및 기존 alarm inventory의 semantics 변경 +- `trex-notifier`의 Telegram routing·rendering 구현, RabbitMQ topology 변경과 production delivery 검증 +- live credential·account·database·RabbitMQ·portfolio-hub 연결, production migration, deployment, release 또는 issue closure