Merge remote-tracking branch 'origin/main' into copilot/feat-reconcile-unmanaged-position-durable-lifecycl
# Conflicts: # .proofline/criteria/ac-0029.md # .proofline/identities.json # tests/adapters/persistence/test_migration_and_drift.py Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
@@ -110,13 +110,20 @@
|
||||
"last_seen": "ISO-8601 | null"
|
||||
},
|
||||
"paused": "bool",
|
||||
"reconcile_warned": "bool", // durable B/C warning 또는 unknown case 존재
|
||||
"reconcile_warning_cases": ["B", "C"], // 활성 case code 목록
|
||||
"trade_eligible": "bool", // known blocking/unknown case가 없을 때만 true
|
||||
"strategy_name": "str | null", // REQ-116 additive (BAT-055) — portfolio-hub PortfolioItem 출처
|
||||
"strategy_params": "dict", // REQ-116 additive (BAT-055) — portfolio-hub opaque dict; {} if absent
|
||||
"markets": ["str"] // REQ-116 additive (BAT-055) — portfolio-hub markets list; [] if absent
|
||||
}
|
||||
],
|
||||
"excluded_slots": {
|
||||
"reconcile_quantity_mismatch": "int",
|
||||
"active_reconcile_warning_cases": {"B": "int", "C": "int"}
|
||||
},
|
||||
"degraded": "bool",
|
||||
"reason": "portfolio_hub_unavailable | snapshot_fetch_failed | cash_position_stale | null",
|
||||
"reason": "portfolio_hub_unavailable | snapshot_fetch_failed | cash_position_stale | warn_store_unavailable | null",
|
||||
"message": "str | null" // sanitized — no credentials/raw exceptions
|
||||
}
|
||||
```
|
||||
@@ -138,11 +145,14 @@
|
||||
| `warned` | 파생 | `slot_quantity != asset_quantity` (REQ-074 AC-2) |
|
||||
| `watchdog` | WatchdogStore | 만료 여부 + last_seen; null=store 미연결 |
|
||||
| `paused` | PauseStore | durable pause overlay (REQ-032) |
|
||||
| `reconcile_warned`, `reconcile_warning_cases` | ReconcileWarningStore | 동일 slot의 durable B/C 원인을 동시에 보존·노출 |
|
||||
| `trade_eligible` | 파생(safety gate) | 활성 B/C 또는 지원하지 않는 warning case가 있으면 false |
|
||||
| `strategy_name` | hub (PortfolioItem via SnapshotStore) | REQ-116 additive. portfolio-hub 정본. hub 미제공 시 null |
|
||||
| `strategy_params` | hub (PortfolioItem via SnapshotStore) | REQ-116 additive. opaque dict; hub 미제공 시 {} |
|
||||
| `markets` | hub (PortfolioItem via SnapshotStore) | REQ-116 additive. list[str]; hub 미제공 시 [] |
|
||||
|
||||
- **slot-origin strategy 필드 없음** (REQ-074 AC-5, REQ-116 additive 재도입 후에도 유지): `strategy_id`, `sell_strategy_id`, `active_strategy_id` 는 응답에 포함되지 않는다. `strategy_name`/`strategy_params`/`markets` 는 PortfolioItem(hub) 출처이므로 AC-5 제외 대상과 구분된다 — REQ-116 Notes 참조.
|
||||
- **Reconcile warning fail-closed**: B/C warning이 활성인 holding slot은 응답에서 제외된다. warning store 조회 실패, empty metadata, unknown case는 `slots=[]`, `degraded=true`, `reason="warn_store_unavailable"`로 처리한다. intent admission도 동일 durable source를 재확인하며 unknown case를 거래 허용 근거로 사용하지 않는다.
|
||||
- **Breaking change (holding `qty`) rollout**: 기존 전략 클라이언트가 `qty=slot mirror`로 가정했다면 `slot_quantity`를 읽도록 전환해야 한다. 권장 경로는 (1) 클라이언트에 `qty_source`/`broker_quantity_available` 체크 추가 → (2) fail-closed (`qty is null`) 처리 배포 → (3) `qty`를 broker 정본으로 사용하도록 cutover 순서다.
|
||||
- `spy=false`(기본): holding 슬롯 응답 후 `WatchdogStore.update(slot_id)` 호출 — 전략 폴링 = 생존 신호.
|
||||
- `spy=true`: watchdog 갱신 건너뜀 — 운영자 CLI(`universe`/`slots`) 가 항상 사용.
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
1. 신규 자산(portfolio_id 에 슬롯 없음) → `inactive` 영구 슬롯 생성(멱등 — `ON CONFLICT (instance_key, portfolio_id)` partial unique).
|
||||
2. universe 이탈 + inactive → soft-retire(`retired_at` set; 행 보존, 운영 뷰 `universe_detached`). 이탈 + holding/pending → tombstone(그대로 유지 — safety-sell 경로 보존).
|
||||
3. 가드: universe 빈 응답이면 reconcile skip(degrade 가드); 직전 대비 ≥50% 축소면 retire 보류(`allow_retire=False`, 대량 소실 가드 — dcy-020 OQ-2 D3).
|
||||
4. 이어서 `reconcile_core_holdings`(REQ-062/121, req-0010/ac-0029): 브로커 잔고 ground truth 로 core holding 검증 — core holding/브로커 0 → slot `error` 격리+WARN, 수량 불일치 → WARN(상태 불변), 브로커 보유인데 대상 instance에 관리 slot이 없으면 `unmanaged_position`, 동일 instance에 `inactive`/`error` 등 non-holding 관리 slot이 있으면 `managed_non_holding_divergence`를 WARN으로 남긴다. `managed_non_holding_divergence`는 기존 warning 경로를 유지하고, `unmanaged_position`은 `(instance_key, symbol)` durable lifecycle state/audit에 기록된다. 같은 수량 반복 관측은 `last_seen_at`만 갱신하고 새 detect/change alarm을 만들지 않는다. 어떤 broker-only 분류도 주문·청산·slot 생성·warning 자동 삭제를 하지 않는다.
|
||||
4. 이어서 `reconcile_core_holdings`(REQ-062/121): 브로커 잔고 ground truth 로 core holding 검증 — core holding/브로커 0 → slot `error` 격리+WARN, 수량 불일치 → WARN(상태 불변), 브로커 보유인데 대상 instance에 관리 slot이 없으면 `unmanaged_position`, 동일 instance에 `inactive`/`error` 등 non-holding 관리 slot이 있으면 `managed_non_holding_divergence`를 WARN으로 남긴다. `managed_non_holding_divergence`는 기존 warning 경로를 유지하고, `unmanaged_position`은 `(instance_key, symbol)` durable lifecycle state/audit에 기록된다. 같은 수량 반복 관측은 `last_seen_at`만 갱신하고 새 detect/change alarm을 만들지 않는다. 어떤 broker-only 분류도 주문·청산·slot 생성·warning 자동 삭제를 하지 않는다.
|
||||
|
||||
### Broker provider and operator balance-read barrier (BAT-057 / REQ-119/120)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user