diff --git a/.claude/commands/bootstrap-baseline.md b/.claude/commands/bootstrap-baseline.md deleted file mode 100644 index 03afeef..0000000 --- a/.claude/commands/bootstrap-baseline.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/bootstrap-baseline/SKILL.md` exactly. diff --git a/.claude/commands/capture-release-feedback.md b/.claude/commands/capture-release-feedback.md deleted file mode 100644 index 03266f9..0000000 --- a/.claude/commands/capture-release-feedback.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/capture-release-feedback/SKILL.md` exactly. diff --git a/.claude/commands/change-req.md b/.claude/commands/change-req.md deleted file mode 100644 index 20fa644..0000000 --- a/.claude/commands/change-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/change-req/SKILL.md` exactly. diff --git a/.claude/commands/confirm-batch-verification.md b/.claude/commands/confirm-batch-verification.md deleted file mode 100644 index 6e72a0b..0000000 --- a/.claude/commands/confirm-batch-verification.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-batch-verification/SKILL.md` exactly. diff --git a/.claude/commands/confirm-discovery.md b/.claude/commands/confirm-discovery.md deleted file mode 100644 index 4b36987..0000000 --- a/.claude/commands/confirm-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-discovery/SKILL.md` exactly. diff --git a/.claude/commands/confirm-release.md b/.claude/commands/confirm-release.md deleted file mode 100644 index fea9962..0000000 --- a/.claude/commands/confirm-release.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-release/SKILL.md` exactly. diff --git a/.claude/commands/confirm-req-implemented.md b/.claude/commands/confirm-req-implemented.md deleted file mode 100644 index d9e90b5..0000000 --- a/.claude/commands/confirm-req-implemented.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-req-implemented/SKILL.md` exactly. diff --git a/.claude/commands/confirm-req.md b/.claude/commands/confirm-req.md deleted file mode 100644 index 558f868..0000000 --- a/.claude/commands/confirm-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-req/SKILL.md` exactly. diff --git a/.claude/commands/draft-batch-design.md b/.claude/commands/draft-batch-design.md deleted file mode 100644 index 2674265..0000000 --- a/.claude/commands/draft-batch-design.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-design/SKILL.md` exactly. diff --git a/.claude/commands/draft-batch-planning.md b/.claude/commands/draft-batch-planning.md deleted file mode 100644 index cd5261b..0000000 --- a/.claude/commands/draft-batch-planning.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-planning/SKILL.md` exactly. diff --git a/.claude/commands/draft-batch-verification.md b/.claude/commands/draft-batch-verification.md deleted file mode 100644 index 7758a67..0000000 --- a/.claude/commands/draft-batch-verification.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-verification/SKILL.md` exactly. diff --git a/.claude/commands/draft-batch.md b/.claude/commands/draft-batch.md deleted file mode 100644 index 66d1a06..0000000 --- a/.claude/commands/draft-batch.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch/SKILL.md` exactly. diff --git a/.claude/commands/draft-release.md b/.claude/commands/draft-release.md deleted file mode 100644 index 40c6826..0000000 --- a/.claude/commands/draft-release.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-release/SKILL.md` exactly. diff --git a/.claude/commands/draft-req.md b/.claude/commands/draft-req.md deleted file mode 100644 index 29b91bb..0000000 --- a/.claude/commands/draft-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-req/SKILL.md` exactly. diff --git a/.claude/commands/new-discovery.md b/.claude/commands/new-discovery.md deleted file mode 100644 index 970e28a..0000000 --- a/.claude/commands/new-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/new-discovery/SKILL.md` exactly. diff --git a/.claude/commands/review-discovery.md b/.claude/commands/review-discovery.md deleted file mode 100644 index 255c040..0000000 --- a/.claude/commands/review-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/review-discovery/SKILL.md` exactly. diff --git a/.claude/commands/run-batch-delivery.md b/.claude/commands/run-batch-delivery.md deleted file mode 100644 index 4c1c9ce..0000000 --- a/.claude/commands/run-batch-delivery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-batch-delivery/SKILL.md` exactly. diff --git a/.claude/commands/run-batch-implementation.md b/.claude/commands/run-batch-implementation.md deleted file mode 100644 index 9014da3..0000000 --- a/.claude/commands/run-batch-implementation.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-batch-implementation/SKILL.md` exactly. diff --git a/.claude/commands/run-sdlc.md b/.claude/commands/run-sdlc.md deleted file mode 100644 index e8cdb83..0000000 --- a/.claude/commands/run-sdlc.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-sdlc/SKILL.md` exactly. diff --git a/.claude/commands/suggest-batch-reqs.md b/.claude/commands/suggest-batch-reqs.md deleted file mode 100644 index b4a7b1d..0000000 --- a/.claude/commands/suggest-batch-reqs.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/suggest-batch-reqs/SKILL.md` exactly. diff --git a/.claude/commands/suggest-next-discovery.md b/.claude/commands/suggest-next-discovery.md deleted file mode 100644 index 16198ca..0000000 --- a/.claude/commands/suggest-next-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/suggest-next-discovery/SKILL.md` exactly. diff --git a/.claude/skills/run-trex-core-daemon/SKILL.md b/.claude/skills/run-trex-core-daemon/SKILL.md deleted file mode 100644 index f92ff5b..0000000 --- a/.claude/skills/run-trex-core-daemon/SKILL.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -name: run-trex-core-daemon -description: "Launch the trex-core daemon against the real PostgreSQL + RabbitMQ infrastructure and drive it via the messaging surfaces (snapshot RPC, intent admission RPC, slot events). Use when asked to run trex-core, smoke-test the daemon, confirm a change works end-to-end, or reproduce the BAT-007 transport lifecycle." ---- - -# Run trex-core (sync + thread daemon over RabbitMQ) - -The daemon (BAT-007) has **no HTTP surface**. All strategy-facing traffic goes -over RabbitMQ — snapshot RPC on `trex.snapshot.req`, intent admission RPC on -`trex.intent.req`, slot lifecycle events on the `trex.events` topic exchange. -"Run" means: boot the daemon in the background, drive it with messages, look -at the responses + the events. - -## Prerequisites - -The daemon needs three live services. Verify before launching: - -```bash -.venv/bin/python - <<'PY' -import os, psycopg, pika -from dotenv import load_dotenv -load_dotenv("config/.env") # BAT-026: .env lives in the config dir -psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"], connect_timeout=3).close() -print("postgres OK") -p = pika.URLParameters(os.environ["TREX_RABBITMQ_URL"]); p.socket_timeout = 3 -pika.BlockingConnection(p).close() -print("rabbitmq OK") -PY -``` - -If either fails, fix the infra first — the daemon will not boot without -`TREX_POSTGRESQL_DSN` and will boot silently degraded if `TREX_RABBITMQ_URL` -is missing (stub `InMemoryTransport` — strategy modules can't reach it). - -### Required env (`config/.env`, gitignored) + per-instance TOML - -| Variable | Required | Notes | -|---|---|---| -| `TREX_POSTGRESQL_DSN` | yes | psycopg DSN | -| `TREX_RABBITMQ_URL` | yes for real use | e.g. `amqp://admin:admin@localhost:5672/` | -| `TREX_BROKER` | no | `paper` (default) or `kis` | -| `TREX_EXECUTION_INTERVAL_SEC` | no | default 1.0 | -| `TREX_LEDGER_INTERVAL_SEC` | no | default 1.0 | -| `TREX_PORTFOLIO_HUB_BASE_URL` | no | if set, real portfolio-hub universe+ledger; else stub universe (pf-005930/pf-000660) + in-memory ledger | - -`--config ` (required since BAT-013) resolves `config/.toml`: -`instance_key` (queue names: `trex.intent.req.` etc.; `default` keeps -the literal names) and `max_active_slots` (M cap; unset = no cap). - -**BAT-030 (asset-centric)**: there is NO daily preallocation. Permanent asset -slots (one per universe portfolio, status `inactive`) are created by the -snapshot scheduler's universe reconcile right after boot. Slot states are -`inactive / pending_buy / holding / pending_sell / error` — `idle`/`closed` -no longer exist. - -## Launch - -Background, redirect log (smoke against the test DB: point -TREX_POSTGRESQL_DSN at `trex_core_test` via `--env-file`, never the live DB): - -```bash -.venv/bin/trex-core-daemon --config &> /tmp/trex-core.log & -DAEMON_PID=$! -``` - -The daemon does not bind any TCP port; "ready" means **four worker threads -running and all RabbitMQ topology declared**. Poll for the topology to -appear: - -```bash -for i in {1..15}; do - .venv/bin/python - <<'PY' && break -import pika, sys -p = pika.URLParameters("amqp://admin:admin@localhost:5672/"); p.socket_timeout = 2 -c = pika.BlockingConnection(p) -try: - ch = c.channel() - # passive=True: succeeds only if the daemon already declared it - ch.queue_declare("trex.intent.req", durable=True, passive=True) - ch.queue_declare("trex.snapshot.req", durable=True, passive=True) - ch.exchange_declare("trex.events", exchange_type="topic", durable=True, passive=True) - print("daemon ready") - sys.exit(0) -except Exception as exc: - print(f"not ready: {exc}"); sys.exit(1) -finally: - c.close() -PY - sleep 0.5 -done -``` - -`/tmp/trex-core.log` has the pika INFO chatter — first connection lines mean -boot succeeded; absence of `ERROR`/exception traceback means topology declare -worked. - -## Drive - -The canonical end-to-end smoke is in `smoke.py` — copy/adapt it. It exercises: - -1. **snapshot RPC** (view=all) — daemon answers; `slots[]` holds the permanent - asset slots from the universe reconcile (status `inactive`). -2. **buy intent RPC keyed by `slot_id`** (REQ-061 canonical; `portfolio_id` - dual-accept) — admitted, slot → `pending_buy`. Rejections: `unknown_slot`, - `already_active`, `capacity_full` (M cap). -3. **duplicate buy** with the *same* `intent_id` — identical response, - no double transition (intent_log dedup gate, REQ-023 AC-1). -4. **wait one execution loop tick** — paper broker fills, slot → `holding`. -5. **sell intent RPC** on the holding slot — fill returns the slot to - `inactive` (occupancy cleared, asset binding kept — NOT `closed`). - Sell dispatch quantity is the **broker balance** (REQ-062); broker flat → - the sell is skipped with a WARN. - -Run it (instance key optional, default `default`): - -```bash -.venv/bin/python .claude/skills/run-trex-core-daemon/smoke.py [instance_key] -``` - -Sample output (paper broker, stub universe): - -``` -=== 1) snapshot RPC === -assets=1 slots=2 available_slots=1 -target slot_id=8a160254-… portfolio_id=pf-005930 -=== 2) buy by slot_id === --> admitted reason=None -=== 3) duplicate buy (same intent_id) === -identical: True -=== 4) wait for fill === -slot status=holding qty=10.0 avg_price=1000.0 -=== 5) sell intent === --> admitted -slot status=inactive qty=None portfolio_id=pf-005930 (asset binding kept) -DONE -``` - -### Subscribing to events (timing caveat) - -If you subscribe to `trex.events` *after* the publish happens, the message -is gone — topic exchanges drop unroutable publishes. Production strategy -modules have their per-strategy queue bound from boot, so they don't miss -anything; ad-hoc subscribers need to declare + bind their queue **before** -triggering the intent. `smoke.py` does this correctly (declares queue, then -sleeps 0.6s, then runs the RPC). - -## Stop - -```bash -kill -TERM $DAEMON_PID -# or if you've lost the PID: -pkill -f trex-core-daemon -``` - -The daemon installs SIGINT/SIGTERM handlers that set the stop event, -join all worker threads, and `close()` every RabbitMQ connection — last -log line should be `Normal shutdown` from pika. Exit code 0. - -## Inspecting state - -The daemon's source of truth is PostgreSQL `daily_slot`: - -```bash -.venv/bin/python - <<'PY' -import os, psycopg -from dotenv import load_dotenv -load_dotenv("config/.env") -with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c: - with c.cursor() as cur: - cur.execute(""" - SELECT instance_key, status, COUNT(*) - FROM daily_slot GROUP BY instance_key, status ORDER BY 1, 2 - """) - for r in cur.fetchall(): print(r) -PY -``` - -Ledger outbox state (BAT-006): - -```bash -.venv/bin/python - <<'PY' -import os, psycopg -from dotenv import load_dotenv -load_dotenv("config/.env") -with psycopg.connect(os.environ["TREX_POSTGRESQL_DSN"]) as c: - with c.cursor() as cur: - cur.execute("SELECT status, last_outcome, COUNT(*) FROM ledger_outbox GROUP BY 1,2 ORDER BY 1,2") - for r in cur.fetchall(): print(r) -PY -``` - -## Troubleshooting - -| Symptom | Cause | Fix | -|---|---|---| -| `slots returned: 0` from snapshot | universe reconcile hasn't run (portfolio-hub down / boot race) | check the scheduler log line `slot reconcile: {'created': N, ...}`; with no hub config the stub universe gives 2 slots | -| intent gets `rejected: unknown_slot` / `unknown_portfolio` | slot_id/portfolio_id not in this instance's slot set | read `slots[]` from a snapshot RPC first and reuse its `slot_id` | -| intent gets `rejected: capacity_full` | `max_active_slots` (M) reached — FCFS, no eviction | sell something first or raise M in the instance TOML | -| sell never dispatches, WARN `sell dispatch skipped` | broker balance 0 for the symbol (REQ-062 divergence) | reconcile the divergence (operator); no mirror fallback by design | -| `RpcTimeoutError` on the client | daemon not up, or consumer thread crashed | check `/tmp/trex-core.log` for exception trace | -| `TransportUnavailableError` | RabbitMQ down | the daemon does NOT buffer locally (REQ-024) — fix the broker, then re-issue | -| fill event missed by ad-hoc subscriber | queue bound *after* publish | bind queue + sleep ≥0.5s before triggering the intent | -| import-time `ModuleNotFoundError: pika` | dev deps not installed | `.venv/bin/pip install -e ".[dev]"` | - -## Related docs - -- `docs/interface-contract.md` — full RabbitMQ topology + message payloads -- `docs/runtime-flows.md` — flow diagrams (snapshot RPC, intent RPC, events, degrade branch) -- `docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/` — the batch that introduced this runtime shape diff --git a/.claude/skills/run-trex-core-daemon/smoke.py b/.claude/skills/run-trex-core-daemon/smoke.py deleted file mode 100644 index b30bbbc..0000000 --- a/.claude/skills/run-trex-core-daemon/smoke.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -"""End-to-end smoke for trex-core (BAT-030 asset-centric). - -Assumes the daemon is already running with `--config ` (see SKILL.md). -Drives every RabbitMQ surface of the asset-centric model: - - 1. snapshot RPC (view=all) — daemon answers; slots[] = permanent - asset slots created by the universe - reconcile (status `inactive`) - 2. buy intent RPC keyed by slot_id — admission + slot → pending_buy - (REQ-061 canonical key; portfolio_id is the legacy dual-accept) - 3. duplicate buy (same intent_id) — intent_log dedup gate (REQ-023 AC-1) - 4. wait for execution tick + fill — slot → holding - 5. sell intent RPC — slot → pending_sell → fill → - `inactive` (occupancy cleared, - asset binding kept; NOT 'closed') - -Pass the instance key as argv[1] (default: "default"). Env from config/.env. -""" -from __future__ import annotations - -import os -import sys -import time -import uuid - -from dotenv import load_dotenv - -load_dotenv("config/.env") - -from trex_core.adapters.messaging import topology # noqa: E402 -from trex_core.adapters.messaging.rabbitmq import RabbitMQTransport # noqa: E402 - -URL = os.environ.get("TREX_RABBITMQ_URL", "amqp://admin:admin@localhost:5672/") -INST = sys.argv[1] if len(sys.argv) > 1 else "default" - - -def main() -> int: - mq = RabbitMQTransport(URL, timeout=10.0) - snap_q = topology.slots_queue(INST) - intent_q = topology.intent_queue(INST) - - def snap(): - return mq.call_rpc( - snap_q, - {"view": "all", "actor": "operator", "touch_watchdog": False}, - correlation_id=str(uuid.uuid4()), timeout=8.0, - ) - - def intent(payload): - cid = payload.get("intent_id") or str(uuid.uuid4()) - return mq.call_rpc(intent_q, payload, correlation_id=cid, timeout=8.0) - - print("=== 1) snapshot RPC ===") - s = snap() - slots = s["slots"] - available = [x for x in slots if x["status"] == "inactive" and not x["universe_detached"]] - print(f"slots={len(slots)} available_slots={len(available)}") - inactive = [x for x in slots if x["status"] == "inactive" - and not x["universe_detached"]] - if not inactive: - print("no admittable (inactive) asset slot — universe reconcile not " - "done yet, or all assets active") - return 1 - target = inactive[0] - sid = target["slot_id"] - print(f"target slot_id={sid} portfolio_id={target['portfolio_id']}") - - print("=== 2) buy by slot_id ===") - iid = str(uuid.uuid4()) - d = intent({"intent_type": "buy", "intent_id": iid, "slot_id": sid, - "qty": 10, "entry_price": 1000, "strategy_id": "STRAT-A"}) - print(f"-> {d['status']} reason={d['reason']}") - if d["status"] != "admitted": - return 1 - - print("=== 3) duplicate buy (same intent_id) ===") - d2 = intent({"intent_type": "buy", "intent_id": iid, "slot_id": sid, "qty": 10}) - print(f"identical: {d2 == d}") - - print("=== 4) wait for fill ===") - for _ in range(20): - time.sleep(0.5) - st = next(x for x in snap()["slots"] if x["slot_id"] == sid) - if st["status"] == "holding": - break - print(f"slot status={st['status']} qty={st['qty']} avg_price={st['avg_price']}") - - print("=== 5) sell intent ===") - d3 = intent({"intent_type": "sell", "intent_id": str(uuid.uuid4()), - "slot_id": sid}) - print(f"-> {d3['status']}") - for _ in range(20): - time.sleep(0.5) - st = next(x for x in snap()["slots"] if x["slot_id"] == sid) - if st["status"] == "inactive": - break - print(f"slot status={st['status']} qty={st['qty']} " - f"portfolio_id={st['portfolio_id']} (asset binding kept)") - - mq.close() - print("DONE") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 623cf73..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,80 +0,0 @@ -# Copilot Instructions - -이 파일은 호스트 프로젝트 고유 규칙과 StagePilot 규칙을 함께 담는다. - -## Host Project Rules - -호스트 프로젝트 전용 규칙은 이 섹션에 작성한다. - -## StagePilot - -# SDLC Governance Instructions - -이 워크스페이스의 AI 에이전트는 Agile 중심 반복형 SDLC를 따른다. -모든 작업은 3-phase 구조와 그 안의 단위별 게이트를 기준으로 수행하며, 임의 단계 스킵은 허용되지 않는다(긴급 핫픽스 예외는 별도 규칙 적용). - -## Lifecycle Phases - -1. Requirements -2. Delivery -3. Release & Feedback - -## Governing Units - -1. Discovery: 문제 정의 단위 -2. REQ: 승인 가능한 요구사항 단위 -3. Batch: 함께 계획, 설계, 구현, 검증하는 delivery 단위 -4. Release: 함께 배포하고 운영 환류를 기록하는 단위 - -## Bootstrap Baseline Path - -- 프로젝트 시작 시 baseline 문서와 active index는 `bootstrap-baseline`으로 먼저 초기화한다. -- `bootstrap-baseline`은 3-phase lifecycle 밖의 bootstrap 단계이며 별도 governance unit가 아니다. -- 첫 real Discovery는 baseline 초기화 이후 실제 제품/서비스 변경 주제로 시작한다. - -## Default Flow - -1. Bootstrap: `bootstrap-baseline` (baseline 문서와 active index가 비어 있을 때만) -2. Requirements: `new-discovery` -> `confirm-discovery` -> `draft-req` -> `confirm-req` -> `suggest-batch-reqs` -> `draft-batch` -3. Delivery: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` -4. Release & Feedback: `draft-release` -> `confirm-release` -> `capture-release-feedback` - -## Lightweight Change Path - -- 단일 저위험 `Approved` REQ이고 구조/인터페이스/런타임 흐름 영향이 없으면 `minor-change` fast path를 사용할 수 있다. -- 이 경로는 `suggest-batch-reqs`를 생략하고 `draft-batch`로 바로 `batch-lite` batch를 만든다. -- `batch-lite`는 planning부터 시작하고, design은 필요할 때만 생성한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 선택해 검증 강도를 조정한다. - -## Change Management Extension - -- 기존 REQ 본문을 수정할 때는 `change-req`를 사용해 Change Log 근거를 먼저 남긴다. -- release feedback의 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 분리해 기록한다. -- 다음 반복 후보만 먼저 정리하려면 `suggest-next-discovery`를 사용하고, 실제 문서 생성은 `new-discovery`로 이어 간다. - -## Stage Mapping - -- Discovery는 Requirements phase 안에서 유지한다. -- Planning, Design, Implementation, Verification은 Batch 내부 stage로 다룬다. -- Release와 Operations는 Release & Feedback phase 내부에서 다룬다. -- `review-*` stage command는 더 이상 active flow에 포함하지 않고 대응 `confirm-*` 절차에 흡수한다. -- prompt 기반 stage command는 active 경로에서 사용하지 않고 `.stage-pilot/skills/` 아래 skill entrypoint만 사용한다. - -## Global Rules - -1. 증거 기반: 요구사항, 가정, 리스크, 결정 근거를 문서에 남긴다. -2. 게이트 준수: 각 phase와 단위의 DoR(진입 조건)과 DoD(완료 조건)을 충족해야 다음 단계로 이동한다. -3. 테스트 우선 검증: 구현 결과는 테스트 또는 검증 근거 없이 완료로 간주하지 않는다. -4. 변경 영향도 기록: 코드/문서/운영 영향 범위를 명시한다. -5. 불확실성 우선 해소: 차단 이슈가 있으면 임의 추정 대신 질문 또는 추가 탐색을 수행한다. -6. 운영 환류: 운영 단계 인사이트는 다음 Discovery의 입력으로 반드시 반영한다. - -## Hotfix Exception Path - -긴급 핫픽스는 Requirements/Delivery 설계를 경량화할 수 있다. 단, 아래는 필수다. - -1. 최소 요구사항 정의(증상, 영향, 성공 조건) -2. 최소 검증(재현 테스트 또는 핵심 시나리오 확인) -3. 배포 전 롤백 계획 -4. 배포 후 Postmortem 작성 및 다음 스프린트 환류 - diff --git a/.github/instructions/placeholder-guide.md b/.github/instructions/placeholder-guide.md deleted file mode 100644 index 30b97f5..0000000 --- a/.github/instructions/placeholder-guide.md +++ /dev/null @@ -1,239 +0,0 @@ -# 플레이스홀더 가이드 - -이 파일은 active workspace 기준으로 `.github/templates/`와 `.github/skills/`에서 사용하는 플레이스홀더의 문법과 처리 원칙을 정의한다. - -기본 전제는 아래와 같다. - -- active SDLC 진입점은 skill-only 체계다. -- baseline 문서와 active index 초기화는 `bootstrap-baseline`이 담당하며, 이 단계는 Discovery unit를 만들지 않는다. -- `.github/prompts/`는 active 기준 문서가 아니다. -- active 산출물 경로는 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`다. - -## 1. 문법 규칙 - -| 형식 | 의미 | 예시 | -| --- | --- | --- | -| `{{KEY}}` | 단순 치환 대상 | `{{DISCOVERY_ID}}` | -| `{{KEY:hint}}` | 힌트 포함 치환 대상 | `{{CONFIRMED_AT_KST:TBD}}` | -| `{{KEY:opt1\|opt2}}` | 열거형 선택 값 | `{{DOC_STATUS:draft\|confirmed}}` | - -규칙: - -- 최종 문서에는 `{{...}}` 문법이 남지 않아야 한다. -- 힌트 텍스트는 치환 시 함께 제거한다. -- 사람 승인 없이는 확정할 수 없는 값은 본문에 무리하게 확정하지 말고 Notes 또는 결정 필요 항목으로 남긴다. - -## 2. 처리 주체 - -플레이스홀더는 아래 세 가지 처리 주체 중 하나에 속한다. - -### A. 자동 치환 - -- ID, 생성 시각, 갱신 시각, 상태 기본값처럼 실행 시점에 자동으로 정해지는 값 -- 예: - - `{{DISCOVERY_ID}}` - - `{{CONFIRMED_AT_KST}}` - - `{{BAT_ID}}` - - `{{REL_ID}}` - -### B. AI 근거 기반 치환 - -- 현재 저장소 상태, 상위 문서, 이미 확정된 값으로 채울 수 있는 항목 -- 예: - - Discovery의 현재 상태 요약 - - REQ의 Impacted Area 초안 - - Batch planning의 dependency 정리 - - Release rollout checklist 초안 - -### C. 사람 결정 필요 - -- 정책 선택, 승인자 지정, 우선순위 확정, 외부 환경 정보처럼 사람 판단이 필요한 값 -- 이런 항목은 무리하게 확정하지 않는다. - -## 3. 경로별 플레이스홀더 원칙 - -### 3.0 Bootstrap baseline 템플릿 - -대상 경로: - -- `.github/templates/bootstrap/baseline-seed.yaml` -- `.github/templates/project-structure.md` -- `.github/templates/runtime-flows.md` -- `.github/templates/discovery/index.md` -- `.github/templates/srs/index.md` -- `.github/templates/batches/index.md` -- `.github/templates/releases/index.md` - -처리 원칙: - -- `bootstrap-baseline`은 먼저 `.stage-pilot/bootstrap/baseline.yaml` seed를 만들거나 보강한다. -- greenfield 저장소처럼 관찰 가능한 정보가 부족하면 최소 질문 세트로 seed를 채운다. -- `bootstrap-baseline`은 baseline 문서와 index를 초기화하지만 Discovery 문서를 만들지 않는다. -- baseline 문서는 seed를 source of truth로 렌더링하고, 필요하면 저장소 관찰값으로 보강한다. -- index 문서는 빈 register 골격만 준비하고, 실제 unit 행은 이후 skill이 추가한다. - -주요 키: - -- `{{BASELINE_MODE}}` -- `{{BASELINE_SEED_PATH}}` -- `{{PROJECT_SUMMARY}}` -- `{{PRIMARY_DOMAIN}}` -- `{{TECH_STACK_BULLETS}}` -- `{{PRIMARY_RUNTIME}}` -- `{{PRIMARY_ENTRYPOINT_BULLETS}}` -- `interfaces.contracts[*]` -- `data_model.entities[*]` - -### 3.0.1 선택 cross-cutting 템플릿 - -대상 경로: - -- `.github/templates/interface-contract.md` -- `.github/templates/data-model.md` - -처리 원칙: - -- 이 문서들은 optional cross-cutting reference다. -- active unit의 source of truth를 대체하지 않고, 여러 REQ와 batch에 공통으로 반복되는 계약 정보를 모아 둔다. -- `bootstrap-baseline`은 seed의 `interfaces`와 `data_model` 섹션을 우선 읽고, 값이 없으면 `runtime`, `structure`, `notes`를 바탕으로 기본 초안을 구체적으로 유도한다. -- baseline 수준의 변경이 있을 때만 갱신하고, REQ별 acceptance criteria를 그대로 복사하지 않는다. - -주요 키: - -- `{{BASELINE_MODE}}` -- `{{BASELINE_SEED_PATH}}` -- `{{PROJECT_SUMMARY}}` -- `{{PRIMARY_DOMAIN}}` -- `{{PRIMARY_RUNTIME}}` -- `{{INTERFACE_NAME_1}}` -- `{{INTERFACE_PURPOSE_1}}` -- `{{INTERFACE_SUMMARY_BULLETS}}` -- `{{INTERFACE_INPUTS_1}}` -- `{{INTERFACE_OUTPUTS_1}}` -- `{{INTERFACE_ERRORS_1}}` -- `{{INTERFACE_COMPATIBILITY_1}}` -- `{{INTERFACE_SHARED_CONSTRAINTS_BULLETS}}` -- `{{INTERFACE_CURRENT_GAPS_BULLETS}}` -- `{{PERSISTENCE_BACKEND}}` -- `{{ENTITY_NAME_1}}` -- `{{ENTITY_PURPOSE_1}}` -- `{{ENTITY_SOURCE_1}}` -- `{{ENTITY_LIFECYCLE_1}}` -- `{{MODEL_SUMMARY_BULLETS}}` -- `{{ENTITY_FIELDS_1}}` -- `{{ENTITY_RELATIONSHIPS_1}}` -- `{{CONSISTENCY_RULE_BULLETS}}` -- `{{PERSISTENCE_NOTES_BULLETS}}` -- `{{MODEL_CURRENT_GAPS_BULLETS}}` - -### 3.1 Discovery 템플릿 - -대상 경로: - -- `.github/templates/discovery/discovery.md` -- `docs/discovery/*.md` - -주요 키: - -- `{{DISCOVERY_ID}}` -- `{{ISSUE_NAME}}` -- `{{CREATED_AT_KST}}` -- `{{UPDATED_AT_KST}}` -- `{{FREEZE_HANDOFF_DECISION}}` -- `{{READY_FOR_REQ_DRAFTING}}` -- `{{CONFIRMED_BY}}` -- `{{CONFIRMED_AT_KST}}` - -처리 원칙: - -- Discovery는 REQ 초안 작성으로 넘길 수 있을 때만 freeze handoff를 완료한다. -- Discovery 문서의 용어는 `Planning` 직결이 아니라 `REQ Drafting` 기준으로 유지한다. - -### 3.2 SRS 템플릿 - -대상 경로: - -- `docs/srs/req-template.md` -- `docs/srs/index.md` - -주요 키: - -- `REQ-XXX` -- `Status` -- `Type` -- `Priority` -- `Owner` - -처리 원칙: - -- REQ는 `Proposed -> Approved -> Implemented -> Deprecated` 상태를 따른다. -- `draft-req`는 초안을 만들고, `confirm-req`는 `Proposed -> Approved`, `confirm-req-implemented`는 `Approved -> Implemented` 전환을 담당한다. - -### 3.3 Batch 템플릿 - -대상 경로: - -- `.github/templates/batches/*` -- `docs/batches/*` - -주요 키: - -- `{{BAT_ID}}` -- `Profile` -- Included REQ 목록 -- Source Discovery - -처리 원칙: - -- batch는 Approved REQ만 포함한다. -- batch는 `standard` 또는 `batch-lite` profile을 가진다. -- `batch-lite`는 `minor-change` fast path의 기본 delivery profile이며, 처음에는 `index.md`와 `planning.md`만 만들 수 있다. -- planning, design, implementation, verification 문서는 같은 batch 폴더 아래에서 관리한다. 단, `batch-lite`는 design/implementation/verification을 단계 진입 시점에 생성할 수 있다. -- `confirm-batch-verification` 성공 전에는 `release-candidate`로 올리지 않는다. - -### 3.4 Release 템플릿 - -대상 경로: - -- `.github/templates/releases/*` -- `docs/releases/*` - -주요 키: - -- `{{REL_ID}}` -- `Profile` -- `Change Request Input` -- Included Batch -- Rollout Plan -- Rollback Plan - -처리 원칙: - -- release는 `release-candidate` batch만 포함한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. -- feedback handoff에는 `Discovery Input`, `REQ Input`, `Change Request Input`을 구분해 기록한다. -- `confirm-release`는 release 승인, `capture-release-feedback`는 운영 환류 기록을 담당한다. - -## 4. Active Skill Summary - -| 목적 | Skill | 주요 입력 | 주요 출력 | -| --- | --- | --- | --- | -| Bootstrap baseline 초기화 | `.github/skills/bootstrap-baseline/SKILL.md` | 저장소 루트, baseline/index 존재 여부 | baseline 문서와 active index | -| Discovery 초안 생성 | `.github/skills/new-discovery/SKILL.md` | 이슈/요청 요약 | `docs/discovery/*.md` | -| Discovery 확정 | `.github/skills/confirm-discovery/SKILL.md` | `DISCOVERY_ID` | confirmed Discovery | -| REQ 초안 생성 | `.github/skills/draft-req/SKILL.md` | `DISCOVERY_ID` | `docs/srs//req-*.md` | -| REQ 확정 | `.github/skills/confirm-req/SKILL.md` | `REQ-ID` | Approved REQ | -| REQ 변경 관리 | `.github/skills/change-req/SKILL.md` | `REQ-ID` | Change Log 기반 REQ 갱신 | -| REQ 구현 완료 전환 | `.github/skills/confirm-req-implemented/SKILL.md` | `REQ-ID` 또는 `BAT-ID` | Implemented REQ | -| Batch 추천 | `.github/skills/suggest-batch-reqs/SKILL.md` | `DISCOVERY_ID` 또는 REQ 목록 | batch 후보안 | -| Batch 생성 | `.github/skills/draft-batch/SKILL.md` | Approved REQ 목록 | `docs/batches//` | -| Delivery 진행 | `.github/skills/draft-batch-planning/SKILL.md`, `.github/skills/draft-batch-design/SKILL.md`, `.github/skills/run-batch-implementation/SKILL.md`, `.github/skills/draft-batch-verification/SKILL.md`, `.github/skills/confirm-batch-verification/SKILL.md` | `BAT-ID` | batch 문서와 상태 | -| Release 진행 | `.github/skills/draft-release/SKILL.md`, `.github/skills/confirm-release/SKILL.md`, `.github/skills/capture-release-feedback/SKILL.md` | `BAT-ID` 또는 `REL-ID` | release 문서와 운영 환류 | -| 다음 Discovery 추천 | `.github/skills/suggest-next-discovery/SKILL.md` | `REL-ID` 또는 없음 | follow-up 후보안 | -| 상위 orchestration | `.github/skills/run-sdlc/SKILL.md` | Discovery/REQ/Batch/Release 식별자 | 다음 skill 라우팅 | - -## 5. 금지 사항 - -- active 설명 문서에서 `.github/prompts/`를 기준 경로로 안내하지 않는다. -- `review-*`를 별도 active 단계로 서술하지 않는다. -- `docs/sdlc/`를 신규 active 산출물 경로로 사용하지 않는다. \ No newline at end of file diff --git a/.github/skills/CHANGELOG.md b/.github/skills/CHANGELOG.md deleted file mode 100644 index 6048c0d..0000000 --- a/.github/skills/CHANGELOG.md +++ /dev/null @@ -1,155 +0,0 @@ -# Changelog - -All notable changes to the StagePilot skill pack should be recorded in this file. - -## 2026-05-22 - -### run-batch-delivery `0.7.0` - -- added new orchestration skill `run-batch-delivery` -- defined a 6-step delivery chain for an existing batch: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` -- clarified that the skill resumes from the first incomplete delivery stage and stops at approval blockers instead of forcing progress -- clarified that release drafting remains a separate next step and is recommended, not auto-executed - -### draft-req `1.0.3` - -- bumped `draft-req` from `1.0.2` to `1.0.3` -- normalized the template path section to the same logical `stage-pilot/templates/...` + common resolution format used across the pack -- clarified source vs target path distinction for SRS index and REQ document generation - -### bootstrap-baseline `1.0.1` - -- bumped `bootstrap-baseline` from `1.0.0` to `1.0.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for bootstrap seed, index, and baseline document generation - -### new-discovery `1.0.1` - -- bumped `new-discovery` from `1.0.0` to `1.0.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for discovery document, discovery index, and baseline reference templates - -### draft-release `0.8.1` - -- bumped `draft-release` from `0.8.0` to `0.8.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for release document and release index generation - -### draft-batch `0.9.1` - -- bumped `draft-batch` from `0.9.0` to `0.9.1` -- added `When to use` and `Do not use when` guidance to separate batch creation from recommendation, approval, downstream drafting, and SDLC routing -- clarified that `draft-batch` operates only after people have selected the REQ set to include -- replaced the fixed batch template path wording with logical `stage-pilot/templates/batches/...` paths plus explicit resolution order -- clarified that `minor-change` is an input fast path while the persisted batch profile remains `batch-lite` -- added explicit source-to-target template mapping, source discovery recording rules, `Common Pitfalls`, and a `Verification Checklist` - -### suggest-batch-reqs `0.8.2` - -- bumped `suggest-batch-reqs` from `0.8.0` to `0.8.2` -- added `Do not use when` guidance to separate recommendation from REQ approval, direct batch drafting, minor-change fast paths, and top-level SDLC routing -- added a dedicated `사람 선택 규칙` section so batch candidate adoption stays explicitly human-decided -- strengthened Discovery input handling so linked REQs must be explicit and approved before recommendation proceeds -- added explicit candidate-level batch profile hints for `standard` vs `batch-lite`, including required reasoning -- added `Common Pitfalls` and a `Verification Checklist` - -### confirm-req `0.9.1` - -- bumped `confirm-req` from `0.9.0` to `0.9.1` -- added `Do not use when` guidance to separate approval from drafting, change management, implementation confirmation, and batch planning -- made approval prerequisites explicit, including blocker vs non-blocker handling for `Notes` and open questions -- clarified that approval `Change Log` entries are appended and not written for blocked REQs -- added a local `docs/srs/index.md` preservation rule so existing repository wording/format is updated in place rather than overwritten from template wording -- added `Common Pitfalls` and a `Verification Checklist` - -### draft-req `1.0.2` - -- bumped `draft-req` from `1.0.1` to `1.0.2` -- added `Do not use when` guidance to separate `draft-req` from discovery review, confirmation, change-req, and batch planning stages -- made the confirmed Discovery prerequisite explicit and documented the limited draft-only exception case -- added a `Common Pitfalls` section covering confirmation, NFR splitting, baseline gaps, path confusion, index updates, and source Discovery back-references -- replaced the old validation bullets with a `Verification Checklist` for operational verification after drafting - -### draft-req `1.0.1` - -- bumped `draft-req` from `1.0.0` to `1.0.1` -- replaced fixed template path references with logical template source paths under `stage-pilot/templates/...` -- added explicit template path resolution rules for workspace-local, vendor/subtree, and Hermes external installs -- clarified resolution priority: workspace-local -> vendor/subtree -> Hermes external -- clarified the distinction between template source paths and generated target paths under `docs/srs/` -- added validation checks for logical-path-to-physical-path resolution and source/target path confusion - -### confirm-discovery `0.9.1` - -- bumped `confirm-discovery` from `0.9.0` to `0.9.1` -- applied wording cleanup (`Skill` -> `skill`) for local style consistency -- clarified that `Confirmed By` is never auto-generated or inferred by AI -- clarified when to use `review-discovery` or `new-discovery` instead of `confirm-discovery` -- added baseline cross-check guidance for `docs/project-structure.md` and `docs/runtime-flows.md` -- added explicit pitfalls and a verification checklist -- clarified how to add a missing `docs/discovery/index.md` row while preserving register format - -### Metadata normalization - -Applied long-term management metadata across all StagePilot skills under `~/.stage-pilot/skills`. - -Changed for every skill: - -- added or normalized `author: Justin Ko` -- added `license: private` -- added `metadata.hermes.tags` -- added `metadata.hermes.related_skills` -- normalized frontmatter layout for long-term reuse - -### Version reclassification - -Assigned workflow-maturity-based semantic versions instead of a uniform initial version. - -#### Stable core (`1.0.0`) - -- `bootstrap-baseline` -- `new-discovery` -- `draft-req` - -#### Near-stable (initial classification) - -- `confirm-discovery` → later bumped to `0.9.1` -- `confirm-req` → `0.9.0` -- `draft-batch` → `0.9.0` - -#### Maturing workflow (`0.8.0`) - -- `change-req` -- `confirm-batch-verification` -- `confirm-release` -- `draft-batch-design` -- `draft-batch-planning` -- `draft-batch-verification` -- `draft-release` -- `review-discovery` -- `suggest-batch-reqs` - -#### Active evolution / orchestration (`0.7.0`) - -- `confirm-req-implemented` -- `run-batch-delivery` -- `run-batch-implementation` -- `run-sdlc` - -#### Early-support (`0.6.0`) - -- `capture-release-feedback` -- `suggest-next-discovery` - -### Notes - -Current intent of the version scale: - -- `0.6.x` — useful early-support skill, likely to evolve significantly -- `0.7.x` — execution/orchestration behavior still evolving -- `0.8.x` — maturing drafting/decision workflow -- `0.9.x` — near-stable, only moderate refinement expected -- `1.0.x` — stable core workflow skill diff --git a/.github/skills/README.md b/.github/skills/README.md deleted file mode 100644 index 78cdaca..0000000 --- a/.github/skills/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# StagePilot Skills - -Personal StagePilot skill pack for Hermes external skills. - -This directory is configured as an external skill root and contains the reusable skills, shared conventions, and cross-skill metadata for the StagePilot SDLC workflow. - -## Purpose - -StagePilot organizes work into a document-driven flow around: - -- baseline bootstrap -- discovery -- requirements -- batch planning / design / implementation / verification -- release -- release feedback and next-discovery planning - -These skills are intended to be reusable across repositories and machines, with versioned frontmatter so changes can be tracked over time. - -## Installation / usage on another machine - -1. Copy or clone this directory to the target machine. -2. Point Hermes `config.yaml` external skills setting at `~/.stage-pilot/skills`. -3. Reload skills: - - `/reload-skills` in-session, or - - restart Hermes / start a new session. - -## Skill inventory - -### Stable core skills - -- `bootstrap-baseline` — `1.0.1` -- `new-discovery` — `1.0.1` -- `draft-req` — `1.0.3` - -These are treated as the most reusable, foundational StagePilot workflow skills. - -### Near-stable skills - -- `confirm-discovery` — `0.9.1` -- `confirm-req` — `0.9.1` -- `draft-batch` — `0.9.1` - -These are close to stable but may still get light structural refinement. - -### Maturing workflow skills - -- `change-req` — `0.8.0` -- `confirm-batch-verification` — `0.8.0` -- `confirm-release` — `0.8.0` -- `draft-batch-design` — `0.8.0` -- `draft-batch-planning` — `0.8.0` -- `draft-batch-verification` — `0.8.0` -- `draft-release` — `0.8.1` -- `review-discovery` — `0.8.0` -- `suggest-batch-reqs` — `0.8.2` - -These are structured and usable now, but are expected to evolve as StagePilot usage expands. - -### Active evolution / orchestration skills - -- `confirm-req-implemented` — `0.7.0` -- `run-batch-delivery` — `0.7.0` -- `run-batch-implementation` — `0.7.0` -- `run-sdlc` — `0.7.0` - -These involve orchestration or execution-heavy behavior and are expected to change more than drafting skills. - -### Early-support skills - -- `capture-release-feedback` — `0.6.0` -- `suggest-next-discovery` — `0.6.0` - -These are useful but still relatively early in the overall workflow maturity. - -## Versioning policy - -Semantic versioning is used loosely to describe workflow maturity: - -- `0.6.x` — useful early-support skill, likely to evolve significantly -- `0.7.x` — active execution/orchestration logic still evolving -- `0.8.x` — maturing drafting/decision workflow -- `0.9.x` — near-stable and expected to change lightly -- `1.0.x` — stable core skill suitable for broad reuse - -Practical interpretation: - -- patch bump (`x.y.Z`) → wording fixes, metadata tweaks, examples, minor clarifications -- minor bump (`x.Y.z`) → meaningful behavior/structure expansion without redefining the role of the skill -- major bump (`X.y.z`) → significant workflow or contract change - -## Metadata conventions - -Each StagePilot skill should include these frontmatter fields: - -- `name` -- `description` -- `version` -- `author: Justin Ko` -- `license: private` -- `metadata.hermes.tags` -- `metadata.hermes.related_skills` - -Optional but commonly retained: - -- `argument-hint` -- `user-invocable` - -## Recommended repository hygiene - -If this directory is tracked in git, recommended companions are: - -- `README.md` — this file -- `CHANGELOG.md` — top-level release notes for the skill pack -- commits for every meaningful skill change -- optional tags when you want to snapshot a known-good pack revision - -## Last metadata normalization - -- Date: 2026-05-22 -- Author normalized to: `Justin Ko` -- Version fields reclassified by workflow maturity -- Cross-skill tags and related-skill metadata added to all StagePilot skills diff --git a/.github/skills/bootstrap-baseline/SKILL.md b/.github/skills/bootstrap-baseline/SKILL.md deleted file mode 100644 index 8841863..0000000 --- a/.github/skills/bootstrap-baseline/SKILL.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -name: bootstrap-baseline -description: "Use when: initializing StagePilot in a project before the first real Discovery, running /bootstrap-baseline to create baseline documents and active indexes, or backfilling missing docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, and docs/data-model.md without creating a Discovery unit." -version: 1.0.1 -author: Justin Ko -license: private -argument-hint: "예: 신규 저장소 baseline 초기화, StagePilot init, baseline 문서/인덱스 생성" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, bootstrap, baseline, documentation, sdlc] - related_skills: [new-discovery, run-sdlc] ---- - -# Purpose - -This skill initializes the StagePilot baseline outside the normal Discovery -> REQ -> Batch -> Release flow. - -`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, 그리고 active index 문서는 첫 real Discovery 전에 준비하는 bootstrap 산출물이다. 이 skill은 baseline과 index를 먼저 만들고, 첫 Discovery가 실제 제품/서비스 변경 주제로 시작되도록 만든다. - -greenfield 저장소처럼 읽을 코드나 설정이 아직 없으면, 이 skill은 사용자에게 최소 질문 세트를 묻고 `.stage-pilot/bootstrap/baseline.yaml` seed를 만든 뒤 baseline 문서를 렌더링한다. - -# When to use - -다음 상황에서 사용한다. - -- StagePilot을 새 프로젝트에 처음 적용한 직후 baseline 문서와 index를 만들 때 -- `/bootstrap-baseline` 또는 유사한 init 요청으로 baseline 초기화를 수행할 때 -- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md` 중 일부가 없어서 SDLC를 시작하기 전에 뼈대를 복구해야 할 때 -- 첫 real Discovery를 기능, 운영 문제, 기술 결정 같은 실제 변경 주제로 시작하고 싶을 때 - -다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. - -- 이미 진행 중인 Discovery/REQ/Batch/Release가 있고, baseline 변경 책임이 특정 active unit에 속하는 경우 -- 실제 제품/서비스 변경 주제가 이미 명확하여 Discovery 초안 생성이 우선인 경우 - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- 현재 저장소 루트와 디렉터리 구조 -- `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 존재 여부 -- `.stage-pilot/bootstrap/baseline.yaml` 존재 여부 -- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` 존재 여부 -- 템플릿의 논리 경로 - - `stage-pilot/templates/bootstrap/baseline-seed.yaml` - - `stage-pilot/templates/discovery/index.md` - - `stage-pilot/templates/srs/index.md` - - `stage-pilot/templates/batches/index.md` - - `stage-pilot/templates/releases/index.md` - - `stage-pilot/templates/project-structure.md` - - `stage-pilot/templates/runtime-flows.md` - - `stage-pilot/templates/interface-contract.md` - - `stage-pilot/templates/data-model.md` -- 현재 시각 (KST) - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `.stage-pilot/bootstrap/baseline.yaml`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` - -# Core Rules - -## 0. 단위 경계 - -- `bootstrap-baseline`은 Discovery, REQ, Batch, Release 중 어느 것도 아니다. -- 이 skill은 초기 운영 기준선을 만드는 bootstrap 단계다. -- 이 skill은 Discovery 문서를 생성하지 않는다. -- baseline 초기화가 끝난 뒤 첫 real Discovery는 실제 제품/서비스 변경, 운영 이슈, 기술 결정 같은 주제로 시작한다. - -## 1. 생성 대상 - -- 아래 경로가 없으면 생성한다. - - `.stage-pilot/bootstrap/baseline.yaml` - - `docs/discovery/index.md` - - `docs/srs/index.md` - - `docs/batches/index.md` - - `docs/releases/index.md` - - `docs/project-structure.md` - - `docs/runtime-flows.md` - - `docs/interface-contract.md` - - `docs/data-model.md` -- 이미 존재하는 파일은 임의로 덮어쓰지 않는다. -- 일부만 없는 경우에는 누락된 파일만 생성한다. - -## 1.5 최소 질문 세트 - -- 저장소 관찰만으로 baseline 핵심 정보가 부족하면 먼저 질문을 수행한다. -- 질문은 아래 다섯 개를 기본 세트로 사용한다. - - `project-summary` - - 질문: `이 프로젝트를 한 문장으로 설명하면 무엇인가?` - - 답변 형식: 한 문장 자유 텍스트 - - `primary-domain` - - 질문: `이 프로젝트의 주요 도메인은 무엇인가?` - - 답변 형식: 짧은 명사구 또는 분야명 - - `tech-stack` - - 질문: `계획 중인 주 언어, 프레임워크, 핵심 인프라는 무엇인가?` - - 답변 형식: 쉼표로 구분한 목록 - - `primary-runtime` - - 질문: `계획 중인 주 실행 형태는 무엇인가?` - - 답변 형식: `cli` | `api-service` | `web-app` | `worker` | `library` | `mixed` | `other` - - `primary-entrypoints` - - 질문: `대표 진입점 1~3개를 적어 달라.` - - 답변 형식: 줄마다 `name :: purpose` -- 아래 질문은 선택 입력이다. - - `planned-top-level-areas` - - 질문: `이미 알고 있는 top-level 경로가 있으면 적어 달라.` - - 답변 형식: 줄마다 `path :: responsibility` - - `interface-contracts` - - 질문: `대표 인터페이스 1~2개가 있으면 적어 달라.` - - 답변 형식: 줄마다 `name :: type :: actors :: purpose` - - `interface-inputs` - - 질문: `각 인터페이스의 대표 input을 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: input contract or invocation note` - - `interface-outputs` - - 질문: `각 인터페이스의 대표 output을 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: output contract or artifact` - - `interface-errors` - - 질문: `각 인터페이스의 대표 error를 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: error code or failure behavior` - - `core-entities` - - 질문: `핵심 엔티티 1~2개가 있으면 적어 달라.` - - 답변 형식: 줄마다 `name :: purpose` - - `persistence-backend` - - 질문: `주 persistence backend가 있으면 적어 달라.` - - 답변 형식: 짧은 backend 또는 storage 이름 - - `compatibility-rules` - - 질문: `인터페이스 호환성 규칙이 있으면 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: rule` 또는 `* :: rule` - - `known-unknowns` - - 질문: `아직 미정인 핵심 항목이 있으면 적어 달라.` - - 답변 형식: 줄마다 항목 하나 -- 이미 읽을 수 있는 저장소 증거가 있으면 질문 수를 줄이고, seed의 비어 있는 값만 보충한다. -- `interface-contracts`, `interface-inputs`, `interface-outputs`, `interface-errors`, `core-entities`, `persistence-backend`, `compatibility-rules`가 비어 있더라도 bootstrap은 `runtime`, `primary-entrypoints`, `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 기본 초안을 유도해 seed를 보강할 수 있다. - -## 1.6 seed 파일 source-of-truth 규칙 - -- bootstrap 질문 결과와 저장소 관찰 결과의 합성 source of truth는 `.stage-pilot/bootstrap/baseline.yaml`이다. -- seed 파일은 active SDLC unit가 아니라 bootstrap 선언 파일이다. -- seed 파일이 있으면 baseline 문서 생성과 갱신 시 이를 우선 읽는다. -- seed 파일이 없고 저장소 증거가 충분하지 않으면 질문 없이 baseline 문서를 추정 생성하지 않는다. -- 저장소 관찰값과 사용자 답변이 함께 있으면 `capture_mode`는 `mixed`다. -- 저장소 관찰값만으로 충분하면 `capture_mode`는 `observed`다. -- 사용자 선언이 중심이고 저장소 관찰값은 보조면 `capture_mode`는 `declared`다. - -## 2. baseline 문서 작성 원칙 - -- `docs/project-structure.md`는 seed의 `project`, `stack`, `runtime`, `structure` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. -- `docs/runtime-flows.md`는 seed의 `runtime`과 `notes` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. -- `docs/interface-contract.md`는 seed의 `interfaces` 섹션을 먼저 반영하고, 값이 부족하면 `runtime`, `primary-entrypoints`, `known-unknowns`를 바탕으로 대표 외부 API, CLI, 이벤트, 파일 I/O 계약의 기본 초안을 구체화한다. -- `docs/data-model.md`는 seed의 `data_model` 섹션을 먼저 반영하고, 값이 부족하면 `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 핵심 엔티티, 관계, 상태, persistence 규칙의 기본 초안을 구체화한다. -- 입력만으로 알 수 없는 항목은 의미 있는 플레이스홀더로 남기되, 추론 가능한 구조 정보는 실제 문장으로 채운다. -- baseline 문서의 `Source Discovery / Batch`는 bootstrap 산출물임을 드러내는 값으로 채운다. -- baseline 문서는 `declared`, `observed`, `mixed` 중 어느 모드로 렌더링되었는지와 seed 경로를 드러내야 한다. - -## 3. index 작성 원칙 - -- index 문서는 템플릿을 기반으로 생성한다. -- 초기화 시점에는 register 본문만 만들고 실제 Discovery/REQ/Batch/Release 항목은 비워 둔다. -- index 생성은 active unit 생성으로 간주하지 않는다. - -## 4. 진행 중 unit가 있는 경우 - -- 진행 중인 Discovery/REQ/Batch/Release가 이미 있고 baseline 문서만 일부 비어 있다면, 먼저 현재 active unit가 그 변경을 소유해야 하는지 판정한다. -- 특정 active unit의 요구사항이나 설계/검증 근거로 baseline 변경이 필요한 경우에는 그 unit 안에서 처리한다. -- 단순 bootstrap 누락 복구이고 active unit 책임이 아니라면 `bootstrap-baseline`으로 누락 파일만 보강할 수 있다. - -# Execution Procedure - -1. 저장소에서 active docs 루트와 baseline 파일 존재 여부를 확인한다. -2. `.stage-pilot/bootstrap/baseline.yaml`이 없거나 핵심 필드가 비어 있으면 저장소 관찰로 채울 수 있는 값과 질문이 필요한 값을 분리한다. -3. 저장소 관찰만으로 부족하면 최소 질문 세트를 사용해 사용자 선언 입력을 수집한다. -4. `stage-pilot/templates/bootstrap/baseline-seed.yaml`에 해당하는 실제 template source를 해석한 뒤 `.stage-pilot/bootstrap/baseline.yaml`을 생성 또는 보강한다. -5. 누락된 디렉터리가 있으면 `docs/discovery`, `docs/srs`, `docs/batches`, `docs/releases`를 먼저 준비한다. -6. 필요한 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤, 누락된 index 파일을 각 템플릿으로 생성한다. -7. seed 파일과 저장소 상태를 반영해 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`를 생성 또는 보강한다. -8. 이미 존재하는 파일은 보존하고, 필요한 경우에만 누락 복구 또는 seed 반영 사실을 결과에 기록한다. -9. 완료 후 다음 단계로 `new-discovery`를 사용해 첫 real Discovery를 시작하도록 안내한다. - -# Output Expectations - -- 생성 또는 복구한 파일 목록 -- seed 파일 상태 (`created` | `preserved` | `updated`) -- baseline 문서 상태 (`created` | `preserved` | `missing-information`) -- index 문서 상태 (`created` | `preserved`) -- 첫 real Discovery에서 다뤄야 할 주제 후보 또는 다음 action - -# Validation - -- 생성한 파일 경로가 active docs 구조와 일치하는지 확인한다. -- `.stage-pilot/bootstrap/baseline.yaml`의 필수 필드가 비어 있지 않은지 확인한다. -- baseline 문서와 index에 남아 있는 플레이스홀더가 정말 사람 결정이 필요한 값인지 점검한다. -- baseline 문서의 `Project Summary`, `Primary Domain`, `Tech Stack`, `Primary Runtime`와 공통 계약 정보가 seed 및 저장소 관찰과 모순되지 않는지 확인한다. -- Discovery 문서를 생성하지 않았는지 확인한다. \ No newline at end of file diff --git a/.github/skills/capture-release-feedback/SKILL.md b/.github/skills/capture-release-feedback/SKILL.md deleted file mode 100644 index 9be543f..0000000 --- a/.github/skills/capture-release-feedback/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: capture-release-feedback -description: "Use when: recording post-release observations, running /capture-release-feedback with a REL ID, appending feedback and follow-up inputs to docs/releases/rel-XXX_*.md, or marking included batches as released after deployment feedback is captured." -version: 0.6.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, feedback, retrospective, sdlc] - related_skills: [draft-release, confirm-release, suggest-next-discovery] ---- - -# Purpose - -This skill records post-release observations, converts them into follow-up Discovery or REQ inputs, and closes the release with captured feedback. - -# Inputs - -- `REL-ID` 또는 release 경로 -- 운영 관찰 결과 -- 대상 release 문서 -- 포함 batch 목록 -- `docs/releases/index.md` -- `docs/batches/index.md` - -# Core Rules - -- 운영 관찰 결과는 사실과 후속 입력으로 분리해 기록한다. -- 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 구분한다. -- release feedback을 남긴 뒤 release 상태는 `feedback-captured`로 전환할 수 있다. -- 포함 batch 상태는 필요하면 `released`로 갱신한다. - -# Execution Procedure - -1. release 문서와 포함 batch를 읽는다. -2. 운영 관찰 결과를 release 문서의 Feedback Handoff 섹션에 반영한다. -3. 후속 Discovery 입력, 신규 REQ 입력, 기존 REQ 변경 후보를 분리해 기록한다. -4. release index 상태를 `feedback-captured`로 갱신하고, 포함 batch 상태를 `released`로 조정한다. - -# Validation - -- release 문서에 운영 관찰 결과와 `Discovery Input`, `REQ Input`, `Change Request Input`이 모두 남았는지 확인한다. -- release index 상태와 batch index 상태가 문서 본문과 일치하는지 확인한다. \ No newline at end of file diff --git a/.github/skills/change-req/SKILL.md b/.github/skills/change-req/SKILL.md deleted file mode 100644 index 8b2036a..0000000 --- a/.github/skills/change-req/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: change-req -description: "Use when: updating an existing requirement after approval or implementation, running /change-req with a REQ ID or file path, recording a change-request entry before overwriting a REQ, analyzing impacted batches/releases, or deciding whether an Implemented REQ must be downgraded and reverified." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 docs/srs/Documentation/req-001_minor-docs.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, change-management, documentation, sdlc] - related_skills: [draft-req, confirm-req, draft-batch] ---- - -# Purpose - -This skill applies a controlled change to an existing REQ by recording a new change-request entry before editing the requirement body, analyzing downstream delivery impact, and updating status/index state when the prior implementation is no longer valid. - -# When to use - -- `/change-req req-001`처럼 기존 REQ의 Requirement, Acceptance Criteria, Impacted Area를 수정해야 할 때 -- 운영 피드백이나 release feedback에서 기존 REQ 수정 필요가 확인됐을 때 -- `Implemented` 또는 `Approved` REQ가 더 이상 현재 구현/검증 증거와 맞지 않는지 판단해야 할 때 -- `docs/srs/index.md`의 REQ 상태와 실제 변경 영향 범위를 다시 동기화해야 할 때 - -# Inputs - -- 선택 입력 - - `REQ-ID` prefix - - REQ 파일 경로 -- 변경 요청 근거 - - 사용자 요청 - - release feedback - - batch verification 또는 implementation 결과 -- 대상 REQ 문서 -- `docs/srs/index.md` -- 관련 `docs/batches/index.md` -- 관련 `docs/releases/index.md` -- 영향이 의심되는 batch/release 문서들 - -# Core Rules - -## 1. 입력 해석 - -- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- 동일 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. -- REQ가 `Deprecated`면 기본적으로 새 변경 요청 대상으로 삼지 않는다. 복구가 필요하면 근거를 먼저 명시한다. - -## 2. 변경 요청 기록 규칙 - -- 기존 `Requirement`, `Acceptance Criteria`, `Impacted Area`를 덮어쓰기 전에 새 `Change Log` 항목을 추가한다. -- 새 변경 항목 ID는 `CHG-YYYYMMDD-NN` 형식을 사용하고 같은 날짜 내에서 순차 증가시킨다. -- 새 항목에는 최소한 아래를 모두 기록한다. - - `Change Summary` - - `Intent` - - `Acceptance Criteria Delta` - - `Impacted Area Delta` - - `Delivery Trace Delta` - - `Revalidation Impact` - - `Validation Plan` -- 과거 `Change Log` 항목은 삭제하거나 덮어쓰지 않는다. - -## 3. 상태 영향 규칙 - -- 변경이 기존 구현 또는 verification evidence를 무효화하지 않으면 현재 상태를 유지할 수 있다. -- `Implemented` REQ의 기존 구현/검증 근거가 깨지면 상태를 `Approved`로 되돌린다. -- 변경이 승인 전제 자체를 다시 검토하게 만들면 상태를 `Proposed`로 되돌릴 수 있다. -- 상태를 되돌릴 때는 이유를 `Revalidation Impact`와 `Change Log`에 함께 남긴다. -- 상태가 바뀌면 `docs/srs/index.md`의 Register와 `Recent Change Log Summary`도 함께 갱신한다. - -## 4. 영향 분석 규칙 - -- 관련 batch가 있으면 어떤 batch planning/design/verification이 다시 검토되어야 하는지 기록한다. -- 관련 release가 있으면 release 문서를 직접 덮어쓰지 말고 영향 범위와 후속 action을 REQ의 `Change Log`에 남긴다. -- 사람 판단이 필요한 범위 재정의, 우선순위 변경, 정책 변경은 임의 확정하지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 경로를 확정한다. -2. REQ 현재 상태와 최근 `Change Log`를 읽는다. -3. `docs/batches/index.md`, `docs/releases/index.md`, 관련 batch/release 문서를 읽어 현재 delivery trace를 요약한다. -4. 변경 요청 근거를 기존 구현/검증 상태와 비교한다. -5. 새 `Change Log` 항목을 먼저 추가하고 delta, trace, revalidation impact를 채운다. -6. 그 다음 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`)을 필요한 만큼 수정한다. -7. 상태 유지 또는 상태 되돌림 필요 여부를 결정해 REQ 본문과 `docs/srs/index.md`를 갱신한다. -8. `Recent Change Log Summary`에 최신 변경 항목을 반영한다. - -# Output Expectations - -- 대상 REQ 경로 -- 새 `Change Log` 항목 ID -- 변경 요약 -- 영향 받는 batch/release 목록 -- 재검증 필요 여부 -- 상태 유지 또는 상태 되돌림 결과 -- `docs/srs/index.md` 갱신 결과 - -# Common Pitfalls - -1. 활성 batch 안에서 계약/필드 순서 같은 외부 계약을 바꾸면서 REQ만 수정하고 delivery 산출물과 baseline 문서를 같이 재정렬하지 않는 실수 - - 예: persistence flat slot 순서, CTSP read/write 슬롯 순서, revision migration 시 기본값 채움 순서 변경. - - 이런 변경은 `change-req` 후에 source Discovery, 연결된 Approved REQ들, 현재 batch의 `planning/design/implementation/verification`, 그리고 `CTSP_PROTOCOL`/`CONFIGURATION`/`runtime-flows` 같은 기준 문서를 같은 변경 묶음으로 갱신해야 한다. - - verification evidence가 다시 필요해질 수 있으므로, 검증 전에는 batch를 `release-candidate`로 올리거나 REQ를 `Implemented`로 확정하지 않는다. - -# Validation - -- `Change Log` 새 항목이 REQ 본문 수정보다 앞선 시간순 근거로 남았는지 확인한다. -- `Acceptance Criteria Delta`, `Delivery Trace Delta`, `Revalidation Impact`가 모두 채워졌는지 확인한다. -- 상태 되돌림이 필요한 경우 REQ 문서와 `docs/srs/index.md` 상태가 함께 바뀌었는지 확인한다. -- 관련 batch/release 영향이 `Change Log`에 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/.github/skills/confirm-batch-verification/SKILL.md b/.github/skills/confirm-batch-verification/SKILL.md deleted file mode 100644 index 6906e6d..0000000 --- a/.github/skills/confirm-batch-verification/SKILL.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: confirm-batch-verification -description: "Use when: approving a batch verification result, running /confirm-batch-verification with a BAT ID, promoting docs/batches/ to release-candidate, or updating docs/batches/index.md after verification passes." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, verification, approval, sdlc] - related_skills: [draft-batch-verification, confirm-req-implemented, draft-release] ---- - -# Purpose - -This skill checks whether a batch verification document has enough evidence to release the batch and, if so, promotes the batch to `release-candidate`. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//verification.md` -- 포함된 REQ 문서들 -- `docs/batches/index.md` - -# Core Rules - -- verification에 미해결 blocker가 있으면 승인하지 않는다. -- verification에 미해결 blocker가 있으면 승인하지 않는다. -- 단, verification 문서에 명시적 `Human Approval Memo`가 있고 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거가 모두 적혀 있으면 그 메모가 명시적으로 수용한 blocker는 residual risk로 보고 승인할 수 있다. 이 경우 승인 결과에는 사람이 수용한 예외 범위를 함께 요약한다. -- 포함된 REQ의 acceptance criteria가 evidence와 연결돼야 한다. -- `batch-lite`는 design 문서 없이도 승인할 수 있지만, planning의 `Design Gate`가 design 불필요를 명시하고 verification이 구조 영향 없음 또는 baseline 영향 없음을 확인해야 한다. -- 승인 성공 시 batch status는 `release-candidate`가 된다. - -# Execution Procedure - -1. batch와 verification 문서를 읽는다. -2. 필요하면 planning과 design을 함께 읽어 profile과 구조 영향 여부를 확인한다. -3. Acceptance Mapping, Evidence, Blocking Issues를 점검한다. -4. 승인 가능하면 verification 상태와 batch index 상태를 갱신한다. -5. 승인 불가면 상태는 유지하고 blocker를 보고한다. -6. verification에 `Human Approval Memo`가 있으면, 메모가 수용한 blocker와 여전히 승인 불가한 blocker를 분리해서 판단한다. 사람이 수용한 항목만 남아 있다면 verification을 `approved`로 올리고 batch를 `release-candidate`로 승격할 수 있다. - -# Validation - -- 승인 성공인 경우 batch index 상태가 `release-candidate`인지 확인한다. -- 승인 보류인 경우 blocker가 명시됐는지 확인한다. -- `batch-lite` 승인 성공인 경우 design 부재가 검증 근거와 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/.github/skills/confirm-discovery/SKILL.md b/.github/skills/confirm-discovery/SKILL.md deleted file mode 100644 index d322109..0000000 --- a/.github/skills/confirm-discovery/SKILL.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -name: confirm-discovery -description: "Use when: running /confirm-discovery with dcy-xyz or a Discovery path, re-checking the current repository state against docs/discovery/*.md, filling AI-resolvable placeholders or inconsistencies, and promoting the Discovery to confirmed when it is ready for REQ drafting in the Requirements phase." -version: 0.9.1 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, approval, quality, sdlc] - related_skills: [new-discovery, review-discovery, draft-req] ---- - -# Purpose - -This skill revalidates an existing Discovery document against the current repository state, fills anything the AI can resolve from local evidence, and promotes the document to `confirmed` only when REQ drafting handoff has no remaining blockers. - -이 skill의 결과는 아래 두 경우로 나뉜다. - -- 확인 결과 승인 가능이면 대상 Discovery 문서를 `confirmed`로 전환하고 `docs/discovery/index.md`의 해당 행 상태를 함께 갱신한다. -- 승인까지는 불가하더라도 AI가 근거 있게 채울 수 있는 항목이 있으면 문서를 먼저 보강한다. 단, 이 경우 `confirmed` 전환과 REQ drafting 준비 플래그 변경은 하지 않는다. - -# When to use - -다음 상황에서 사용한다. - -- `/confirm-discovery dcy-xyz` 형태로 기존 Discovery를 최종 확인해야 할 때 -- `docs/discovery/` 아래 Discovery 초안 문서를 REQ 초안 작성 가능 상태로 승격해야 할 때 -- 현재 저장소 구현 상태를 다시 확인해 Discovery 본문의 사실 관계를 정정하거나 보강해야 할 때 -- 문서 안의 플레이스홀더 중 AI가 근거 있게 채울 수 있는 항목을 먼저 정리한 뒤 승인 가능 여부를 판정해야 할 때 -- `docs/discovery/index.md`의 상태가 실제 Discovery 상태와 일치하는지 함께 맞춰야 할 때 - -다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. - -- Discovery 품질 점검, 누락 점검, OQ 후보 정리만 먼저 하고 싶을 때 - - 이 경우 `review-discovery`를 사용한다. -- 현재 문서가 사실상 새 Discovery 생성 또는 대규모 범위 재정의를 필요로 할 때 - - 이 경우 `new-discovery`로 새 주기를 시작하거나 기존 Discovery를 재작성해야 한다. -- 승인자 지정, 정책 확정, 외부 확인 결과처럼 사람 판단 또는 외부 근거가 먼저 필요한 상태일 때 - - 이 경우 confirm 없이 보류하고 필요한 결정을 먼저 해소한다. - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- Discovery 식별자 - - `dcy-001` 같은 축약 ID - - `dcy-001_20260424_topic-slug` 같은 전체 Discovery ID - - `docs/discovery/.md` 같은 직접 경로 -- Discovery 문서 본문 -- `docs/discovery/index.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) -- 현재 저장소 구현 상태 - - 관련 소스 파일 - - 설정 파일 - - 관련 문서 -- 현재 시각 (KST) - -# Core Rules - -## 1. 입력 해석 규칙 - -- 입력이 `dcy-001`처럼 prefix만 주어지면 `docs/discovery/` 아래에서 해당 prefix로 시작하는 문서를 찾는다. -- 입력이 전체 Discovery ID면 `docs/discovery/.md`를 직접 찾는다. -- 입력이 파일 경로면 해당 경로를 그대로 사용한다. -- 매칭 결과가 0개면 자동 진행하지 않고 사용자에게 입력을 재확인하도록 알린다. -- 같은 prefix에 대해 여러 문서가 매칭되면 임의 선택하지 않고 후보 목록을 보여 준 뒤 사용자 확인을 요청한다. -- `.stage-pilot/` 경로는 탐색 대상에서 제외한다. - -## 2. 현재 구현 상태 재확인 규칙 - -- 현재 구현 상태 확인은 요구사항이 이미 모두 구현되었는지 검사하는 절차가 아니다. -- 이 확인의 목적은 Discovery의 `현재 상태`, `영향 범위`, `성공 기준`, `체크 항목`, `사용자 결정 필요 항목`이 저장소 현실과 맞는지 검증하는 것이다. -- `docs/project-structure.md`, `docs/runtime-flows.md`가 존재하면 Discovery의 구조 설명, 영향 범위, runtime flow 설명이 baseline과 모순되지 않는지도 함께 확인한다. -- 구현 부재 자체는 차단 사유가 아니다. Discovery가 애초에 신규 작업을 정의하는 문서라면 코드가 없는 상태도 정상일 수 있다. -- 차단이 되는 경우는 아래와 같다. - - 문서가 이미 존재한다고 적은 구현이 실제 저장소에는 없어서 본문 사실 관계가 틀린 경우 - - 저장소 현실이 문서의 핵심 범위, 사용자 시나리오, 성공 기준을 다시 써야 할 정도로 바뀐 경우 - - 문서의 남은 결정 항목이 여전히 사람 판단을 필요로 하는데 승인 상태로 올리려는 경우 - -## 3. 승인 전 보강 규칙 - -승인 판정 전에 먼저 아래 보강 절차를 수행한다. - -### 3-1. 일관성 검토 - -- 용어 혼용, 범위 충돌, 중복 서술, 번호 누락을 확인한다. -- `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 확인한다. -- 문장만 다르고 같은 의미를 반복하는 부분은 간결하게 정리한다. -- 문법, 오타, 링크 형식, 체크리스트 표현을 함께 정리한다. - -### 3-2. 증거 기반 보강 - -- Discovery 본문에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계를 읽고 키워드를 추출한다. -- 키워드를 바탕으로 저장소에서 관련 파일, 설정, 문서를 탐색한다. -- 탐색 결과는 최소한 아래 판단으로 정리한다. - - 이미 존재함 - - 일부 존재함 - - 아직 없음 - - 문서와 모순됨 -- AI가 아래 근거로 확정 가능한 항목은 실제 문장으로 치환한다. - - 같은 Discovery 문서 안 다른 섹션에 이미 답이 있는 항목 - - 현재 저장소 파일 구조, 설정, 문서에서 직접 확인 가능한 항목 - - 승인 행위 시각처럼 현재 실행 시점에 자동 확정 가능한 항목 -- 아래 항목은 추론만으로 확정하지 않는다. - - 정책 선택 - - 우선순위 승인 - - 외부 시스템 또는 외부 데이터 확인 결과 - - 승인자 지명처럼 사람 책임을 수반하는 결정 - - `Confirmed By`처럼 승인 책임 주체를 명시하는 값 - -### 3-3. 플레이스홀더 분류 기준 - -- A. 승인 시점 자동 채움 - - 예: `{{DATA_CONFIRMED_AT_KST:...}}`, `{{CONFIRMED_AT_KST:...}}` - - 승인 성공 시 현재 시각으로 채운다. -- B. 증거 기반 해소 가능 - - 문서 다른 섹션, 저장소 구조, 이미 확정된 값으로 해소 가능한 플레이스홀더다. - - 승인 성공 여부와 무관하게 먼저 채운다. -- C. 사람 결정 또는 외부 확인 필요 - - `{{DECIDE_*}}`, `{{CONFIRM_*}}`, `{{DATA_*}}` 중 아직 문서와 저장소 근거만으로 해소되지 않는 항목이다. - - 승인 차단 대상이다. - -### 3-4. 책임자 지정과 저장소 범위 DATA 처리 기준 - -- `REQ/검토 책임자 지정`, `구현/검토 책임자 지정`, `해소 책임자 지정`처럼 사람 책임 배정이 필요한 항목은 AI가 자동으로 채우지 않는다. -- 이런 항목이 Discovery의 범위/정책/성공 기준 자체를 결정하는 질문이 아니라 이후 REQ 또는 batch planning 단계의 담당자 할당 문제라면, `# 10. 사용자 결정 필요 항목 요약`에 남겨 Discovery confirm을 가로막는 질문으로 유지하지 말고 `# 8. REQ로 넘기기 전 확인 체크` 또는 `# 11. Discovery Freeze`의 보류 사유로 정리한다. -- 다만 사람이 이미 대화에서 승인자/책임자/범위 결정을 명시해 주었고, 그 값이 문서에 직접 반영 가능한 형태라면 AI는 그 사람 결정을 문서에 옮길 수 있다. 이 경우에도 AI가 새 값을 추정하면 안 되고, 사용자가 제공한 사람 이름/결정만 그대로 기록해야 한다. -- 저장소 내부에서 확인 가능한 DATA 항목은 repo-wide consumer 탐색으로 먼저 해소를 시도한다. 예를 들어 특정 명령의 응답 길이에 의존하는 호스트 도구/스크립트 목록이 필요한 경우, 관련 토큰(`VISIONCFG`, `visioncfg` 등)으로 코드/문서/QA 스크립트 전체를 검색해 in-repo 소비처를 먼저 열거한다. -- 측정 경로와 검증 방법은 저장소 근거로 이미 확정됐지만 실제 수치가 구현/운영 단계에서만 수집 가능한 경우, 그 DATA 항목이 REQ 초안 작성 자체를 막는지 분리해서 판단한다. 대표 검증셋, 범위, 성공 기준, 사람 승인자가 확정돼 있으면 runtime baseline 수치 같은 운영 데이터는 REQ 단계 계측 항목으로 넘기고 confirm을 진행할 수 있다. -- 이 과정에서 저장소 안에서 길이 의존 구현이 확인되지 않으면 `현 저장소 기준 확인된 소비처는 없음`처럼 범위를 한정해 정리할 수 있다. 단, 저장소 밖 배포 도구나 외부 시스템까지 없다고 확대 해석하지 않는다. - -- 승인 판정 직전에는 A와 B만 남아 있어야 한다. -- B로 분류한 항목을 채운 뒤에도 `# 1`부터 `# 10` 사이에 C 유형 플레이스홀더가 남아 있으면 승인하지 않는다. -- 단, `DATA_*` 성격의 항목이 "REQ 또는 delivery 단계에서 계측·확정할 운영 데이터"임이 문서 안에 명시되고, Discovery 본문만으로도 FR/NFR, 범위, 성공 기준, 검증 시나리오를 REQ 초안으로 내리기에 충분하다면 confirm 차단 사유로 유지하지 않는다. 이런 경우에는 `# 8. REQ로 넘기기 전 확인 체크`와 `# 10. 사용자 결정 필요 항목 요약`에 "REQ 단계 계측/확정" 또는 동등한 표현으로 넘기고, Discovery Freeze rationale에도 비차단 운영 데이터라는 점을 적는다. - -## 4. 최종 승인 게이트 - -아래 조건을 모두 만족해야 `confirmed`로 전환한다. - -- 문서 상태가 아직 `confirmed`가 아니어야 한다. -- B 유형 보강 이후 C 유형 플레이스홀더가 0개여야 한다. -- `# 6. 리스크/가정 목록`의 오픈 질문에 `상태: Open` 항목이 없어야 한다. -- `# 8. REQ로 넘기기 전 확인 체크`에 남아 있는 미완료 항목이 있다면, 그 항목이 REQ 초안 작성을 막지 않는다고 문서에 명시되어 있어야 한다. 그렇지 않으면 차단한다. -- `# 10. 사용자 결정 필요 항목 요약`에 남은 항목이 모두 `없음`이거나, 문서 안에서 이미 답이 반영되어 제거 가능해야 한다. -- `# 11. Discovery Freeze`에 `Confirmed By`가 비어 있지 않아야 한다. -- 현재 저장소 상태가 Discovery의 핵심 범위나 요구사항을 다시 정의해야 할 정도로 모순되지 않아야 한다. -- runtime budget baseline, 허용 상한, 운영 계측값처럼 구현 전에는 원천적으로 확정되지 않는 수치가 남아 있더라도, 그 값이 REQ의 acceptance/verification에서 수집할 evidence로 이미 위치가 정리돼 있으면 confirm 차단 사유로 과도하게 남기지 않는다. - -`Confirmed By` 관련 규칙: - -- `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 의미한다. -- AI는 `Confirmed By`를 새로 생성하거나 추정해서 채우지 않는다. -- `Confirmed By`가 비어 있으면 승인 차단 사유로 유지한다. - -다음 경우는 `confirmed` 전환 대신 보류한다. - -- Discovery 초안이 아니라 사실상 새 Discovery 생성 또는 대규모 범위 재정의가 필요한 경우 -- 확인 과정에서 FR/NFR, In Scope/Out of Scope, 성공 기준을 실질적으로 다시 써야 하는 경우 -- 승인자 또는 필수 결정 사항이 여전히 문서 밖 사람 판단에 의존하는 경우 - -## 5. 허용되는 수정 범위 - -이 skill은 아래 수정만 수행한다. - -- AI가 근거 있게 채울 수 있는 플레이스홀더 치환 -- 현재 저장소 상태에 맞춘 사실 관계 보정 -- 표현, 번호, 체크리스트, 링크의 일관성 정리 -- 실제 문서 변경이 있었다면 `마지막 갱신 시각(KST)` 갱신 -- 승인 가능 시 아래 상태 전환 수행 - - `# 0. 문서 상태`의 `상태`를 `confirmed`로 변경 - - `# 11. Discovery Freeze`의 `Handoff Decision`을 `승인`으로 변경 - - `# 11. Discovery Freeze`의 `Ready for REQ Drafting`을 `true`로 변경 - - `Confirmed At (KST)`를 현재 시각으로 채움 - - `docs/discovery/index.md`의 해당 Discovery 행 상태를 `confirmed`로 갱신 - -단, 아래 항목은 자동으로 채우지 않는다. - -- `Confirmed By` - - 사람 승인자 또는 사람 책임 주체가 이미 문서에 명시되어 있어야 한다. - - 비어 있으면 승인 전환 없이 보류한다. - -아래 상황이면 상태 전환 없이 보강만 하고 종료한다. - -- C 유형 플레이스홀더가 남아 있는 경우 -- 오픈 질문 또는 사용자 결정 항목이 남아 있는 경우 -- 승인 자체보다 먼저 Discovery 내용 재정비가 필요한 경우 - -# Repository Exploration - -현재 구현 상태를 다시 확인할 때는 아래 순서로 저장소를 탐색한다. - -1. Discovery 문서에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계 키워드를 뽑는다. -2. 키워드 기준으로 관련 소스, 설정, 문서를 찾는다. - - `docs/project-structure.md`, `docs/runtime-flows.md`가 있으면 baseline 참조 문서로 함께 읽는다. -3. 아래 항목은 기본적으로 제외한다. - - 테스트 파일 - - 의존성 잠금 파일 - - `.stage-pilot/` 하위 파일 -4. 탐색 결과를 아래 형태로 정리한다. - - 관련 파일 경로 목록 - - 이미 구현된 내용 - - 일부만 구현된 내용 - - 아직 없는 내용 - - 문서와 모순되는 내용 - - baseline 문서와 일치하는 내용 / 충돌하는 내용 -5. 탐색 결과는 Discovery 보강과 승인 게이트 판단에만 사용한다. - -# Execution Procedure - -다음 순서대로 작업한다. - -1. 입력에서 Discovery 문서 경로를 확정한다. -2. 대상 Discovery 문서와 `docs/discovery/index.md`를 읽는다. -3. Discovery의 핵심 범위, FR/NFR, 오픈 질문, 체크리스트, 사용자 결정 필요 항목을 추출한다. -4. 위 `Repository Exploration` 규칙에 따라 현재 저장소 상태를 확인한다. -5. 문서 전체를 검토해 일관성 오류와 플레이스홀더를 나열한다. -6. B 유형 플레이스홀더와 명백한 사실 관계 오류를 먼저 수정한다. -7. 문서 내용이 바뀌었다면 `마지막 갱신 시각(KST)`를 현재 시각으로 갱신한다. -8. 다시 한 번 `최종 승인 게이트`를 평가한다. -9. 게이트를 통과하면 A 유형 플레이스홀더를 채우고 문서를 `confirmed`로 전환한다. -10. 게이트를 통과하면 `docs/discovery/index.md`의 해당 행 상태도 `confirmed`로 갱신한다. -11. `docs/discovery/index.md`에 해당 Discovery 행이 없으면 현재 문서 메타데이터를 기준으로 기존 register/header 형식과 열 순서를 유지한 채 행을 추가한다. - - 최소한 Discovery ID, 제목/이슈명, 상태, 링크, 마지막 갱신 시각에 해당하는 값을 채운다. - - 표가 아닌 다른 register 형식이면 그 형식을 그대로 따른다. -12. 게이트를 통과하지 못하면 상태는 유지하고, 보강된 내용과 남은 차단 항목만 보고한다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 대상 `DISCOVERY_ID`와 문서 경로 -- 현재 저장소 상태 재확인 결과 요약 -- AI가 자동으로 채운 항목 목록 -- 남겨 둔 항목 목록과 남긴 이유 -- 최종 판정 - - `confirmed 전환 완료` - - `보강만 수행, 승인 보류` - - `문서 변경 없이 승인 보류` -- `docs/discovery/index.md` 갱신 결과 -- REQ 초안 작성으로 넘기기 전에 남은 작업 요약 - -# Common Pitfalls - -1. 구현이 아직 없다는 이유만으로 승인 차단하기 - - 신규 작업을 정의하는 Discovery라면 구현 부재는 정상일 수 있다. 차단 사유는 문서와 저장소 사실 관계 모순 또는 사람 결정 미해소여야 한다. - -2. `Confirmed By`를 AI가 추정해서 채우기 - - `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 뜻한다. AI가 새로 만들거나 추정하면 안 된다. - -3. 사실상 새 Discovery가 필요한 문서를 confirm으로 밀어 올리기 - - 확인 과정에서 FR/NFR, 범위 경계, 성공 기준을 다시 써야 한다면 confirm보다 재작성 또는 새 Discovery 판단이 우선이다. - -4. `review-discovery`에서 할 품질 점검과 `confirm-discovery`의 승인 게이트를 혼동하기 - - 단순 누락 점검과 구조 품질 확인이 목적이면 먼저 `review-discovery`를 사용한다. - -5. Discovery 본문만 바꾸고 index 또는 freeze 필드를 함께 확인하지 않기 - - 승인 완료 시 본문 상태, Freeze 필드, `docs/discovery/index.md` 상태가 함께 일치해야 한다. - -# Verification Checklist - -완료 전에 아래를 확인한다. - -- [ ] 대상 경로가 `docs/discovery/.md` 형식을 만족하는지 -- [ ] 대상 Discovery 문서가 실제로 존재하는지 -- [ ] `docs/project-structure.md`, `docs/runtime-flows.md`가 있다면 필요한 baseline cross-check가 반영되었는지 -- [ ] 보강 후에도 C 유형 플레이스홀더와 `상태: Open` 오픈 질문이 정확히 식별되었는지 -- [ ] 승인 완료인 경우 `Confirmed By`가 사람에 의해 채워져 있고, 문서 상태와 `docs/discovery/index.md` 상태가 모두 `confirmed`인지 -- [ ] 승인 보류인 경우 `confirmed`, `승인`, `true` 같은 승인 전환 값이 잘못 들어가지 않았는지 -- [ ] 문서 본문 보강이 새로운 요구사항 생성이나 범위 재정의로 번지지 않았는지 -- [ ] 결과 보고에 자동 처리 항목과 차단 항목이 모두 포함되는지 \ No newline at end of file diff --git a/.github/skills/confirm-release/SKILL.md b/.github/skills/confirm-release/SKILL.md deleted file mode 100644 index c062437..0000000 --- a/.github/skills/confirm-release/SKILL.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: confirm-release -description: "Use when: approving a release plan, running /confirm-release with a REL ID or release path, promoting docs/releases/rel-XXX_*.md from draft to confirmed, or validating rollout and rollback readiness before deployment." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, approval, quality, sdlc] - related_skills: [draft-release, capture-release-feedback, suggest-next-discovery] ---- - -# Purpose - -This skill validates a release document and promotes it to `confirmed` only when rollout, rollback, and verification checks are ready. - -# Inputs - -- `REL-ID` 또는 release 경로 -- 대상 release 문서 -- `docs/releases/index.md` -- 포함 batch 문서들 - -# Core Rules - -- Included Batch가 모두 release-candidate여야 한다. -- Rollout Plan, Rollback Plan, Verification Checklist가 비어 있으면 승인하지 않는다. -- release profile에 맞는 검증 항목이 비어 있으면 승인하지 않는다. -- `docs-only`는 링크/렌더링/패키징 검증으로 충분하며 health endpoint를 요구하지 않는다. -- `tooling`은 설치/업데이트/CLI 또는 스크립트 smoke 결과가 있어야 한다. -- `app-service`는 health, 로그, smoke 또는 동등한 운영 확인 근거가 있어야 한다. -- 승인 성공 시 release 상태를 `confirmed`로 바꾸고 index를 갱신한다. - -# Execution Procedure - -1. release 문서와 포함 batch 상태를 확인한다. -2. rollout, rollback, verification 준비도와 release profile의 필수 검증 항목을 점검한다. -3. 게이트 통과 시 release 상태와 index를 `confirmed`로 갱신한다. -4. 미통과 시 상태는 유지하고 blocker를 보고한다. - -# Validation - -- 승인 성공인 경우 release 문서와 index 상태가 모두 `confirmed`인지 확인한다. -- 승인 보류인 경우 누락 항목이 명시됐는지 확인한다. -- profile별 검증 항목이 release 문서의 선택된 profile과 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/.github/skills/confirm-req-implemented/SKILL.md b/.github/skills/confirm-req-implemented/SKILL.md deleted file mode 100644 index 93ad197..0000000 --- a/.github/skills/confirm-req-implemented/SKILL.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: confirm-req-implemented -description: "Use when: confirming that approved requirements have implementation and verification evidence, running /confirm-req-implemented with a REQ ID, BAT ID, or file path, promoting docs/srs//req-XXX_.md from Approved to Implemented, or updating docs/srs/index.md after batch verification passes." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 bat-001 또는 docs/srs/Interface/req-001_picker-cli-run.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, implementation, validation, sdlc] - related_skills: [draft-batch-verification, confirm-batch-verification, draft-release] ---- - -# Purpose - -This skill validates that an approved requirement has implementation and verification evidence, then promotes it to `Implemented` only when every acceptance criterion is traceable to delivery evidence. - -`Implemented`는 배포 완료 자체를 뜻하지 않는다. 기본 기준은 구현과 검증 증거가 확보된 상태이며, 일반적으로 `release-candidate` 또는 `released` batch의 verification 결과를 근거로 삼는다. - -# When to use - -- `/confirm-req-implemented req-001`처럼 특정 REQ를 `Implemented`로 전환해야 할 때 -- `/confirm-req-implemented bat-001`처럼 batch verification 이후 포함 REQ 상태를 동기화해야 할 때 -- `/confirm-req-implemented`처럼 입력 없이 현재 저장소의 `Approved` REQ 중 구현 완료로 승격 가능한 대상을 일괄 점검해야 할 때 -- `docs/srs/index.md`의 REQ 상태가 실제 batch verification evidence와 어긋나 있을 때 - -# Inputs - -- 선택 입력 - - `REQ-ID` prefix - - REQ 파일 경로 - - `BAT-ID` prefix - - batch 경로 -- 입력이 없으면 `docs/srs/**/req-*.md` 전체에서 `Status: Approved` REQ를 후보로 찾는다. -- 대상 REQ 문서 또는 대상 batch 문서 -- `docs/srs/index.md` -- `docs/batches/index.md` -- 관련 `docs/batches//index.md` -- 관련 `docs/batches//verification.md` -- 필요하면 관련 `docs/batches//implementation.md` - -# Core Rules - -## 1. 입력 해석 - -- `req-001` 같은 prefix가 주어지면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- `bat-001` 같은 prefix가 주어지면 `docs/batches/` 아래 matching batch를 찾고, 포함 REQ 중 `Approved` 상태만 후보로 삼는다. -- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 읽고, 그중 `Status: Approved` 문서를 후보로 삼는다. -- 같은 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. -- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 게이트로 평가한다. - -## 2. 구현 완료 전환 게이트 - -- 아래 조건을 모두 만족해야 `Implemented`로 전환한다. - - 대상 REQ의 현재 `Status`가 `Approved`다. - - 연결된 batch가 `release-candidate` 또는 `released` 상태다. - - batch `verification.md`에 해당 REQ의 `Acceptance Mapping`이 존재한다. - - REQ의 모든 `Acceptance Criteria`가 verification evidence와 연결된다. - - batch verification의 `Blocking Issues`가 이 REQ를 미통과 상태로 남기지 않는다. -- release 배포 완료는 필수 조건이 아니다. 구현과 검증 evidence가 충분하면 `release-candidate` 상태에서도 `Implemented` 전환이 가능하다. -- 연결된 batch를 찾지 못하거나 evidence가 부족하면 상태를 바꾸지 않는다. -- 하나의 REQ가 여러 batch에 걸쳐 있고 어떤 batch를 근거로 삼아야 할지 명확하지 않으면 임의로 전환하지 않는다. - -## 3. 허용되는 수정 - -- AI가 근거 있게 정리할 수 있는 오타, 링크, 표현 불일치는 보강할 수 있다. -- 전환 성공 시 아래 변경을 수행한다. - - REQ 문서의 `Status`를 `Implemented`로 변경 - - `Change Log`에 구현 완료 전환 기록 추가 - - `docs/srs/index.md`의 해당 Register 행 상태를 `Implemented`로 갱신 - - 필요하면 `Recent Change Log Summary`를 갱신 -- evidence 자체를 새로 만들어 내거나, batch verification의 blocker를 임의 해소하지 않는다. -- 사람 판단이 필요한 범위 변경, acceptance criteria 재정의, batch 재매핑은 임의로 확정하지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 하나, REQ 목록, 또는 batch 경로를 확정한다. -2. batch 입력인 경우 batch `index.md`에서 Included REQ를 읽고 `Approved` REQ만 후보로 만든다. -3. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Approved` 문서를 후보 목록으로 만든다. -4. 각 후보 REQ에 대해 `docs/batches/index.md`와 batch 문서를 읽어 연결된 batch를 찾는다. -5. 연결된 batch의 `verification.md`에서 REQ acceptance mapping, evidence, blocking issue를 점검한다. -6. 게이트를 통과한 REQ만 `Implemented`로 전환하고 `Change Log`, `docs/srs/index.md`를 갱신한다. -7. 게이트를 통과하지 못한 REQ는 상태를 유지하고 남은 blocker를 수집한다. -8. 입력이 batch 또는 무인자 일괄 모드인 경우, 성공 목록과 보류 목록을 분리해 보고한다. - -# Output Expectations - -- 단일 입력 모드 - - 대상 REQ 또는 batch 경로 - - 구현 완료 전환 성공 여부 - - 사용한 근거 batch와 verification evidence 요약 - - 자동 보강한 항목 목록 - - 남은 blocker 또는 미해결 항목 - - `docs/srs/index.md` 갱신 결과 -- batch/무인자 일괄 모드 - - 검토한 REQ 전체 목록 - - `Implemented` 전환 성공한 REQ 목록 - - 전환 보류한 REQ 목록과 각 blocker - - `docs/srs/index.md` 갱신 결과 - -# Validation - -- 전환 성공인 경우 REQ 문서와 `docs/srs/index.md` 상태가 모두 `Implemented`인지 확인한다. -- 전환 보류인 경우 상태가 잘못 바뀌지 않았는지 확인한다. -- 사용한 verification evidence가 실제 REQ acceptance criteria와 연결되는지 확인한다. -- batch 입력 모드에서는 batch에 포함된 `Approved` REQ만 처리되었는지 확인한다. \ No newline at end of file diff --git a/.github/skills/confirm-req/SKILL.md b/.github/skills/confirm-req/SKILL.md deleted file mode 100644 index 38b0486..0000000 --- a/.github/skills/confirm-req/SKILL.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -name: confirm-req -description: "Use when: reviewing a proposed REQ for approval, running /confirm-req with a REQ ID or file path, running /confirm-req without arguments to review all unapproved REQs, promoting docs/srs//req-XXX_.md from Proposed to Approved, updating docs/srs/index.md after approval, or when the user asks in Korean to '요구 사항 명세를 검토하고 승인해줘', '요구사항 명세를 검토하고 승인해줘', 'REQ를 검토하고 승인해줘', or '승인되지 않은 전체 req를 검토하고 승인해줘'." -version: 0.9.1 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 docs/srs/Interface/req-001_picker-cli-run.md 또는 인자 없이 /confirm-req" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, approval, quality, sdlc] - related_skills: [draft-req, change-req, draft-batch] ---- - -# Purpose - -This skill validates a proposed requirement document and promotes it to `Approved` only when it is ready to be used as delivery input. - -# When to use - -- `/confirm-req req-001`처럼 REQ 승인 게이트를 통과시켜야 할 때 -- `/confirm-req`처럼 입력 없이 승인되지 않은 전체 REQ를 일괄 검토하고 승인 가능한 항목만 승격해야 할 때 -- REQ 문서의 필수 필드와 Acceptance Criteria 충족 여부를 다시 확인해야 할 때 -- `docs/srs/index.md` 상태를 REQ 문서와 맞춰야 할 때 - -## Do not use when - -- REQ가 아직 실질적으로 초안 수준이라 먼저 새로 정리하거나 다시 써야 할 때 (`draft-req`가 더 적절함) -- 기존 `Approved` 또는 `Implemented` REQ의 Requirement, Acceptance Criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) -- 승인된 REQ를 구현/검증 evidence 기준으로 `Implemented`로 전환하려는 단계일 때 (`confirm-req-implemented`가 더 적절함) -- 여러 `Approved` REQ를 delivery batch로 묶고 planning/design 단계로 넘기려는 시점일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) - -# Inputs - -- 선택 입력: REQ 식별자 - - `req-001` 같은 prefix - - 전체 REQ 파일 경로 -- 입력이 없으면 `docs/srs/**/req-*.md` 전체를 스캔해 승인되지 않은 REQ를 대상으로 삼는다. -- 대상 REQ 문서 또는 대상 REQ 문서 목록 -- `docs/srs/index.md` - -# Core Rules - -## 0. 승인 전제 - -- 대상 REQ의 현재 `Status`는 기본적으로 `Proposed`여야 한다. -- REQ는 delivery input으로 넘길 수 있을 만큼 구체적이어야 하며, 단순 질문 목록이나 TODO 모음 수준이면 승인하지 않는다. -- source Discovery 또는 상위 변경 맥락과 명백히 모순되는 REQ는 승인하지 않는다. -- `Notes`에 남은 항목은 승인 blocker인지, 승인 후 후속 작업으로 남길 수 있는 메모인지 구분해서 판단한다. -- `Priority`, `Owner`, 핵심 범위, 승인 판단에 영향을 주는 open question이 비어 있거나 사람 결정을 필요로 하면 승인하지 않는다. -- 다만 승인 이후 batch planning에서 더 구체화 가능한 비핵심 운영 메모는 `Notes`에 남아 있어도 승인 blocker가 아닐 수 있다. - -## 1. 입력 해석 - -- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 찾고, 그중 `Status: Proposed`인 문서를 승인 후보로 삼는다. -- 후보가 0개면 진행하지 않는다. -- 후보가 여러 개면 임의 선택하지 않는다. -- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 승인 게이트로 평가한다. - -## 2. 승인 게이트 - -- 아래 항목이 모두 있어야 승인 가능하다. - - `Status`, `Type`, `Priority`, `Owner` - - `Intent` - - `Requirement` - - 구체적인 `Acceptance Criteria` - - `Impacted Area` - - `Change Log` -- 미해결 placeholder, 사람 결정 필요 메모, blocker가 남아 있으면 승인하지 않는다. -- `Status`가 이미 `Approved`, `Implemented`, `Deprecated`이면 중복 승인하지 않는다. -- 일괄 모드에서는 승인 불가한 문서가 있더라도 전체 실행을 중단하지 않는다. 승인 가능한 문서만 승격하고, 나머지는 blocker와 함께 별도 보고한다. - -## 3. 허용되는 수정 - -- AI가 근거 있게 바로 고칠 수 있는 표현, 오타, 누락 제목 정리는 보강할 수 있다. -- 승인 성공 시 아래 변경을 수행한다. - - REQ 문서의 `Status`를 `Approved`로 변경 - - `Change Log`에 승인 기록을 새 항목으로 추가 - - `docs/srs/index.md`의 해당 Register 행 상태 갱신 - - 필요하면 `Recent Change Log Summary` 갱신 -- `Change Log` 승인 기록은 기존 항목을 덮어쓰지 않고 append한다. -- 승인 보류인 REQ에는 승인 기록을 남기지 않는다. -- `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. - - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. - - 필요한 상태값과 요약 항목만 in-place로 갱신한다. -- 사람 판단이 필요한 우선순위, 범위, Owner는 임의로 확정하지 않는다. -- 일괄 모드에서도 허용되는 수정 범위는 동일하며, 승인 불가 문서의 상태는 바꾸지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 경로 하나 또는 대상 REQ 문서 목록을 확정한다. -2. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Proposed` 문서를 승인 후보 목록으로 만든다. -3. 대상 REQ들과 `docs/srs/index.md`를 읽는다. -4. 각 REQ마다 필수 필드, Acceptance Criteria, Notes의 blocker 여부를 확인한다. -5. AI가 근거 있게 정리할 수 있는 사소한 일관성 문제를 먼저 고친다. - - 승인 기록을 append 하거나 문서 말미를 patch할 때는 대상 REQ 파일을 전체 기준으로 다시 읽었는지 확인한다. offset/limit 기반 부분 읽기나 도구 축약 출력만 본 상태에서 Change Log를 덧붙이면 `Change Summary` 같은 제목/본문이 잘린 채 저장될 수 있다. - - 승인 패치 직후에는 최소한 (a) 대상 REQ의 새 Change Log tail, (b) `Status: Approved`, (c) `docs/srs/index.md`의 해당 Register 행을 다시 읽어 실제 저장 결과를 검증한다. -6. source Discovery가 명시돼 있거나 `생성된 REQ 참조`로 추적 가능한 경우, 승인 전에 Discovery의 범위/결정/오픈 질문과 REQ가 모순되지 않는지 확인한다. - - 특히 Discovery의 `사용자 결정 필요 항목 요약`, `REQ로 넘기기 전 확인 체크`, `범위 경계`, `리스크/가정 목록`에서 승인 blocker가 남아 있지 않은지 본다. - - Discovery에 `Open` 상태 질문, 미확정 DECIDE/CONFIRM/DATA 항목, handoff 미승인 상태가 남아 있으면 승인 보류를 우선 검토한다. -7. 각 REQ에 대해 승인 게이트를 다시 평가한다. -8. 게이트 통과한 REQ만 `Approved`로 전환하고 인덱스를 갱신한다. -9. 게이트 미통과한 REQ는 상태를 유지하고 남은 blocker를 수집한다. -10. 입력이 없는 경우 승인 성공 목록과 승인 불가 목록을 분리해 함께 보고한다. - -# Output Expectations - -- 단일 입력 모드: - - 대상 REQ 경로 - - 승인 성공 여부 - - 자동 보강한 항목 목록 - - 남은 blocker 또는 미해결 항목 - - `docs/srs/index.md` 갱신 결과 -- 무인자 일괄 모드: - - 검토한 REQ 전체 목록 - - 승인 성공한 REQ 목록 - - 승인 불가한 REQ 목록과 각 blocker - - 자동 보강한 항목 목록 - - `docs/srs/index.md` 갱신 결과 - -# Common Pitfalls - -1. 필수 섹션은 있지만 내용이 너무 비어 있는 REQ를 승인하는 실수 - - 필드 존재 여부만 보지 말고, 실제로 구현/검증 판단에 쓸 수 있을 정도로 구체적인지 확인한다. - -2. `Notes`의 open question이나 사람 결정 필요 항목을 blocker로 처리하지 않고 승인하는 실수 - - Priority, Owner, 핵심 범위, 승인 판단에 영향을 주는 질문은 해소 전까지 승인하지 않는다. - -3. 이미 `Approved`, `Implemented`, `Deprecated`인 REQ를 다시 confirm 대상으로 다루는 실수 - - 이 경우 중복 승인 대신 현재 상태 유지 또는 `change-req`, `confirm-req-implemented` 경로를 검토한다. - -4. `docs/srs/index.md` 상태만 바꾸고 REQ 본문 상태를 같이 안 바꾸는 실수 - - 승인 성공 시 REQ 본문과 index register 상태를 함께 `Approved`로 맞춰야 한다. - -5. 일괄 모드에서 일부 blocker가 있다고 전체를 중단하는 실수 - - 일괄 모드에서는 각 REQ를 독립 게이트로 평가하고, 승인 가능한 문서만 승격한다. - -6. 승인 단계에서 사람이 정해야 할 Priority, Owner, 범위를 AI가 임의로 확정하는 실수 - - 사람이 결정해야 하는 항목은 자동으로 메우지 말고 blocker로 남긴다. - -# Verification Checklist - -- [ ] 대상 REQ 경로가 유일하게 확정되었거나, 일괄 모드 후보 집합이 올바르게 수집되었다. -- [ ] 대상 REQ의 현재 상태가 `Proposed`인지 확인했다. -- [ ] 필수 필드(`Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`)가 모두 존재한다. -- [ ] `Acceptance Criteria`가 구현/검증에 사용할 수 있을 만큼 구체적이다. -- [ ] 승인 blocker인 placeholder, open question, 사람 결정 필요 항목이 남아 있지 않다. -- [ ] source Discovery가 있으면 handoff 상태와 open question 정리가 승인 전제와 모순되지 않는다. -- [ ] 사람이 판단해야 하는 `Priority`, `Owner`, 범위를 AI가 임의 확정하지 않았다. -- [ ] 승인 성공 시 REQ 본문과 `docs/srs/index.md` 상태가 둘 다 `Approved`로 갱신되었다. -- [ ] 승인 보류 시 상태가 잘못 바뀌지 않았다. -- [ ] 일괄 모드에서는 승인 가능한 REQ만 승격되었다. -- [ ] `docs/srs/index.md`는 로컬 형식을 유지한 채 필요한 상태값만 in-place로 갱신되었다. \ No newline at end of file diff --git a/.github/skills/draft-batch-design/SKILL.md b/.github/skills/draft-batch-design/SKILL.md deleted file mode 100644 index 16cbc19..0000000 --- a/.github/skills/draft-batch-design/SKILL.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: draft-batch-design -description: "Use when: drafting the design document for an existing batch, running /draft-batch-design with a BAT ID, filling docs/batches//design.md, recording architecture and interface decisions before implementation, or planning updates to docs/project-structure.md and docs/runtime-flows.md." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, design, architecture, sdlc] - related_skills: [draft-batch, draft-batch-planning, run-batch-implementation] ---- - -# Purpose - -This skill writes the design document for a batch by summarizing architecture, changed areas, key decisions, and edge cases from the batch plan and included REQs. - -`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, 이 skill은 해당 baseline 문서를 읽고 이번 batch의 구조 영향과 흐름 영향을 비교해야 한다. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//planning.md` -- 포함된 REQ 문서들 -- `docs/batches//design.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- planning이 없거나 범위가 비어 있으면 설계를 진행하지 않는다. -- 설계는 구현 전에 결정해야 할 구조와 인터페이스를 중심으로 적는다. -- `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 없다면 design 단계는 생략할 수 있다. -- REQ 간 공통 설계 전제가 있다면 batch design에 끌어올린다. -- baseline 문서가 있으면 이번 batch 설계는 baseline 대비 변경점과 유지점이 무엇인지 명시해야 한다. -- `design.md`에는 최소한 아래 내용을 포함해야 한다. - - Architecture Summary - - Changed Areas - - Key Decisions - - Edge Cases - - Architecture Impact (`none` | `project-structure` | `runtime-flows` | `both`) - - Reference Doc Update Plan -- 기존 템플릿에 위 섹션이 없으면 설계 문서에 추가해 채운다. - -# Execution Procedure - -1. batch 경로를 확정한다. -2. planning과 포함 REQ를 읽는다. -3. planning의 `Design Gate`가 `no`이고 batch profile이 `batch-lite`면 design 없이 진행 가능한지 다시 확인하고, 그대로면 design 생략 결정을 유지한다. -4. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고 현재 baseline과 batch 범위를 비교한다. -5. `design.md`에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases를 채운다. -6. Architecture Impact와 Reference Doc Update Plan을 명시한다. -7. 설계상 blocker가 있으면 문서에 명시하고 구현으로 넘기지 않는다. - -# Validation - -- design 문서에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases가 모두 있는지 확인한다. -- planning에서 정의한 핵심 범위가 design에서 누락되지 않았는지 확인한다. -- 구조 또는 흐름 영향이 있는 batch라면 Architecture Impact와 Reference Doc Update Plan이 채워졌는지 확인한다. -- `batch-lite`에서 design을 생략한 경우 planning의 `Design Gate`와 batch index profile이 그 결정을 설명하는지 확인한다. \ No newline at end of file diff --git a/.github/skills/draft-batch-planning/SKILL.md b/.github/skills/draft-batch-planning/SKILL.md deleted file mode 100644 index 1217014..0000000 --- a/.github/skills/draft-batch-planning/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: draft-batch-planning -description: "Use when: drafting the planning document for an existing batch, running /draft-batch-planning with a BAT ID, populating docs/batches//planning.md, or setting batch scope and dependency notes before design." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, planning, implementation, sdlc] - related_skills: [draft-batch, draft-batch-design, run-batch-implementation] ---- - -# Purpose - -This skill writes the planning document for a batch by consolidating included REQs, scope, dependencies, milestones, and key risks. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//index.md` -- 포함된 REQ 문서들 -- `docs/batches//planning.md` - -# Core Rules - -- planning은 batch 수준 범위, 의존성, 순서를 명확히 해야 한다. -- REQ 문서를 다시 쓰지 말고 delivery 관점의 묶음 계획으로 요약한다. -- planning은 design이 구현 전에 필요한지 여부를 명시해야 한다. -- `batch-lite`라면 planning에 왜 design을 생략할 수 있는지 또는 언제 design이 필요해지는지 남긴다. -- planning 작성 후 batch 상태는 `in-delivery`로 올릴 수 있다. - -# Execution Procedure - -1. 입력에서 batch 경로를 확정한다. -2. batch index와 포함 REQ를 읽는다. -3. `planning.md`에 scope, out-of-scope, dependency, milestone, risk를 채운다. -4. `planning.md`의 `Design Gate`에 design 필요 여부와 근거를 적는다. -4. 필요하면 batch `index.md`의 Status를 `in-delivery`로 갱신한다. - -# Validation - -- planning 문서에 Included REQ, Delivery Plan, Dependencies, Design Gate, Milestones, Risks가 모두 존재하는지 확인한다. -- batch index와 planning의 포함 REQ 목록이 일치하는지 확인한다. \ No newline at end of file diff --git a/.github/skills/draft-batch-verification/SKILL.md b/.github/skills/draft-batch-verification/SKILL.md deleted file mode 100644 index 66f5348..0000000 --- a/.github/skills/draft-batch-verification/SKILL.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: draft-batch-verification -description: "Use when: drafting verification evidence for a batch, running /draft-batch-verification with a BAT ID, filling docs/batches//verification.md, mapping REQ acceptance criteria to evidence before release approval, or verifying sync with docs/project-structure.md and docs/runtime-flows.md." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, verification, testing, sdlc] - related_skills: [draft-batch, run-batch-implementation, confirm-batch-verification] ---- - -# Purpose - -This skill drafts the verification document for a batch by mapping included REQ acceptance criteria to available evidence and identifying remaining blockers. - -batch design 또는 REQ가 baseline 문서 생성/갱신 또는 구조/흐름 영향이 있음을 나타내면, verification은 `docs/project-structure.md`와 `docs/runtime-flows.md`가 코드와 설계에 맞게 동기화되었는지도 확인해야 한다. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- 포함된 REQ 문서들 -- `docs/batches//design.md` (존재하는 경우) -- `docs/batches//planning.md` -- `docs/batches//implementation.md` -- `docs/batches//verification.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- verification은 Discovery 성공 기준이 아니라 REQ acceptance criteria에 직접 연결한다. -- evidence가 없는 항목은 통과로 간주하지 않는다. -- stateful runtime/PYDEBUG binding 검증에서는 같은 입력을 연속 호출해도 내부 상태가 누적될 수 있음을 먼저 확인한다. REQ가 deterministic candidate/order evidence를 요구하면 '같은 입력 + 같은 초기 상태'를 보장하는 reset hook, 재초기화 step, 또는 동등한 통제 절차를 verification evidence에 포함한다. -- 확인되지 않은 항목은 `Blocking Issues`에 명시한다. -- `batch-lite`에서 design 문서가 없다면 planning의 `Design Gate`와 implementation 결과를 기준으로 구조 영향이 실제로 없었는지 확인해야 한다. -- design의 Architecture Impact가 `none`이 아니거나 baseline 문서 생성/갱신이 batch 범위에 있으면, baseline 문서 동기화 evidence를 verification에 포함해야 한다. -- baseline 문서 동기화가 필요한데 evidence가 없으면 release-ready로 간주하지 않는다. -- hardware/manual 검증이 남아 있는 상태에서 코드/문서 구현만 끝났다면 `Ready for Release: false`를 유지하고, 누락된 실장치 증거를 `Missing evidence`와 `Blocking Issues`에 명시해 confirm 단계로 성급히 올리지 않는다. -- 설정 persistence를 실장치로 검증할 때는 장치가 노출하는 실제 제어 경로를 먼저 확인한다. CTSP 설정의 재부팅 persistence를 확인하는 목적이면 가능할 때 CTSP `REBOOT`/`RESET` 명령으로 재부팅하고, 재부팅 전후 동일 CTSP readback(`DATE`, 대상 설정 명령 등)으로 복귀와 설정 유지 여부를 캡처한다. 테스트 중 설정을 바꿨다면 verification evidence를 수집한 뒤 baseline 값을 다시 써서 원복 여부도 함께 남긴다. -- CTSP 기반 실장치 검증에서는 먼저 비파괴 명령(예: `DATE`, 읽기 전용 `VISIONCFG`)으로 연결과 응답 형식을 확인한 뒤 쓰기/재부팅 테스트로 넘어간다. `VISIONCFG`처럼 값 목록이 단일 payload 안에 `/`로 직렬화되는 명령은 raw response, 분해 후 값 개수, slot별 해석(예: ratio 27번째, trailing reserved 28~32번째)을 함께 기록해야 한다. -- 업그레이드/migration 검증에서는 업그레이드 직전 legacy readback 원문과 기대 26개 값을 먼저 고정하고, 업그레이드 직후 첫 current 32값 readback의 1~26 슬롯을 그 legacy 캡처와 직접 대조해야 한다. ratio=`10`, trailing reserved=`0xFFFFFFFF`만 맞고 1~26 슬롯이 업그레이드 전 값과 다르면 migration 성공으로 간주하지 말고, 설정 영역 보존 실패/플래시 경로 문제/fixture 주입 실패 가능성을 blocker로 기록한다. -- 업그레이드 직후 boot log에 `current revision size mismatch (...)` 뒤 `vision config default`가 보이면, 이는 rev0 migration 성공 증거가 아니라 "current revision으로 해석된 저장본의 binary schema/길이가 현 코드와 다름"을 뜻한다. 이런 경우에는 downgrade 후 legacy `VISIONCFG` readback을 다시 캡처해 기존 26개 값이 되살아나는지 확인하고, 값이 복원되면 storage erase보다 revision-schema drift 또는 current-loader incompatibility를 우선 원인 후보로 기록한다. -- 업그레이드 경로 진단에서는 image slot과 `storage_partition`을 분리해서 해석한다. firmware loader가 image slot만 erase/write하는 구조라면, migration 실패 후 default current 값이 보였더라도 곧바로 "설정이 지워졌다"고 결론내리지 말고, downgrade/legacy readback 또는 raw property dump로 storage 보존 여부를 먼저 구분한다. -- verification에 사람이 명시적으로 잔여 리스크를 수용하기로 했다면 `Human Approval Memo` 섹션을 추가해 승인 결정, 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거를 구조적으로 남긴다. 이후 confirm 단계에서는 이 메모가 수용한 blocker와 미수용 blocker를 분리해서 판단할 수 있어야 한다. -- 재부팅 persistence를 자동 검증할 때는 가능하면 ext-proto reboot보다 CTSP `REBOOT`/`RESET`처럼 실제 설정 검증 경로와 같은 제어면을 우선 사용하고, 재부팅 전후 `DATE`와 대상 설정 readback을 같이 캡처한다. - -# Execution Procedure - -1. batch와 포함 REQ를 확정한다. -2. REQ acceptance criteria를 목록화한다. -3. design 문서가 있으면 Architecture Impact와 Reference Doc Update Plan을 읽고, 없으면 planning의 `Design Gate`를 읽는다. -4. implementation 결과와 테스트 로그를 근거로 evidence를 연결한다. -5. baseline 문서 동기화가 필요하면 `docs/project-structure.md`와 `docs/runtime-flows.md`의 생성/갱신 여부를 evidence에 포함한다. -6. `batch-lite`에서 design이 없었다면 verification에 `no architecture impact confirmed` 여부를 남긴다. -7. `verification.md`의 Acceptance Mapping, Evidence, Result를 채운다. -8. 재부팅 persistence나 CTSP contract 계열 실장치 검증을 수행했다면 `references/opspro-visioncfg-hardware-verification.md` 같은 session note를 남겨, 실제로 성공한 명령/응답/원복 순서를 future batch verification에 재사용할 수 있게 한다. OpsPro `VISIONCFG` 작업에서는 current 32값 round-trip, legacy 26값 호환, invalid ratio reject, reboot persistence, migration failure signature(`current revision size mismatch`)뿐 아니라, 순수 legacy device에서의 성공한 first-boot migration / post-reboot current direct-load evidence와 downgrade/re-upgrade schema-drift exclusion rule까지 그 reference에 누적한다. - -# Validation - -- 포함 REQ마다 acceptance mapping이 존재하는지 확인한다. -- evidence 없는 항목이 있으면 Result에 blocker로 반영했는지 확인한다. -- Architecture Impact가 있는 batch라면 baseline 문서 동기화 evidence 또는 blocker가 명시됐는지 확인한다. -- `batch-lite`에서 design이 없는 경우 planning과 implementation을 기준으로 구조 영향 없음 또는 design 누락 blocker 중 하나가 명시됐는지 확인한다. -- 실장치/manual evidence가 필요한 REQ인데 코드 diff나 빌드 결과만 있는 경우, `Ready for Release`가 false로 유지되고 confirm 단계 보류 사유가 적혀 있는지 확인한다. \ No newline at end of file diff --git a/.github/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md b/.github/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md deleted file mode 100644 index 694142a..0000000 --- a/.github/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md +++ /dev/null @@ -1,58 +0,0 @@ -# OpsPro VISIONCFG hardware verification reference - -Use this reference when drafting or updating verification evidence for OpsPro `VISIONCFG` CTSP and migration work. - -## CTSP hardware verification patterns - -1. Start with non-destructive probes. - - `DATE` confirms port/CTSP responsiveness. - - `VISIONCFG` read confirms payload shape before any write. -2. For `VISIONCFG`, always capture: - - raw request - - raw response - - parsed value count - - slot interpretation (1..26 legacy/public, 27 ratio, 28..32 reserved for current 32-value contract) -3. When write tests are allowed, use this order: - - current 32-value round-trip - - legacy 26-value compatibility write - - invalid ratio reject / no partial write - - reboot persistence - - migration from pure legacy device/state -4. After write-based evidence capture, restore baseline values and confirm readback. - -## Migration verification lessons - -### Exclude downgrade/re-upgrade drift from pure legacy migration evidence -If a device has already booted a newer firmware that can write a `current` `visioncfg`, then later downgrade/re-upgrade behavior is not clean REQ-005 legacy migration evidence. Treat that as a separate compatibility/schema-drift investigation, not as the primary legacy-to-current migration acceptance path. - -### Pure legacy migration acceptance path -For REQ-005 migration evidence, use a device that has never been updated to the newer firmware revision being tested. - -Required sequence: -1. Confirm legacy firmware returns 26-value `VISIONCFG`. -2. Write distinctive 26-value legacy test data. -3. Capture legacy readback before upgrade. -4. Upgrade to target firmware and reboot. -5. On first boot, read `VISIONCFG` and confirm: - - 32 values total - - slots 1..26 exactly match pre-upgrade legacy values - - slot 27 is `10` - - slots 28..32 are `-1/-1/-1/-1/-1` -6. Reboot via CTSP `REBOOT` and confirm current readback is unchanged. -7. Restore baseline current values after evidence capture. - -### Diagnostic logging for schema issues -When investigating migration failures, instrument `_load_vision_config()` to log: -- `sizeof(struct prop_header)` -- `sizeof(struct ocv_rect)` -- `sizeof(struct _vision_config_rev0)` -- `sizeof(struct _vision_config)` -- header probe `rc`, `type`, `revision` -- current read expected size vs `rc` -- revision-0 read expected size vs `rc` - -Interpretation rule: -- `current revision size mismatch (...)` followed by `vision config default` means the stored blob was interpreted as current revision but its binary size/layout did not match the current firmware's expected current schema. Do not misclassify that as successful legacy migration. - -## ABI / struct-layout caution -Do not infer target firmware ABI from `PYDEBUG`-only fallback definitions or host-side binding builds. For firmware storage compatibility analysis, trust target-firmware definitions and on-device logs first. diff --git a/.github/skills/draft-batch/SKILL.md b/.github/skills/draft-batch/SKILL.md deleted file mode 100644 index 5789833..0000000 --- a/.github/skills/draft-batch/SKILL.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -name: draft-batch -description: "Use when: creating a new batch from approved REQ IDs, running /draft-batch with one or more req IDs, scaffolding docs/batches// files, or updating docs/batches/index.md for a new delivery unit." -version: 0.9.2 -author: Justin Ko -license: private -argument-hint: "예: req-001 req-002 req-003" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, planning, documentation, sdlc] - related_skills: [suggest-batch-reqs, draft-batch-planning, draft-batch-design, draft-batch-verification, run-batch-delivery] ---- - -# Purpose - -This skill creates a new batch delivery unit from already-selected approved requirements, chooses the right delivery profile, scaffolds its folder and documents, and updates the batch register. - -# When to use - -- 포함할 `Approved` REQ 집합이 이미 확정되어 새 batch를 실제로 생성해야 할 때 -- `/draft-batch req-001 req-002`처럼 batch 문서 scaffold와 register entry가 필요할 때 -- 단일 저위험 `Approved` REQ를 `minor-change` fast path로 바로 batch-lite batch로 시작할 때 -- `suggest-batch-reqs` 결과에서 사람이 후보를 선택한 뒤 실제 batch 문서를 만들 차례일 때 - -## Do not use when - -- 아직 REQ가 `Approved`되지 않아 먼저 승인 절차가 필요할 때 (`confirm-req`가 더 적절함) -- 어떤 REQ를 함께 묶을지 아직 사람 선택이 끝나지 않았을 때 (`suggest-batch-reqs`가 더 적절함) -- 이미 존재하는 batch의 planning/design/verification 내용을 채우는 단계일 때 (`draft-batch-planning`, `draft-batch-design`, `draft-batch-verification`이 더 적절함) -- 현재 저장소의 전체 다음 SDLC 단계를 알고 싶은 상황일 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- `Approved` `REQ-ID` 목록 -- 선택 입력 - - batch profile 힌트 (`standard` | `batch-lite` | `minor-change`) -- `docs/srs/index.md` -- 대상 REQ 문서들 -- `docs/batches/index.md` -- 배치 템플릿의 논리 경로 - - `stage-pilot/templates/batches/batch-index.md` - - `stage-pilot/templates/batches/planning.md` - - `stage-pilot/templates/batches/design.md` - - `stage-pilot/templates/batches/implementation.md` - - `stage-pilot/templates/batches/verification.md` - - `stage-pilot/templates/batches/index.md` (`docs/batches/index.md` register 템플릿/기준) - -## Template path resolution - -- 배치 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/batches/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/batches/...` - 2. `~/.stage-pilot/templates/batches/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 이 resolution rule은 SKILL.md 본문에 그대로 적어 두는 편이 좋다. 배치 템플릿이 실제로 존재하는 현재 머신의 경로만 적어 두면 다른 PC나 다른 agent install shape에서 portability가 떨어진다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/batches/...` - - target: `docs/batches//...`, `docs/batches/index.md` - -# Core Rules - -- 이 skill은 사람이 선택을 마친 REQ 집합을 batch 문서로 구체화하는 단계다. -- `Approved` 상태가 아닌 REQ는 batch에 포함하지 않는다. -- 새 batch 폴더는 `docs/batches/bat-XXX_YYYYMMDD_scope/` 형식을 사용한다. -- 새 batch의 persisted profile은 `standard` 또는 `batch-lite` 중 하나다. -- `minor-change`는 profile 자체가 아니라 단일 저위험 REQ를 빠르게 `batch-lite`로 판단하는 입력 fast path다. -- 입력이 정확히 1개의 `Approved` REQ이고 범위가 국소적이며 구조/인터페이스/런타임 흐름 변경이 없으면 `minor-change` fast path를 사용할 수 있다. 이 경우 최종 batch profile은 `batch-lite`로 기록한다. -- `standard` batch는 생성 시 아래 파일을 함께 만든다. - - `index.md` ← `stage-pilot/templates/batches/batch-index.md` - - `planning.md` ← `stage-pilot/templates/batches/planning.md` - - `design.md` ← `stage-pilot/templates/batches/design.md` - - `implementation.md` ← `stage-pilot/templates/batches/implementation.md` - - `verification.md` ← `stage-pilot/templates/batches/verification.md` -- `batch-lite`는 생성 시 아래 파일만 먼저 만든다. - - `index.md` ← `stage-pilot/templates/batches/batch-index.md` - - `planning.md` ← `stage-pilot/templates/batches/planning.md` -- `batch-lite`의 `implementation.md`와 `verification.md`는 해당 단계 진입 시 생성한다. -- `batch-lite`라도 구조/인터페이스/흐름 영향이 생기면 `design.md`를 추가하고 사실상 standard depth로 다룬다. -- REQ 묶음 선택이 아직 불안정하거나 대안 비교가 필요하면 batch를 생성하지 말고 먼저 `suggest-batch-reqs`로 되돌린다. -- `docs/batches/index.md`의 register는 새 batch 생성과 함께 반드시 갱신한다. -- register 행에는 최소한 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 채운다. -- source discovery는 포함 REQ에서 역추적해 채운다. source가 하나면 그대로 기록하고, 여러 source discovery가 섞이면 축약 표기와 함께 batch 문서 Notes에 설명한다. - -# Execution Procedure - -1. 입력된 REQ 경로와 상태를 확인하고 모두 `Approved`인지 검증한다. -2. 입력된 REQ 묶음이 이미 사람에 의해 선택된 집합인지 확인한다. 아직 후보 선택 단계면 batch를 만들지 않는다. -3. 필요한 템플릿의 논리 경로를 현재 환경의 물리 경로로 해석하고, 어떤 위치를 사용할지 확정한다. -4. 현재 저장소의 기존 batch 문서(`docs/batches/index.md`, 가능하면 가장 최근 batch의 `index.md`/`planning.md`)를 먼저 읽어 로컬 heading, register 표기, 문체를 확인한다. 템플릿은 scaffold source이고, target 문서는 저장소 로컬 형식을 우선한다. -5. REQ 수, 영향 범위, 구조/인터페이스/런타임 영향 여부를 기준으로 `standard` 또는 `batch-lite` profile을 결정한다. - - 특히 같은 Discovery에서 나온 REQ 묶음이 (a) runtime 또는 interface 구조 변경, (b) 성능/운영 budget 같은 Non-Functional 기준, (c) baseline 문서 정렬을 함께 포함하면 기본값은 `standard`로 본다. 이런 조합은 planning만으로 끝나지 않고 design, implementation, verification 문서를 같은 턴에 scaffold하는 편이 drift와 재작업을 줄인다. -6. 단일 저위험 REQ면 `minor-change` fast path를 검토하되 최종 기록 profile은 `batch-lite`로 남긴다. -7. 배치 제목과 slug를 결정하고 다음 `BAT-ID`를 계산한다. -8. profile에 맞는 템플릿으로 batch 폴더와 문서를 생성하되, placeholder를 그대로 두지 말고 이번 REQ 집합 기준의 구체적 초안 내용을 즉시 채운다. -9. `index.md`에 `Profile`, Included REQ, Excluded REQ, Source Discovery, Scope, Documents, Notes를 채운다. -10. 포함 REQ의 source discovery를 수집해 `index.md`와 register에 반영한다. source가 복수면 Notes에 혼합 배경을 설명한다. -11. `planning.md`에 `Design Gate`를 기록해 design이 즉시 필요한지 여부와 근거를 남긴다. -12. `docs/batches/index.md`에 새 register 행을 추가하고 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 실제 생성 결과와 일치하게 채운다. 이때 register의 헤더/구분선/행 순서는 현재 저장소 로컬 형식을 유지한다. -13. 생성된 문서 집합과 register가 profile 판단 및 입력 REQ 집합과 일치하는지 검증한다. - -# Common Pitfalls - -1. `Approved`가 아닌 REQ를 섞는 실수 - - batch 생성은 delivery 입력이 확정된 REQ만 대상으로 해야 하므로 상태를 먼저 검증한다. - -2. 아직 사람 선택이 끝나지 않았는데 곧바로 batch를 만드는 실수 - - 여러 후보안 중 선택이 남아 있으면 먼저 `suggest-batch-reqs`에서 멈춘다. - -3. `minor-change`를 최종 profile 값처럼 기록하는 실수 - - `minor-change`는 fast path 이름일 뿐이고, persisted profile은 `batch-lite`다. - -4. `stage-pilot/templates/batches/index.md`와 `docs/batches/index.md`를 혼동하는 실수 - - 전자는 register template/source이고 후자는 실제 저장소 target 파일이다. - -5. `batch-lite`인데 구조 영향이 생겼는데도 design 문서를 만들지 않는 실수 - - planning의 `Design Gate`를 다시 보고 필요하면 `design.md`를 추가한다. - -6. 템플릿 경로를 한 설치 형태에만 고정하는 실수 - - `.stage-pilot`, `~/.stage-pilot` 순서의 resolution rule을 따른다. - -7. pack-level 문서를 빼먹는 실수 - - 외부 skill pack의 SKILL.md를 실제 수정해 버전이 바뀌면 `README.md` inventory와 `CHANGELOG.md`도 같은 턴에 함께 갱신한다. - -8. 템플릿 문구를 저장소 로컬 형식보다 우선해 그대로 덮어쓰는 실수 - - 템플릿은 scaffold source일 뿐이다. 실제 생성 시에는 현재 저장소의 기존 batch 문서 형식과 register 배치를 먼저 보고, target 문서는 그 로컬 형식에 맞춰 채운다. - -9. 템플릿 placeholder만 복사하고 batch 초안 내용을 비워 두는 실수 - - `draft-batch` 단계에서는 최소한 포함/제외 REQ, source discovery, scope, Design Gate, 핵심 리스크까지 즉시 채워 넣어 다음 stage가 바로 이어질 수 있게 한다. - -# Verification Checklist - -- [ ] 포함 REQ가 모두 `Approved` 상태다. -- [ ] 새 `BAT-ID`와 folder명이 기존 register와 충돌하지 않는다. -- [ ] 사용한 템플릿 물리 경로가 resolution rule과 일치한다. -- [ ] template source path와 generated target path를 혼동하지 않았다. -- [ ] `standard`면 5개 문서가 모두 생성되었다. -- [ ] `batch-lite`면 최소 `index.md`, `planning.md`가 생성되었고 profile이 `batch-lite`로 기록되었다. -- [ ] `planning.md`의 `Design Gate`가 현재 profile과 구조 영향 판단을 설명한다. -- [ ] `docs/batches/index.md` register 행이 실제 batch folder, profile, included REQ, discovery와 일치한다. -- [ ] source discovery가 단일/복수 여부에 맞게 기록되었다. -- [ ] 아직 사람 선택이 필요한 상태를 batch 생성으로 건너뛰지 않았다. diff --git a/.github/skills/draft-release/SKILL.md b/.github/skills/draft-release/SKILL.md deleted file mode 100644 index e9350ca..0000000 --- a/.github/skills/draft-release/SKILL.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: draft-release -description: "Use when: creating a release document from one or more release-candidate batches, running /draft-release with BAT IDs, generating docs/releases/rel-XXX_YYYYMMDD_.md, or updating docs/releases/index.md before release approval." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 bat-001 bat-002" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, drafting, documentation, sdlc] - related_skills: [confirm-batch-verification, confirm-release, capture-release-feedback] ---- - -# Purpose - -This skill creates a profile-aware release document from one or more verified batches and registers it in the release index. - -# Inputs - -- `BAT-ID` 목록 -- `docs/batches/index.md` -- 각 batch의 `verification.md` -- `docs/releases/index.md` -- 템플릿의 논리 경로 - - `stage-pilot/templates/releases/release.md` - - `stage-pilot/templates/releases/index.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/releases/rel-XXX_YYYYMMDD_.md`, `docs/releases/index.md` - -# Core Rules - -- `release-candidate`가 아닌 batch는 release에 포함하지 않는다. -- 새 release 경로는 `docs/releases/rel-XXX_YYYYMMDD_.md` 형식을 사용한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. -- release profile은 포함 batch의 운영 부담 중 가장 무거운 것을 따른다. 기본 우선순위는 `app-service` > `tooling` > `docs-only`다. -- `docs-only`는 문서, 가이드, 템플릿, 정적 산출물 변경에 사용한다. -- `tooling`은 bootstrap, 스크립트, CLI, 자동화, 패키지 동작 검증이 필요한 변경에 사용한다. -- `app-service`는 실제 서비스 배포, runtime health, 로그, 운영 확인이 필요한 변경에 사용한다. -- release 문서 생성 시 Included Batch, Profile, Scope, Rollout Plan, Rollback Plan, Verification Checklist를 채운다. - -# Execution Procedure - -1. 입력 batch들의 상태와 verification 결과를 확인한다. -2. 포함 batch의 변경 성격과 검증 evidence를 기준으로 release profile을 결정한다. -3. 다음 `REL-ID`를 계산하고 제목/slug를 정한다. -4. 필요한 release 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤 release 문서를 생성한다. -5. `docs/releases/index.md`가 없으면 `stage-pilot/templates/releases/index.md`에 해당하는 실제 template source를 해석한 뒤 생성하고, 이미 있으면 register 행을 추가한다. - -# Validation - -- 포함 batch가 모두 `release-candidate`인지 확인한다. -- release 문서에 `Profile`이 있고 profile별 검증 항목이 비어 있지 않은지 확인한다. -- release 문서와 index register 행이 함께 생성됐는지 확인한다. \ No newline at end of file diff --git a/.github/skills/draft-req/SKILL.md b/.github/skills/draft-req/SKILL.md deleted file mode 100644 index 14af9b2..0000000 --- a/.github/skills/draft-req/SKILL.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -name: draft-req -description: "Use when: turning a confirmed Discovery into one or more requirement documents under `docs/srs/`, running /draft-req with a dcy-id or Discovery path, creating docs/srs//req-XXX_.md files, updating docs/srs/index.md from FR/NFR input, or carrying forward constraints from docs/project-structure.md and docs/runtime-flows.md." -version: 1.0.3 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, drafting, documentation, sdlc] - related_skills: [confirm-discovery, change-req, confirm-req, suggest-batch-reqs] ---- - -# Purpose - -This skill converts a Discovery document into one or more requirement documents under `docs/srs/`, updates the SRS register, and writes the generated REQ references back to the source Discovery document. - -Reference: template path portability guidance lives in `references/template-path-resolution.md`. - -`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, REQ 작성 시 이 문서들을 공통 baseline 참조 문서로 읽고 필요한 구조 제약만 REQ에 반영한다. - -# When to use - -- `/draft-req dcy-001`처럼 confirmed Discovery를 REQ backlog로 정규화해야 할 때 -- Discovery의 FR/NFR을 구현 가능한 requirement 문서로 분리해야 할 때 -- `docs/srs/index.md`의 register와 Next Requirement ID를 함께 갱신해야 할 때 -- Discovery가 구조 또는 runtime baseline 갱신을 요구할 때 이를 REQ backlog에 반영해야 할 때 - -## Do not use when - -- source Discovery가 아직 `confirmed` 상태가 아니고, 아직 문제 정의나 범위 정리가 끝나지 않았을 때 -- 기존 Discovery 문서를 검토/보강/승인해야 하는 단계일 때 (`review-discovery`, `confirm-discovery`가 더 적절함) -- 새 REQ를 만드는 것이 아니라 기존 REQ의 상태, acceptance criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) -- 이미 승인된 REQ를 구현 batch로 묶는 단계일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) - -# Inputs - -- Discovery 식별자 - - `dcy-001` 같은 prefix - - 전체 Discovery ID - - `docs/discovery/.md` 경로 -- 대상 Discovery 문서 본문 -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) -- 템플릿의 논리 경로 - - `stage-pilot/templates/srs/index.md` - - `stage-pilot/templates/srs/req-template.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/srs/index.md`, `docs/srs//req-XXX_.md` -- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. - -- Repository targets - - `docs/srs/index.md` - - `docs/srs//req-XXX_.md` - -# Core Rules - -## 0. Entry gating and confirmed prerequisite - -- source Discovery는 기본적으로 `confirmed` 상태여야 한다. -- source Discovery가 아직 `draft`이거나 confirmed 여부가 불명확하면, REQ drafting을 계속 진행하지 말고 그 사실을 먼저 보고한다. -- confirmed 전제가 충족되지 않으면 `review-discovery` 또는 `confirm-discovery`가 선행되어야 한다. -- 다만 사용자가 명시적으로 "초안 REQ를 미리 뽑아 달라"고 요청한 예외 상황에서는, 승인 전 초안임을 분명히 표시하고 `Proposed` 초안으로만 제한적으로 작성할 수 있다. - -## 1. Baseline architecture reference rules - -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 REQ의 상세 본문을 대체하지 않지만, 구조 제약과 흐름 제약을 읽기 위한 공통 baseline 참조 문서로 사용한다. -- REQ에는 baseline 문서의 전체 구조나 전체 흐름을 복제하지 않는다. 구현에 필요한 제약과 acceptance 기준만 남긴다. -- source Discovery가 구조 또는 runtime baseline 갱신을 요구하면, REQ 후보에 baseline 문서 갱신 작업을 포함해야 한다. -- baseline 문서가 없고 source Discovery가 그 갭 보완을 직접 범위에 포함한다면, baseline 문서를 다루는 REQ를 누락하면 안 된다. -- baseline 문서가 없는데 source Discovery가 그 갭을 다루지 않는다면, `bootstrap-baseline` 선행 누락 가능성을 보고한다. - -## 2. 입력 해석 - -- 입력이 prefix면 `docs/discovery/` 아래에서 일치하는 문서를 찾는다. -- 여러 후보가 나오면 임의 선택하지 않고 사용자 확인이 필요하다고 보고한다. -- `.stage-pilot/` 경로는 Discovery 문서 탐색 대상에서 제외한다. -- template source는 단일 고정 경로로 가정하지 않는다. -- `stage-pilot/templates/...`는 logical path로 취급하고, 실제 읽기 경로는 현재 workspace의 StagePilot 설치 형태에 맞게 해석한다. -- template source를 읽을 때는 아래 순서로 후보를 확인한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. - -## 3. REQ 분해 원칙 - -- FR은 기본적으로 독립 구현과 독립 검증 가능 단위로 REQ 후보를 만든다. -- 하나의 FR이 과도하게 크면 복수 REQ로 분해한다. -- NFR은 독립 측정 가능성, 독립 backlog 가치, 운영 영향도가 충분할 때만 별도 REQ로 승격한다. -- 특정 FR 또는 batch 검증 기준에 흡수하는 편이 적절한 NFR은 별도 REQ를 만들지 말고 Notes에 연결 근거를 남긴다. -- baseline 문서 갱신 요구는 보통 `Documentation`, `Interface`, `Configuration` 중 가장 적절한 Type으로 분류한다. -- Discovery가 "기능 계약 + 성능/운영 기준 + baseline 문서 정렬"을 함께 요구하는 경우에는 다음 우선 분해 패턴을 먼저 검토한다. - 1. 기능 계약(상태 전이, 인터페이스, 실행 흐름) -> `Interface` 또는 가장 가까운 기능 Type - 2. 상태별 budget, latency, 처리 시간, 계측 의무 -> `Non-Functional` - 3. `docs/runtime-flows.md`/`docs/project-structure.md` 같은 baseline 정렬 -> `Documentation` -- 성능 수치가 구현 전 운영 계측값에 의존하더라도, 측정 경로와 목표 기준이 Discovery에 이미 정리돼 있으면 별도 `Non-Functional` REQ로 승격해 REQ 단계에서 evidence 수집을 요구하는 편을 우선한다. - -## 4. Type 및 경로 규칙 - -- 아래 폴더 중 정확히 하나를 선택한다. - - `Configuration` - - `Data` - - `Deployment` - - `Documentation` - - `Exception` - - `Installation` - - `Integration` - - `Interface` - - `Migration` - - `Non-Functional` - - `Testing` -- 새 REQ 경로는 `docs/srs//req-XXX_.md` 형식을 사용한다. -- `REQ-XXX` 번호는 `docs/srs/index.md`의 `Next Requirement ID`를 source of truth로 사용한다. - -## 5. 문서 작성 규칙 - -- `stage-pilot/templates/srs/req-template.md`에 해당하는 실제 template source를 해석한 뒤 문서를 만든다. -- 생성 대상 경로는 `docs/srs//req-XXX_.md`다. -- `Status` 기본값은 `Proposed`다. -- `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`를 실제 문장으로 채운다. -- baseline 문서와 충돌하거나 baseline 문서를 갱신해야 하는 구조 제약이 있으면 `Impacted Area` 또는 `Notes`에 명시한다. -- 사람 승인 없이는 확정할 수 없는 항목은 비워 두지 말고 `Notes`에 결정 필요 또는 가정으로 명시한다. -- Change Log는 최초 생성 기록을 남긴다. - -## 6. 인덱스 갱신 규칙 - -- REQ를 하나 이상 생성하면 `docs/srs/index.md`의 아래 항목을 함께 갱신한다. - - `Next Requirement ID` - - `Requirement Register` - - `Recent Change Log Summary` -- 기존 `docs/srs/index.md`가 이미 존재하면, 먼저 현재 register/header 형식과 열 순서를 읽고 그 로컬 형식을 우선 보존한다. -- template의 예시 열(`ID, Title, Type, Status, Priority, Owner, Link`)은 기본 기준이지만, 기존 index가 다른 열 이름이나 순서를 사용 중이면 사용자나 저장소가 이미 채택한 형식을 임의로 템플릿 형식으로 갈아엎지 않는다. -- 기존 index에 없는 새 필드가 필요하면 가능한 한 기존 형식에 자연스럽게 확장하고, 대규모 형식 정규화는 별도 변경으로 다룬다. -- Register에는 현재 index 형식이 요구하는 최소 메타데이터를 누락 없이 추가한다. -- 기존 `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. - - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. - - `Next Requirement ID` 표현이 `- REQ-002` 형태인지, `- Current: \`REQ-002\`` 형태인지 등은 현재 파일 형식을 따른다. - - 필요한 값만 in-place로 갱신하고, template source는 새 index bootstrap이 필요할 때만 사용한다. - -## 7. Source Discovery 역참조 갱신 규칙 - -- REQ를 하나 이상 생성하면 source Discovery 문서의 `# 0. 문서 상태`에도 어떤 REQ가 생성됐는지 기록해야 한다. -- Discovery 문서에 `생성된 REQ 참조` 항목이 이미 있으면 생성된 REQ 목록으로 갱신하고, 없으면 `후속 Discovery 참조` 바로 아래에 추가한다. -- `생성된 REQ 참조`는 생성된 REQ 번호 순서대로 유지하고 중복을 허용하지 않는다. -- source Discovery 문서를 갱신했다면 `마지막 갱신 시각(KST)`도 함께 갱신한다. -- 생성된 REQ가 아직 없으면 `생성된 REQ 참조: 없음`을 유지한다. - -# Execution Procedure - -1. 입력에서 Discovery 경로를 확정한다. -2. Discovery 문서에서 FR/NFR, 범위 경계, 리스크, 성공 기준을 읽는다. -3. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고, REQ에 남겨야 할 구조 제약과 흐름 제약을 추린다. -4. FR/NFR별 REQ 후보 목록을 만든다. -5. baseline 문서 갱신 또는 baseline 갭 보완 요구가 있으면 이를 반영한 REQ 후보를 포함한다. -6. 각 후보에 대해 Type, 제목, slug, Acceptance Criteria를 결정한다. -7. `docs/srs/index.md`의 다음 번호부터 순서대로 REQ 파일을 생성한다. -8. `docs/srs/index.md`를 갱신한다. -9. source Discovery 문서의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신한다. -10. 생성 결과와 제외한 NFR이 있으면 그 이유를 함께 보고한다. - -# Output Expectations - -- 생성된 REQ 파일 목록 -- 각 REQ의 Type과 핵심 의도 -- baseline 문서 참조 또는 생성/갱신 요구를 어떤 REQ에 반영했는지 -- `docs/srs/index.md` 갱신 결과 -- source Discovery 문서의 `생성된 REQ 참조` 갱신 결과 -- 별도 REQ로 승격하지 않은 NFR 목록과 처리 이유 - -# Common Pitfalls - -1. source Discovery가 아직 `confirmed`가 아닌데 REQ부터 만드는 실수 - - 이 경우 문제 정의와 범위가 흔들릴 수 있으므로 `review-discovery` 또는 `confirm-discovery`를 먼저 거친다. - -2. NFR을 전부 별도 REQ로 승격하는 실수 - - 독립 측정 가능성, backlog 가치, 운영 영향이 약하면 FR 또는 batch 검증 기준에 흡수하는 편이 낫다. - -3. baseline gap을 읽고도 관련 REQ를 만들지 않는 실수 - - source Discovery 범위에 baseline 보완이 포함되어 있으면, 그 갭을 다루는 REQ를 반드시 backlog에 포함한다. - -4. template source와 generated target path를 혼동하는 실수 - - template source는 `stage-pilot/templates/...` logical path를 해석한 결과이고, 생성 대상은 `docs/srs/...`다. - -5. `docs/srs/index.md`를 갱신하지 않고 REQ 파일만 만드는 실수 - - `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신해야 register가 깨지지 않는다. - -6. source Discovery 역참조를 빼먹는 실수 - - 생성된 REQ 참조와 `마지막 갱신 시각(KST)`를 source Discovery에도 반영해야 추적성이 유지된다. - -# Verification Checklist - -- [ ] source Discovery가 `confirmed` 상태인지 확인했다. -- [ ] Discovery 경로가 유일하게 확정되었고 prefix 충돌이 없다. -- [ ] `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 확인했다. -- [ ] logical template path를 실제 physical path로 올바르게 해석했다. -- [ ] template source와 generated target path를 혼동하지 않았다. -- [ ] 모든 REQ가 `docs/srs//req-XXX_.md` 형식을 따른다. -- [ ] `docs/srs/index.md`의 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신했다. -- [ ] source Discovery의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신했다. -- [ ] baseline 갭 보완이 source Discovery 범위에 포함되면 해당 REQ를 실제로 생성했다. -- [ ] 별도 REQ로 승격하지 않은 NFR은 이유를 함께 보고했다. \ No newline at end of file diff --git a/.github/skills/draft-req/references/template-path-resolution.md b/.github/skills/draft-req/references/template-path-resolution.md deleted file mode 100644 index 1ea6f88..0000000 --- a/.github/skills/draft-req/references/template-path-resolution.md +++ /dev/null @@ -1,61 +0,0 @@ -# Template Path Resolution for `draft-req` - -Use this reference when StagePilot is installed in different physical locations across agents or repositories. - -## Core rule - -Do **not** treat one physical path as the canonical template location for every agent. - -Instead: - -- treat `stage-pilot/templates/...` as the stable logical template path -- resolve that logical path against the active workspace or agent installation -- keep template source paths separate from generated target paths under `docs/srs/` - -## Logical template paths - -For `draft-req`, the logical template sources are: - -- `stage-pilot/templates/srs/index.md` -- `stage-pilot/templates/srs/req-template.md` - -These names are portable across Hermes Agent, GitHub Copilot, Claude, and similar environments. The physical path may differ. - -## Physical path resolution order - -When resolving a logical template path, check candidates in this order: - -1. Workspace-local / subtree install: `.stage-pilot/templates/...` -2. Hermes external install: `~/.stage-pilot/templates/...` - -Prefer the current workspace's installed copy over a user-level fallback. - -## Source vs target distinction - -Keep these separate: - -- Template source: `stage-pilot/templates/...` resolved to one physical path above -- Generated targets: `docs/srs/index.md` and `docs/srs//req-XXX_.md` - -Do not describe a generated `docs/srs/...` file as if it were the template source. - -## Ambiguity rule - -If multiple physical candidates exist: - -- prefer the highest-priority path in the resolution order above -- do not silently mix sources from different installs -- report which physical path was used when ambiguity could matter - -## Durable pitfall - -A common portability mistake is to hardcode only one of these: - -- `.stage-pilot/templates/...` -- `~/.stage-pilot/templates/...` - -That works for one install shape but breaks on another machine or agent. The durable pattern is: - -- logical path in the skill -- resolution rule in the workflow -- generated target path documented separately diff --git a/.github/skills/new-discovery/SKILL.md b/.github/skills/new-discovery/SKILL.md deleted file mode 100644 index 394120b..0000000 --- a/.github/skills/new-discovery/SKILL.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -name: new-discovery -description: "Use when: deciding whether to update an existing Discovery document or start a new one, running /new-discovery with or without parameters, drafting docs/discovery/dcy-__.md for a real product/service change, generating Discovery documents, or updating docs/discovery/index.md after baseline initialization is ready." -version: 1.0.1 -author: Justin Ko -license: private -argument-hint: "예: 사용자 로그인 기능 추가 - OAuth2 소셜 로그인, 세션 관리, 로그아웃 처리" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, documentation, sdlc, planning] - related_skills: [bootstrap-baseline, review-discovery, confirm-discovery, run-sdlc] ---- - -# Purpose - -This skill decides whether an incoming request should update an existing Discovery document or start a new one, then drafts or updates Discovery documentation based on issues, user requests, meeting notes, or repository state. - -입력이 없으면 현재 Discovery/SRS/batch/release 상태와 baseline 문서 상태를 읽어 새 Discovery가 필요한지 판정한다. baseline 초기화가 아직 끝나지 않은 저장소에서는 먼저 `bootstrap-baseline`으로 `docs/project-structure.md`, `docs/runtime-flows.md`, active index를 준비한 뒤 첫 real Discovery를 생성한다. - -새 Discovery를 생성하기로 판정된 경우 generated output must follow this structure: - -- `docs/discovery/dcy-__.md` -- Update `docs/discovery/index.md` - -# When to use - -다음 상황에서 사용한다. - -- 새로운 SDLC 주기를 시작해야 할 때 -- 기존 SDLC의 요구사항, 범위, 성공 기준을 조정해야 하는데 새 주기 생성이 필요한지 먼저 판정해야 할 때 -- `/new-discovery ...` 또는 파라미터 없는 `/new-discovery`로 Discovery 초안을 만들어야 할 때 -- baseline 초기화가 완료된 뒤 첫 real Discovery를 시작해야 할 때 -- 이슈/요청사항을 `docs/discovery/` 아래 표준 Discovery 문서로 전환해야 할 때 -- Discovery 문서를 템플릿 기반으로 작성하되, 추론 가능한 내용을 실제 문장으로 채워야 할 때 -- `docs/discovery/index.md`에 새 Discovery 링크를 추가해야 할 때 - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- 이슈 식별 정보 - - GitHub issue link 또는 issue 번호 -- 이슈 내용 - - 사용자 입력 원문 또는 이슈 본문 전체 - - 이슈 제목 또는 요청 요약 -- SDLC 문맥 - - 기존 `docs/discovery/` 폴더 구조 및 파일 목록 - - 기존 `docs/srs/`, `docs/batches/`, `docs/releases/` 상태 - - 기존 `docs/discovery/index.md` 내용 - - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부 - - 현재 시각 (KST) -- 저장소 문맥 - - 현재 저장소 상태 - - 저장소 현황 탐색 결과 -- 템플릿의 논리 경로 - - `stage-pilot/templates/discovery/discovery.md` - - `stage-pilot/templates/discovery/index.md` - - `stage-pilot/templates/project-structure.md` - - `stage-pilot/templates/runtime-flows.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/discovery/.md`, `docs/discovery/index.md`, 이후 필요 시 참조하는 `docs/project-structure.md`, `docs/runtime-flows.md` - -입력 해석 규칙: -- GitHub issue link 또는 issue 번호가 있으면 해당 이슈의 제목, 본문, 링크를 기준 입력으로 사용한다. -- 사용자 입력 원문과 요청 요약은 이슈 링크가 없거나 보조 설명이 필요할 때 함께 사용한다. -- 파라미터가 없으면 저장소 상태를 먼저 분류한 뒤 추천 Discovery 또는 진행 중인 반복 유지 여부를 판단한다. -- 저장소 현황 탐색 결과는 `# 3. 문제점의 요약` 중 `현재 상태` 작성에 사용한다. - -# Core Rules - -## 0. Baseline 문서 정책 - -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 SDLC의 별도 governance unit가 아니라, 이후 Discovery/REQ/Batch/Release에서 공통으로 참조하는 cross-cutting baseline 문서다. -- baseline 초기화는 첫 Discovery와 분리된 `bootstrap-baseline` 경로를 기본값으로 사용한다. -- baseline 문서와 active index가 모두 없는 저장소에서는 `new-discovery`가 Discovery를 생성하기 전에 `bootstrap-baseline`을 우선 추천하고 중단한다. -- baseline 문서가 이미 있고, Discovery가 구조/런타임 변경을 다룰 때만 해당 Discovery 안에 baseline 문서 갱신 요구를 포함한다. - -## 1. 파라미터 없는 실행 판정 규칙 - -- `/new-discovery`가 파라미터 없이 실행되면 먼저 아래 경로를 점검한다. - - `docs/discovery/` - - `docs/srs/` - - `docs/batches/` - - `docs/releases/` - - `docs/project-structure.md` - - `docs/runtime-flows.md` -- 파라미터 없는 실행은 아래 3가지 저장소 상태 중 하나로 판정한다. - - `no-history`: Discovery와 후속 SDLC 문서가 사실상 없고 첫 반복을 시작해야 하는 상태 - - `active-cycle`: draft/review Discovery, Proposed REQ, 진행 중 batch, 미정리 release처럼 활성 unit가 존재해 파라미터 없는 실행만으로는 다음 반복 추천이 모호한 상태 - - `ready-for-next-iteration`: active unit는 없고 새 반복을 시작할 준비가 된 상태 -- `no-history`이고 baseline 문서 또는 active index가 없으면 `bootstrap-baseline`을 우선 추천하고 새 Discovery를 생성하지 않는다. -- `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery를 생성한다. -- `active-cycle`이면 새 Discovery를 생성하지 않고 중단한다. 대신 현재 반복을 계속 진행하도록 `run-sdlc` 또는 해당 후속 skill을 추천한다. -- `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 차이를 읽고 추천 follow-up Discovery를 생성한다. -- `ready-for-next-iteration`에서 후보 ranking만 먼저 필요하면 `suggest-next-discovery`를 사용하고, 실제 문서 생성이 확정되면 `new-discovery`로 이어 간다. -- baseline 문서가 없고 active unit도 없다면, follow-up Discovery보다 `bootstrap-baseline`을 우선한다. - -## 2. 기존 Discovery 갱신 vs 새 Discovery 생성 판정 규칙 - -- 기존 주기 후보 탐색과 비교는 아래 `확인 절차`를 따른다. -- 후보 문서가 있고 아래 조건을 모두 만족하면 기존 Discovery를 갱신한다. - - 변경이 기존 문제 정의와 핵심 사용자 시나리오를 유지한다. - - FR/NFR 변경이 문구 명확화, 측정 가능성 보강, 범위 경계의 미세 조정, 우선순위 재정렬 수준이다. - - 요구사항 삭제 또는 수정이 기존 REQ, batch planning, batch design, implementation 산출물을 사실상 무효화하지 않는다. - - 대상 Discovery가 아직 초기 정리 단계이거나, 후속 단계 문서가 있어도 변경 영향이 국소적이라 동일 문서 안에서 추적 가능하다. -- 아래 조건 중 하나라도 만족하면 새 Discovery를 생성한다. - - FR/NFR 삭제 또는 수정이 범위, 이해관계자, 핵심 사용자 시나리오, 성공 기준을 실질적으로 바꾼다. - - 기존 하위 단계 문서나 구현 결과를 다시 써야 할 정도로 설계/구현 전제가 깨진다. - - 기존 Discovery가 사실상 Freeze 이후 단계로 진행되어 변경 이력을 같은 문서에 계속 누적하면 책임 경계가 흐려진다. - - 운영 환류, 새로운 이슈, 정책 전환 등으로 후속 반복을 독립 주기로 추적하는 편이 명확하다. -- 기존 Discovery를 더 이상 기준으로 쓰지 않더라도 기존 문서를 삭제하지 않는다. 새 문서를 생성하는 경우 기존 문서는 참조 대상으로 유지하고, 대체 또는 후속 관계를 문서에 남긴다. -- 판정 결과는 반드시 아래 둘 중 하나로 명시한다. - - `기존 Discovery 갱신` - - `새 Discovery 생성` - -### 확인 절차 - -- 입력 원문 또는 추천 Discovery 주제에서 아래 식별 정보를 먼저 추출한다. - - GitHub issue 번호 또는 링크 - - 이슈 제목의 핵심 명사 - - 기능명, 모듈명, 동사, 영향 범위 키워드 -- `docs/discovery/` 아래 기존 문서를 탐색할 때는 `.stage-pilot/` 경로를 제외하고 아래 순서로 후보를 찾는다. - - issue 번호 또는 링크의 정확 일치 - - 이슈 제목과 파일명 slug의 핵심 키워드 일치 - - 기존 Discovery 문서 안의 문제 정의, FR/NFR, 범위 경계 키워드 일치 -- 후보 문서가 하나 이상 발견되면 각 후보마다 아래 항목을 비교한다. - - 같은 문제 정의 또는 같은 기능 묶음을 다루는지 - - 현재 단계가 Discovery, REQ 정제, batch delivery, release 이후 어디까지 진행됐는지 - - 이번 변경이 기존 FR/NFR의 미세 조정인지, 핵심 요구사항 폐기 또는 재정의인지 - - 기존 하위 단계 문서와 구현 산출물을 유지 가능한지, 다시 작성해야 하는지 -- 후보가 없으면 기본 판정은 `새 Discovery 생성`이다. -- 후보가 하나이고 판정 규칙에 명확히 부합하면 해당 후보를 기준으로 자동 판정한다. -- 후보가 여러 개이거나, 하나의 후보라도 `기존 Discovery 갱신`과 `새 Discovery 생성` 사이에서 명확히 갈리지 않으면 임의로 진행하지 않는다. - - 후보 문서 경로 목록과 각 후보의 비교 근거를 사용자에게 보고한다. - - 사용자 확인 전에는 새 폴더 생성이나 기존 문서 갱신을 하지 않는다. -- 최종 판정 결과에는 선택된 대상 문서 경로 또는 새로 만들 Discovery 생성 사유를 반드시 함께 기록한다. - -## 3. Discovery ID 규칙 - -- `docs/discovery/` 아래 기존 문서 중 `dcy-<3자리>_` 패턴의 최대 번호를 찾는다. -- 다음 번호를 3자리 zero-pad로 계산한다. -- 오늘 날짜를 `YYYYMMDD` 형식으로 구한다. -- 사용자 입력 원문, 추천 Discovery 제목, 이슈 본문 전체, GitHub issue link에서 `topic-slug`를 만든다. -- `topic-slug`는 소문자 kebab-case의 형태로 만든다. -- slug 규칙: - - 영문/숫자 외 문자는 `-`로 치환 - - 연속 `-`는 하나로 축약 - - 시작/끝 `-` 제거 - - 결과가 비면 `untitled` 사용 -- 최종 `DISCOVERY_ID`는 `dcy-__` 형식이어야 한다. - -## 4. 문서 생성/갱신 규칙 - -- 판정 결과가 `새 Discovery 생성`이면 새 파일을 `docs/discovery/.md` 경로로 생성한다. -- `새 Discovery 생성`인 경우 `stage-pilot/templates/discovery/discovery.md`에 해당하는 실제 template source를 해석한 뒤 Discovery 문서를 생성한다. -- `docs/discovery/index.md`가 없으면 `stage-pilot/templates/discovery/index.md`에 해당하는 실제 template source를 해석한 뒤 생성한다. -- Discovery가 구조/런타임 baseline 갭을 직접 다루는 경우, `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 이후 산출물의 논리 템플릿 참조로 취급한다. -- `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. -- 동일 경로가 이미 있으면 덮어쓰지 말고 다음 ID를 재계산한다. -- 불필요한 파일은 만들지 않는다. - -## 5. Discovery 초안 작성 원칙 - -- 입력 문장이 짧더라도 기능 목적, 예상 사용자, 산출물, 성공 조건을 합리적으로 추론해 초안을 작성한다. -- 입력만으로 추론 가능한 내용은 플레이스홀더로 남기지 말고 실제 문장으로 채운다. -- 사용자 승인, 외부 데이터, 정책 결정, 확정 범위 경계처럼 추론만으로 확정하면 안 되는 항목만 플레이스홀더로 남긴다. -- 공란은 허용하지 않는다. -- 합리적 기본안을 제시할 수 있으면 초안 문장으로 채운다. -- 사용자 확인이 반드시 필요하면 관련 본문에는 최소 플레이스홀더를 남기고 `# 10. 사용자 결정 필요 항목 요약`에 정리한다. -- `기존 Discovery 갱신`인 경우 FR/NFR 변경 또는 삭제 이유, 영향 받는 하위 단계, 기존 문서와의 차이를 문서 안에 명시해 변경 근거를 남긴다. -- Discovery가 구조/런타임 baseline 변경을 직접 다루는 경우 `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 9. 파일 처리 결과`에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 명시한다. - -## 6. 플레이스홀더 유지 규칙 - -- 사용자 승인 전 확정되면 안 되는 정책/범위/산출물 위치/대상 독자 결정은 본문에도 플레이스홀더를 남긴다. -- `# 10. 사용자 결정 필요 항목 요약`의 항목은 반드시 아래 형식 중 하나를 사용한다. - - `{{DECIDE_*}}` - - `{{CONFIRM_*}}` - - `{{DATA_*}}` -- `TBD` 같은 일반 문자열보다 의미가 드러나는 플레이스홀더를 우선 사용한다. - -# Discovery Sections To Fill - -다음 섹션은 자동으로 채워야 한다. - -## `# 1. 계획 상태 요약` - -- `해석` 2줄은 입력 요청 또는 파라미터 없는 실행의 저장소 상태 해석을 바탕으로 직접 작성한다. - -## `# 0. 문서 상태` - -- `새 Discovery 생성`이면 `대체됨`, `후속 주기 참조` 기본값은 `없음`으로 둔다. -- 기존 Discovery를 대체하거나 이어받는 새 Discovery를 만드는 경우, 이전 Discovery 문서에도 해당 참조 필드를 갱신한다. -- `기존 Discovery 갱신`이면 기존 참조 값을 보존하고, 새 관계가 확정된 경우에만 관련 필드를 갱신한다. - -플레이스홀더 치환 규칙: -- `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` -- `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 -- `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 - -## `# 2. 요구사항 판정 결과` - -- 기본값은 `신규`로 둔다. -- 입력만으로 명백한 중복/유사라고 판단 가능한 경우에만 다르게 쓴다. -- 근거 2줄도 직접 작성한다. - -## `# 3. 문제점의 요약` - -- 현재 상태, 기대 상태, 이해관계자, 주요 사용자 시나리오를 입력 기반으로 채운다. -- 특히 `현재 상태`는 저장소 탐색 결과와 baseline 문서 존재 여부를 반영해야 한다. - -## `# 4. 이번에 정의할 변경` - -- 핵심 변경 항목 1~3개를 직접 작성한다. -- Discovery가 구조 또는 runtime baseline 갭을 다루면 baseline 문서 갱신을 핵심 변경 항목에 포함한다. -- `영향 범위`의 코드/문서/운영 영향도 추론해 채운다. - -## `# 5. 요구사항 목록` - -- FR 2개 이상, NFR 1개 이상을 직접 작성한다. -- Discovery가 구조 또는 runtime baseline 갭을 다루는 경우, `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 FR에 포함한다. -- In Scope/Out of Scope는 초안 수준으로 제안한다. -- 사용자 승인 없이는 확정할 수 없는 범위 경계는 플레이스홀더로 남긴다. - -## `# 6. 리스크/가정 목록` - -- 리스크 2개 이상, 가정 2개 이상을 직접 작성한다. -- 오픈 질문은 정말 결정이 필요한 경우에만 남긴다. - -## `# 7. 초기 성공 기준` - -- 성공 기준 2개 이상을 작성한다. -- Discovery가 구조 또는 runtime baseline 변경을 다루는 경우 baseline 문서와 이후 산출물 참조 가능성을 성공 기준에 연결한다. -- 각 성공 기준에 대해 측정 방식과 데이터 출처 초안을 작성한다. - -## `# 9. 파일 처리 결과` - -- 생성 결과와 최소 참조 문서를 채운다. -- Discovery가 구조 또는 runtime baseline 갭을 다루면 `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 참조 문서에 포함한다. -- 참조 문서가 없으면 `없음`이라고 쓴다. - -## `# 10. 사용자 결정 필요 항목 요약` - -- 자동 추론으로 확정할 수 없는 항목만 `DECIDE`, `CONFIRM`, `DATA`에 넣는다. -- 이 섹션 항목은 설명문이 아니라 플레이스홀더 형식으로 남긴다. - -## `# 11. Discovery Freeze` - -- `Handoff Decision` 기본값은 `보류`다. -- `Handoff Rationale`에는 현재 상태를 짧게 적는다. - - 예: `초기 Discovery 초안 생성 완료, REQ 초안 작성 전 사용자 확인 필요` - -# Repository Exploration - -Discovery 문서의 `# 3. 문제점의 요약` 중 `현재 상태`를 채우기 위해 저장소 현황 탐색을 수행한다. - -1. 이슈 원문 또는 추천 주제에서 핵심 키워드(기능명, 모듈명, 동사)를 추출한다. -2. 키워드를 기반으로 저장소에서 관련 파일, 디렉터리, 함수, 기존 문서를 탐색한다. -3. 탐색 범위는 아래와 같다. - - 소스 파일: `.ts`, `.js`, `.py`, `.go`, `.java` 등 - - 설정 파일 - - 기존 `docs/` 문서 -4. 아래 항목은 제외한다. - - 테스트 파일 - - 의존성 잠금 파일 (`package-lock.json`, `.lock`) -5. 탐색 결과에서 아래 내용을 추출한다. - - 관련 파일 경로 목록 (최대 10개) - - 현재 구현 방식 요약 (함수명, 클래스명, 주요 로직 1~3줄) - - 현재 상태에서 이슈 원문이 지적하는 문제가 실제로 존재하는지 여부 - - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부와 최신성 판단에 필요한 단서 -6. 관련 파일을 찾지 못하면 `관련 파일 없음 — 신규 기능으로 추정`으로 기록한다. -7. 탐색 결과는 `# 3. 문제점의 요약`의 `현재 상태` 기술에만 반영한다. - -# Execution Procedure - -다음 순서대로 작업한다. - -1. 입력 원문이 있으면 이슈명을 한 문장으로 정리하고, 없으면 저장소 상태를 먼저 분류한다. -2. 파라미터 없는 실행이면 discovery/srs/batches/releases/baseline 문서 상태를 읽어 `no-history`, `active-cycle`, `ready-for-next-iteration` 중 하나로 판정한다. -3. 입력 원문이 없고 `active-cycle`이면 새 Discovery 자동 생성을 중단하고 현재 반복을 계속 진행해야 하는 이유와 추천 skill을 보고한다. -4. `no-history`이고 baseline 문서 또는 active index가 비어 있으면 `bootstrap-baseline`을 우선 추천하고 Discovery 생성을 중단한다. -5. `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery 생성 대상으로 확정한다. -6. 입력 원문이 없고 `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 상태를 바탕으로 추천 follow-up Discovery 주제를 정한다. -7. 입력 원문, 이슈 링크, 신규 요구 요약처럼 명시적 신규 입력이 있으면 `active-cycle` 여부와 무관하게 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. -8. 입력 원문이 없고 추천 Discovery 주제가 정해졌으면 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. -9. 후보 비교 결과를 바탕으로 `기존 Discovery 갱신` 또는 `새 Discovery 생성`으로 판정한다. -10. `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 진행하는 경우, 기존 active unit와의 범위 충돌 여부와 병렬 진행 사유를 함께 기록한다. -11. 판정이 애매하면 사용자 확인 전까지 자동 생성 또는 자동 갱신을 중단하고 후보와 근거를 보고한다. -12. 저장소 현황 탐색을 수행한다. -13. 판정 결과가 `새 Discovery 생성`이면 기존 `docs/discovery/` 경로를 다시 스캔해 다음 번호를 계산한다. -14. `새 Discovery 생성`이면 날짜와 slug를 조합해 `DISCOVERY_ID`를 만든다. -15. `새 Discovery 생성`이면 `docs/discovery/.md` 파일을 생성한다. -16. `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. -17. 아래 플레이스홀더를 우선 치환한다. - - `{{DOC_STATUS:draft|review|confirmed}}` -> `draft` - - `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` - - `{{ISSUE_NAME:짧은 한글 또는 영문 이슈명}}` -> 입력 원문 또는 요약 이슈명 - - `{{ISSUE_NAME}}` -> 입력 원문 또는 요약 이슈명 - - `{{CREATED_AT_KST:TBD}}` -> 현재 시각(KST) - - `{{CREATED_AT_KST}}` -> 현재 시각(KST) - - `{{UPDATED_AT_KST:TBD}}` -> 현재 시각(KST) - - `{{UPDATED_AT_KST}}` -> 현재 시각(KST) - - `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 - - `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 - - `{{OUTPUT_PATH}}` -> 생성 파일 경로 - - `{{FILE_RESULT:생성|갱신|미생성}}` -> `생성` -18. Discovery가 구조 또는 runtime baseline 갭을 직접 다루면 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 핵심 변경과 FR에 반영한다. -19. `기존 Discovery 갱신`인 경우 `{{OUTPUT_PATH}}`와 `{{FILE_RESULT}}`는 갱신 대상 파일과 `갱신`으로 해석한다. -20. `새 Discovery 생성`이면서 기존 Discovery를 대체하거나 이어받는 경우, 기존 Discovery 문서에 `대체됨` 또는 `후속 주기 참조` 필드를 새 문서 경로로 갱신한다. -21. Discovery 문서에서는 추론 가능한 플레이스홀더를 적극적으로 실제 내용으로 치환한다. -22. 추론 불가능하거나 사용자 확인이 필요한 항목만 플레이스홀더로 남긴다. -23. `새 Discovery 생성`인 경우에만 `docs/discovery/index.md` 전역 인덱스에 새 행을 추가한다. -24. `기존 Discovery 갱신`인 경우 전역 인덱스에 새 행을 추가하지 않는다. - -# Global Index Rules - -- `docs/discovery/index.md`가 없으면 아래 골격으로 새로 만든다. -- `## Discovery 목록`에는 새 행을 추가한다. -- 형식은 아래와 같다. - -`| | | | [Discovery](./.md) | draft |` - -- 테이블 구조 자체는 바꾸지 않는다. -- `기존 Discovery 갱신`으로 판정된 경우 `docs/discovery/index.md`에 새 행을 추가하지 않는다. -- 같은 `DISCOVERY_ID`가 이미 있으면 중복 추가하지 않는다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 판정 결과 (`기존 Discovery 갱신` 또는 `새 Discovery 생성`) -- 파라미터 없는 실행이면 저장소 상태 분류 (`no-history` | `active-cycle` | `ready-for-next-iteration`) -- `active-cycle` 상태였다면 자동 중단 사유 또는 병렬 Discovery 허용 사유 -- 대상 `DISCOVERY_ID` -- 확인에 사용한 후보 Discovery 목록과 각 후보의 비교 근거 -- 새 Discovery인 경우 생성한 파일 경로 -- 기존 Discovery 갱신인 경우 갱신한 파일 경로 -- 기존 Discovery와의 대체 또는 후속 참조 갱신 결과 -- `docs/discovery/index.md` 갱신 결과 또는 미갱신 사유 -- `docs/project-structure.md`, `docs/runtime-flows.md` baseline 상태와 Discovery 반영 결과 -- 저장소 현황 탐색 결과 -- 자동으로 채운 Discovery 핵심 섹션 요약 -- 사용자 확인이 필요해 플레이스홀더로 남긴 항목 요약 - -# Validation - -완료 전에 아래를 확인한다. - -- `새 Discovery 생성`이면 생성 경로가 `docs/discovery/.md` 형식을 만족하는지 -- `새 Discovery 생성`이면 Discovery 문서가 생성되었는지 -- `새 Discovery 생성`이면 `docs/discovery/index.md`가 신규 생성 또는 올바르게 갱신되었는지 -- Discovery가 구조 또는 runtime baseline 갭을 다루면 `docs/project-structure.md`, `docs/runtime-flows.md` 갱신 요구가 문서 본문에 포함됐는지 -- `기존 Discovery 갱신`이면 대상 Discovery 문서만 갱신되었는지 -- `기존 Discovery 갱신`이면 새 `DISCOVERY_ID`와 불필요한 파일이 생성되지 않았는지 -- 기존 Discovery 후보 탐색 결과와 판정 근거가 보고 가능한 형태로 정리되었는지 -- 파라미터 없는 실행에서 `active-cycle` 상태라면 사용자 확인이나 명시적 신규 입력 없이 새 Discovery를 자동 생성하지 않았는지 -- `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 생성한 경우, 기존 active unit와의 범위 충돌 여부와 판정 근거가 함께 정리됐는지 -- 판정이 애매한 경우 사용자 확인 없이 새 폴더 생성이나 기존 문서 갱신을 진행하지 않았는지 -- `대체됨`, `후속 주기 참조` 필드가 `없음` 또는 유효한 Discovery 경로로 채워졌는지 -- 새 Discovery가 기존 Discovery의 대체 또는 후속 반복이라면 기존 Discovery의 참조 필드가 함께 갱신되었는지 -- 추론 가능한 섹션은 실제 문장으로 채워졌는지 -- 사용자 확인이 필요한 항목만 플레이스홀더로 남았는지 \ No newline at end of file diff --git a/.github/skills/review-discovery/SKILL.md b/.github/skills/review-discovery/SKILL.md deleted file mode 100644 index 5b6b4e0..0000000 --- a/.github/skills/review-discovery/SKILL.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -name: review-discovery -description: "Use when: reviewing an existing Discovery document for missing content, additional OQ candidates, internal consistency, duplication, unnecessary content, placeholder residue, and REQ handoff readiness without changing lifecycle state." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_topic.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, review, quality, sdlc] - related_skills: [new-discovery, confirm-discovery, draft-req] ---- - -# Purpose - -This skill reviews an existing Discovery document as a non-gating quality check before `confirm-discovery` or before human review. It looks for missing content, candidate open questions, internal inconsistencies, duplicated statements, unnecessary text, placeholder residue, and weak handoff readiness. - -이 skill은 별도 lifecycle stage를 만들지 않는다. 기본 목적은 Discovery 품질을 점검하고 수정 우선순위를 정리하는 것이며, 문서 상태 전환이나 index 갱신은 수행하지 않는다. - -# When to use - -- `/review-discovery dcy-xyz` 형태로 Discovery 문서를 먼저 점검하고 싶을 때 -- `confirm-discovery` 전에 누락, 중복, OQ 후보, 표현 불일치를 빠르게 정리하고 싶을 때 -- Discovery 문서가 길어지면서 같은 내용이 여러 섹션에 흩어졌는지 확인해야 할 때 -- 템플릿 잔여 문구, 불필요한 상세 구현 설명, 근거 없는 확정 표현이 남아 있는지 보고 싶을 때 -- 사람이 수정할 항목과 AI가 바로 정리할 수 있는 항목을 분리해서 보고 싶을 때 - -# Inputs - -- Discovery 식별자 - - `dcy-001` 같은 prefix - - `dcy-001_20260424_topic-slug` 같은 전체 ID - - `docs/discovery/.md` 같은 직접 경로 -- 대상 Discovery 문서 본문 -- 필요 시 관련 저장소 문맥 - - 현재 저장소의 관련 파일, 문서, 설정 - - `docs/discovery/index.md` - - `docs/project-structure.md`, `docs/runtime-flows.md` -- 현재 시각 (KST) - -# Core Rules - -## 1. 역할 경계 - -- 이 skill은 review helper이며, active SDLC 단계로 취급하지 않는다. -- 아래 항목은 수정하지 않는다. - - `# 0. 문서 상태`의 `상태` - - `# 11. Discovery Freeze`의 `Handoff Decision`, `Ready for REQ Drafting`, `Confirmed By`, `Confirmed At (KST)` - - `docs/discovery/index.md` -- 이 skill은 REQ 생성, 승인, 상태 승격을 하지 않는다. -- 사실 관계를 바꾸는 추정성 수정은 하지 않는다. 근거 없는 항목은 finding 또는 OQ 후보로 남긴다. -- 사용자가 단순 검토만 요청한 경우 결과는 findings-first로 보고한다. -- 사용자가 `정리`, `수정`, `보강`, `반영`까지 명시한 경우에만 의미 변화가 없는 경미한 편집을 수행할 수 있다. - -## 2. 기본 검토 축 - -반드시 아래 항목을 검토한다. - -### 2-1. 누락 항목 - -- 템플릿 기준으로 필요한 섹션과 핵심 내용이 빠지지 않았는지 확인한다. -- 빈 bullet, `TBD`, `{{...}}`, `필요 없으면 삭제` 잔여 문구를 누락 또는 미정리 항목으로 본다. -- 아래 핵심 정보가 약하거나 비어 있으면 finding으로 기록한다. - - 문제의 현재 상태와 기대 상태 - - 핵심 변경 항목과 영향 범위 - - FR/NFR와 범위 경계 - - 리스크, 가정, 성공 기준 - - REQ handoff 전 확인 기준 -- 단, `# 2. 요구사항 판정 결과`가 `완전 중복`이면 템플릿 가이드에 따라 이후 섹션 축약을 허용한다. 이 경우 `# 9. 파일 처리 결과`까지의 중복 판정 근거와 참조만 충분한지 본다. - -### 2-2. 추가 OQ 후보 - -- 아래 조건이면 새 OQ 후보를 제안한다. - - 범위 경계가 모호해 In Scope/Out of Scope가 흔들리는 경우 - - 성공 기준은 있는데 측정 방식이나 데이터 출처가 없는 경우 - - 리스크 완화 책임자나 판단 주체가 비어 있는 경우 - - 외부 정책, 외부 시스템, 운영 절차, 승인자 결정이 필요한데 본문에 근거가 없는 경우 - - 서로 다른 섹션이 충돌해서 사람 판단 없이는 확정할 수 없는 경우 -- 이미 다른 섹션에서 답이 있는 질문은 OQ로 늘리지 말고, answered cleanup 대상으로 분류한다. -- 사소한 문장 다듬기나 서식 문제는 OQ로 만들지 않는다. - -### 2-3. 문서 일관성 - -- 이슈명, 핵심 변경, FR/NFR, 범위 경계, 성공 기준, Freeze rationale 사이의 용어와 범위가 일치하는지 확인한다. -- `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 본다. -- `Open` OQ가 남아 있는데 Freeze가 사실상 진행 가능처럼 읽히는지 확인한다. -- 문서 안에서 같은 개념을 서로 다른 이름으로 부르는 경우 정규화 필요 finding으로 기록한다. - -### 2-4. 중복 제거 대상 - -- 같은 사실이나 요구를 여러 섹션에서 반복하는지 찾는다. -- 중복은 아래 세 종류로 분류한다. - - 완전 중복: 문장만 다르지만 의미가 같은 경우 - - 부분 중복: 핵심은 같고 세부만 다른 경우 - - 잘못된 재기술: 상위 요약과 하위 상세가 서로 다른 말을 하는 경우 -- 중복이 있으면 어떤 섹션을 source of truth로 둘지 함께 제안한다. - -### 2-5. 불필요한 내용 - -- Discovery 단계에 필요 없는 구현 디테일, 과도한 알고리즘 설계, 배포 절차 상세, 이미 결정된 이력 서술을 찾는다. -- 현재 반복의 문제 정의와 성공 기준에 기여하지 않는 배경 설명은 삭제 또는 압축 후보로 본다. -- 템플릿 가이드 문구, 예시 텍스트, 삭제 예정 placeholder 설명이 본문에 남아 있으면 제거 대상으로 본다. - -## 3. 추가 검토 항목 - -사용자 요청 외에도 아래를 함께 본다. - -### 3-1. 근거와 사실 일치성 - -- 문서가 현재 저장소 상태를 서술하는 경우, 그 주장에 직접 관련된 파일이나 문서와 모순이 없는지 확인한다. -- 저장소 확인은 문서의 핵심 주장과 직접 연결된 범위만 탐색한다. 구현 전체를 감사하지는 않는다. -- 저장소 근거와 충돌하는 서술은 높은 우선순위 finding으로 기록한다. - -### 3-2. 실행 가능성과 측정 가능성 - -- FR/NFR, 성공 기준, 체크 항목이 다음 단계에서 실제 REQ/검증 항목으로 이어질 정도로 구체적인지 본다. -- `개선한다`, `지원한다`, `정리한다`처럼 모호한 동사만 있고 판정 기준이 없으면 구체화 필요 finding으로 기록한다. -- 성공 기준은 가능한 한 측정 방식과 데이터 출처에 연결되어야 한다. - -### 3-3. 플레이스홀더 위생 - -- 사용자 결정이 필요한 placeholder와 단순 미정리 placeholder를 구분한다. -- 같은 의미의 placeholder가 본문과 `# 10. 사용자 결정 필요 항목 요약`에 중복으로 남아 있으면 정리 대상으로 본다. -- 이미 문서 근거로 해소 가능한 placeholder는 unresolved로 방치하지 않는다. - -## 4. 허용되는 편집 범위 - -사용자가 수정까지 요청한 경우에만 아래 경미한 편집을 허용한다. - -- 중복 문장 삭제 또는 압축 -- 템플릿 잔여 문구 제거 -- 번호, 체크리스트, 링크, 용어 표기 일관성 정리 -- 답이 이미 문서 안에 있는 OQ를 본문 반영 후 resolved cleanup으로 정리 -- 의미 변화 없는 섹션 재배치 -- 실제 내용이 바뀌었을 때 `마지막 갱신 시각(KST)` 갱신 - -아래는 이 skill로 수정하지 않는다. - -- 새 요구사항 생성 -- 범위 재정의 -- 정책 확정 -- 승인자 지정 -- 상태 전환과 index 갱신 - -# Review Heuristics By Section - -섹션별로 최소한 아래를 점검한다. - -- `# 0. 문서 상태` - - 문서 ID, 이슈명, 시각 정보, 참조 필드가 자기모순 없는지 확인한다. -- `# 1. 계획 상태 요약` - - 현재 작성 배경과 이번 Discovery의 핵심 판단 포인트가 2줄 요약으로 드러나는지 본다. -- `# 2. 요구사항 판정 결과` - - 신규/유사/중복 판정과 근거가 실제 본문 범위와 맞는지 본다. -- `# 3. 문제점의 요약` - - 현재 상태, 기대 상태, 이해관계자, 시나리오가 왜 이 Discovery가 필요한지를 설명하는지 본다. -- `# 4. 이번에 정의할 변경` - - 변경 항목과 영향 범위가 `# 5` 요구사항과 연결되는지 본다. -- `# 5. 요구사항 목록` - - FR/NFR가 구분되어 있고 범위 경계가 충돌하지 않는지 본다. -- `# 6. 리스크/가정 목록` - - 리스크, 가정, OQ가 각각 역할에 맞게 쓰였는지 본다. -- `# 7. 초기 성공 기준` - - 측정 대상, 방식, 데이터 출처가 요구사항과 연결되는지 본다. -- `# 8. REQ로 넘기기 전 확인 체크` - - 체크 항목이 선언만 있고 판단 기준이 없는지 본다. -- `# 9. 파일 처리 결과` - - 참조 문서가 실제 검토 근거를 반영하는지 본다. -- `# 10. 사용자 결정 필요 항목 요약` - - DECIDE, CONFIRM, DATA가 실제 unresolved 항목과 맞는지 본다. -- `# 11. Discovery Freeze` - - review 결과와 무관하게 상태 전환 없이, 본문과 논리적 충돌만 점검한다. - -# Execution Procedure - -1. 입력에서 Discovery 문서 경로를 확정한다. -2. 대상 Discovery 문서를 읽고, 필요 시 `docs/discovery/index.md`와 직접 관련된 저장소 문맥을 읽는다. -3. 문서를 `중복 판정으로 축약 가능한 문서`인지 `정상 Discovery 검토 대상`인지 먼저 분류한다. -4. 위 `기본 검토 축`과 `추가 검토 항목` 기준으로 findings를 수집한다. -5. findings를 아래 우선순위로 분류한다. - - `blocking`: REQ handoff 또는 사실 관계에 직접 문제를 만드는 항목 - - `major`: 지금 고치는 편이 좋은 구조/일관성/측정 가능성 문제 - - `minor`: 표현, 중복, 서식, 경미한 cleanup 문제 -6. 추가 OQ 후보가 있으면 기존 OQ와 중복 여부를 제거한 뒤 후보 목록으로 정리한다. -7. 사용자가 수정까지 요청한 경우에만 `허용되는 편집 범위` 안에서 경미한 cleanup을 수행한다. -8. 편집을 수행한 경우 `마지막 갱신 시각(KST)`만 갱신하고, 상태 전환 없이 종료한다. -9. 최종적으로 findings, OQ 후보, auto-fix 결과, 다음 추천 행동을 보고한다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 대상 `DISCOVERY_ID`와 문서 경로 -- 전체 판정 - - `양호, 바로 confirm-discovery 검토 가능` - - `경미한 정리 필요` - - `추가 보강 필요` - - `문서 구조 또는 사실 관계 재정비 필요` -- severity별 findings 목록 -- 새 OQ 후보 목록 -- 중복/불필요 내용 정리 제안 -- 자동 정리한 항목 목록(수정 요청이 있었을 때만) -- 다음 추천 행동 - - `confirm-discovery` 진행 - - Discovery 문서 추가 수정 후 재검토 - - 범위가 바뀌어 `new-discovery` 또는 `change-req` 판단 필요 - -# Validation - -완료 전에 아래를 확인한다. - -- 대상 경로가 실제 Discovery 문서를 가리키는지 확인한다. -- `완전 중복` 예외를 제외하고, 누락 판단이 템플릿 구조와 충돌하지 않는지 확인한다. -- 제안한 OQ가 이미 본문이나 다른 섹션에서 답을 가진 항목이 아닌지 확인한다. -- 같은 finding을 누락/중복/불필요 항목에 중복 보고하지 않았는지 확인한다. -- 수정이 있었다면 상태 전환 값과 `docs/discovery/index.md`가 바뀌지 않았는지 확인한다. -- 저장소 사실 관계와의 충돌을 지적했다면 최소 한 개 이상의 직접 근거가 있는지 확인한다. \ No newline at end of file diff --git a/.github/skills/run-batch-delivery/SKILL.md b/.github/skills/run-batch-delivery/SKILL.md deleted file mode 100644 index db06b0e..0000000 --- a/.github/skills/run-batch-delivery/SKILL.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -name: run-batch-delivery -description: "Use when: orchestrating the full delivery chain for an existing batch, running /run-batch-delivery with a BAT ID, resuming an in-progress batch from the first incomplete delivery stage, or chaining planning, design, implementation, verification drafting, verification approval, and REQ implementation sync for one selected batch." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: bat-002 또는 docs/batches/bat-002_20260522_visioncfg-32-contract-migration" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, delivery, orchestration, execution, sdlc] - related_skills: [draft-batch-planning, draft-batch-design, run-batch-implementation, draft-batch-verification, confirm-batch-verification, confirm-req-implemented, draft-release] ---- - -# Purpose - -This skill orchestrates the Delivery phase for one already-created batch by chaining the batch-level skills in order until the batch reaches `release-candidate` and its included REQs are synchronized to `Implemented` where evidence exists. - -이 skill은 새 batch를 만들지 않는다. 이미 존재하는 `docs/batches//`를 기준으로 현재 delivery 진행 상태를 읽고, 아래 6단계를 순서대로 이어 붙이는 orchestration entrypoint다. - -1. `draft-batch-planning` -2. `draft-batch-design` -3. `run-batch-implementation` -4. `draft-batch-verification` -5. `confirm-batch-verification` -6. `confirm-req-implemented` - -# When to use - -- `/run-batch-delivery bat-002`처럼 특정 batch를 delivery phase 끝까지 밀고 싶을 때 -- 이미 `draft-batch`까지 끝난 batch를 planning/design/implementation/verification/REQ 상태 동기화까지 이어서 처리할 때 -- 기존 batch가 어느 delivery 단계에서 멈췄는지 확인하고, 첫 incomplete stage부터 재개하고 싶을 때 -- 사람이 REQ 묶음을 이미 선택했고 batch도 생성했으므로, 이제 batch 내부 delivery chain만 집중 실행해야 할 때 - -## Do not use when - -- 아직 batch가 생성되지 않았고 포함 REQ 집합 선택도 끝나지 않았을 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) -- 단순히 batch의 planning만 채우거나 design만 보강하려는 단일 단계 작업일 때 (해당 개별 skill이 더 적절함) -- verification 승인 이후 release 문서까지 자동으로 이어 가고 싶을 때 (`draft-release`는 별도 단계로 남겨 둔다) -- batch가 아니라 저장소 전체의 다음 SDLC 단계를 판단하려는 상황일 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- `BAT-ID` prefix 또는 batch 경로 -- `docs/batches//index.md` -- `docs/batches//planning.md` -- `docs/batches//design.md` (존재하는 경우) -- `docs/batches//implementation.md` (존재하는 경우) -- `docs/batches//verification.md` (존재하는 경우) -- 포함된 REQ 문서들 -- `docs/batches/index.md` -- `docs/srs/index.md` - -# Core Rules - -## 1. 배치 단위 orchestration - -- 이 skill은 정확히 하나의 batch를 대상으로 한다. -- 입력 batch가 유일하게 확정되지 않으면 진행하지 않는다. -- batch 밖의 다른 REQ나 다른 batch를 임의로 끌어오지 않는다. - -## 2. 6단계 delivery chain 유지 - -- 기본 chain은 아래 순서를 따른다. - 1. `draft-batch-planning` - 2. `draft-batch-design` - 3. `run-batch-implementation` - 4. `draft-batch-verification` - 5. `confirm-batch-verification` - 6. `confirm-req-implemented` -- 앞 단계 결과가 다음 단계 입력 전제를 만족하지 않으면 그 지점에서 멈추고 blocker를 보고한다. -- `confirm-batch-verification`과 `confirm-req-implemented`는 승인 게이트이므로 evidence 부족 상태를 억지로 통과시키지 않는다. - -## 3. design 생략 규칙 - -- batch profile이 `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 실제로 없을 때만 `draft-batch-design`을 생략할 수 있다. -- 그 외에는 design 단계를 delivery chain에 포함한다. -- 생략 여부는 planning 문서와 batch index 근거로 설명되어야 한다. - -## 4. 중복 실행보다 불완전 단계 우선 - -- 이미 충분히 채워진 단계는 다시 덮어쓰는 것이 목적이 아니다. -- 각 단계는 먼저 현재 batch 문서를 읽고, 실제로 비어 있거나 draft 수준인 stage부터 시작한다. -- 다만 앞 단계 산출물이 뒤 단계 전제를 만족하지 못하면 필요한 범위 내에서 보강할 수 있다. - -## 5. 사람 결정과 승인 게이트 존중 - -- verification evidence가 부족하거나 blocker가 있으면 `confirm-batch-verification`에서 멈춘다. -- REQ acceptance criteria와 evidence 연결이 불충분하면 `confirm-req-implemented`에서 멈춘다. -- release 문서 생성과 release 승인까지 자동으로 넘기지 않는다. 이 skill의 종료점은 delivery phase 완료 직전 또는 완료 직후 상태 보고다. - -## 6. child skill 규칙 상속 - -- 이 skill은 하위 skill들을 대체하지 않는다. -- 각 단계에서는 해당 skill의 prerequisite, validation, blocker 규칙을 그대로 따른다. -- 특히 baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 영향 여부는 design/verification 단계 child skill 규칙을 따라 점검한다. - -# Execution Procedure - -1. 입력에서 `BAT-ID` 또는 batch 경로를 유일하게 확정한다. -2. batch `index.md`, `planning.md`, `design.md`, `implementation.md`, `verification.md`, 포함 REQ, `docs/batches/index.md`, `docs/srs/index.md`를 읽어 현재 delivery 상태를 요약한다. -3. 아래 기준으로 첫 incomplete stage를 판정한다. - - `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` - - design이 필요하지만 `design.md`가 비어 있거나 부족하면 `draft-batch-design` - - 구현 로그/변경/검증 기록이 부족하면 `run-batch-implementation` - - verification mapping/evidence 초안이 부족하면 `draft-batch-verification` - - verification evidence는 있으나 batch가 아직 `release-candidate`가 아니면 `confirm-batch-verification` - - batch가 `release-candidate` 또는 `released`인데 포함 REQ 중 `Approved`가 남아 있으면 `confirm-req-implemented` -4. 첫 incomplete stage부터 시작해, blocker가 생길 때까지 다음 stage로 순차 진행한다. -5. `draft-batch-planning`이 실행되면 범위, dependency, milestone, Design Gate가 채워졌는지 확인한다. -6. `draft-batch-design`이 필요하면 architecture summary, changed areas, key decisions, architecture impact가 채워졌는지 확인한다. -7. `run-batch-implementation` 후에는 구현 변경, execution log, validation, remaining risks가 기록됐는지 확인한다. -8. `draft-batch-verification` 후에는 REQ acceptance criteria와 evidence mapping, blocking issues, baseline 동기화 evidence가 필요한 경우 그 항목이 채워졌는지 확인한다. -9. `confirm-batch-verification` 성공 시 batch가 `release-candidate`로 전환됐는지 확인한다. 실패 시 blocker를 보고하고 멈춘다. -10. `confirm-req-implemented`를 실행해 포함 REQ 중 evidence가 충분한 항목만 `Implemented`로 동기화한다. 부족한 항목은 blocker와 함께 남긴다. -11. delivery chain 종료 후 결과를 아래 중 하나로 보고한다. - - `Stopped at ` + blocker - - `Delivery chain complete through REQ sync` -12. delivery chain이 성공적으로 끝났으면 다음 단계로 `draft-release`를 추천하되, 자동 실행하지는 않는다. - -# Output Expectations - -- 대상 batch 경로 -- 시작 시점 batch 상태 요약 -- 실제로 실행한 stage 목록 -- 생략한 stage와 생략 근거 (`batch-lite` design 생략 등) -- 각 stage 결과 요약 -- 중단 지점과 blocker 또는 완료 결과 -- batch 최종 상태 (`draft` | `in-delivery` | `release-candidate` 등) -- 포함 REQ 중 `Implemented` 전환 성공 목록 / 보류 목록 -- 다음 권장 단계 (`draft-release` 또는 blocker 해소) - -# Common Pitfalls - -1. `draft-batch` 전 batch를 바로 delivery chain에 넣는 실수 - - 이 skill은 이미 생성된 batch가 전제다. REQ 묶음 선택이나 batch 생성은 먼저 끝나 있어야 한다. - -2. `draft-batch-verification`을 건너뛰고 바로 `confirm-batch-verification`으로 가는 실수 - - verification 초안과 evidence mapping이 먼저 있어야 approval gate를 제대로 통과시킬 수 있다. - -3. `batch-lite`라는 이유만으로 design을 자동 생략하는 실수 - - planning의 `Design Gate`가 `no`여야 하고 실제 구조 영향도 없어야 한다. - -4. verification blocker가 있는데도 REQ를 `Implemented`로 올리는 실수 - - `confirm-req-implemented`는 batch verification evidence를 근거로 해야 하므로 blocker가 남아 있으면 보류해야 한다. - -5. child skill이 이미 만든 문서를 읽지 않고 매번 처음부터 다시 쓰는 실수 - - 현재 batch 문서를 먼저 읽고, 첫 incomplete stage부터 이어 가야 한다. - -6. release까지 자동으로 계속 진행하는 실수 - - 이 skill의 역할은 delivery chain orchestration이다. release 문서 생성/승인은 별도 stage다. - -# Verification Checklist - -- [ ] 대상 batch가 유일하게 확정되었다. -- [ ] batch 생성 단계가 이미 완료된 상태다. -- [ ] planning -> design -> implementation -> verification drafting -> verification approval -> REQ sync 순서가 유지되었다. -- [ ] `batch-lite` design 생략은 `Design Gate`와 실제 영향 근거로 설명된다. -- [ ] `confirm-batch-verification`은 blocker 없는 evidence 상태에서만 통과했다. -- [ ] `confirm-req-implemented`는 evidence가 연결된 REQ만 전환했다. -- [ ] stage별 실행 결과와 중단/완료 상태가 보고되었다. -- [ ] release 단계는 자동으로 진행하지 않고 별도 추천으로 남겼다. diff --git a/.github/skills/run-batch-implementation/SKILL.md b/.github/skills/run-batch-implementation/SKILL.md deleted file mode 100644 index fd76db0..0000000 --- a/.github/skills/run-batch-implementation/SKILL.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: run-batch-implementation -description: "Use when: implementing a confirmed batch design, running /run-batch-implementation with a BAT ID, applying code changes for docs/batches/, or updating implementation logs and validation evidence for a batch." -version: 0.7.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, implementation, execution, sdlc] - related_skills: [draft-batch-planning, draft-batch-design, draft-batch-verification, confirm-req-implemented] ---- - -# Purpose - -This skill executes the implementation work for a batch, updates code, and records the changed files, execution log, validation, and remaining risks in the batch implementation document. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//planning.md` -- `docs/batches//design.md` -- `docs/batches//implementation.md` -- 관련 REQ 문서와 실제 코드 경로 - -# Core Rules - -- planning과 design이 없는 batch는 구현하지 않는다. -- 코드 변경 전 `implementation.md`의 Plan Summary와 Changed Files 초안을 먼저 맞춘다. -- 구현 직후 가장 좁은 테스트, lint, typecheck, 또는 동작 검증을 수행한다. -- stateful runtime을 host/PYDEBUG binding으로 검증할 때는 동일 입력 반복 호출이 내부 scan/cache/state를 누적할 수 있음을 먼저 점검한다. deterministic evidence가 필요하면 테스트 전에 reset hook을 추가하거나 초기 상태를 명시적으로 복원한 뒤 비교한다. -- blocker가 생기면 문서에 남기고 범위를 임의 확장하지 않는다. -- 구현을 시작한 batch는 `implementation.md`만 갱신하고 끝내지 말고, batch index와 batch index 문서의 Status도 최소 `in-delivery`로 맞춰 stage 상태가 어긋나지 않게 한다. -- 배치 문서 경로가 아직 없거나 추적 문서가 비어 있으면, 구현 단계에서 `index.md`, `implementation.md`, `verification.md` 기본 뼈대를 함께 정리해 이후 stage가 바로 이어질 수 있게 한다. - -# Execution Procedure - -1. batch 경로, plan, design, 관련 REQ를 읽는다. -2. 구현 범위와 변경 파일 후보를 요약한다. -3. 실제 코드 변경을 수행한다. -4. `implementation.md`의 Changed Files, Execution Log, Validation, Remaining Risks를 갱신한다. -5. batch index 상태를 필요하면 `in-delivery`로 유지 또는 보정한다. -6. persistence schema, flash migration, 또는 저장 포맷 호환성 이슈를 디버깅할 때는 호스트/PYDEBUG 빌드의 `sizeof(...)` 추정으로 결론내리지 말고, 타깃 펌웨어 쪽에 구조체 크기(`sizeof(struct prop_header)`, 관련 persisted struct), header probe 결과, 첫/둘째 `property_read(...)` 반환값을 직접 로그로 남겨 원인을 분리한다. - -# Validation - -- `implementation.md`에 실제 변경 파일과 검증 기록이 반영됐는지 확인한다. -- 구현 직후 수행한 가장 좁은 검증 명령 또는 결과가 문서에 남았는지 확인한다. -- persistence/migration 디버깅 변경이라면, 타깃 로그로 ABI 크기와 read rc를 캡처하는 계측 또는 동등한 증거가 남았는지 확인한다. -- batch 문서 `index.md`와 `docs/batches/index.md`의 Status가 구현 진행 상태(`in-delivery` 등)와 일치하는지 확인한다. \ No newline at end of file diff --git a/.github/skills/run-sdlc/SKILL.md b/.github/skills/run-sdlc/SKILL.md deleted file mode 100644 index d5dd2d7..0000000 --- a/.github/skills/run-sdlc/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: run-sdlc -description: "Use when: orchestrating the next SDLC action from the current 3-phase state, running /run-sdlc with a Discovery, REQ, Batch, or Release ID, resuming from the first incomplete unit in docs/discovery, docs/srs, docs/batches, or docs/releases, or routing a no-history repository to bootstrap-baseline before the first real Discovery." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: dcy-001, bat-001, rel-001, req-001 req-002" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, orchestration, workflow, execution, sdlc] - related_skills: [bootstrap-baseline, new-discovery, draft-req, draft-batch, run-batch-delivery, draft-release] ---- - -# Purpose - -This skill inspects the current state of Discovery, REQ, Batch, and Release documents and routes work to the next skill-only step in the 3-phase SDLC model. - -`docs/project-structure.md`와 `docs/runtime-flows.md`는 별도 governance unit는 아니지만, 이 skill은 다음 단계를 추천할 때 해당 baseline 문서의 존재 여부와 갱신 필요 여부를 함께 확인한다. - -# Inputs - -- `DISCOVERY_ID` -- `REQ-ID` 목록 -- `BAT-ID` -- `REL-ID` -- 관련 문서 경로 -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- 이 skill은 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`만 active 경로로 사용한다. -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 active routing 대상은 아니지만, 모든 단계에서 참고할 cross-cutting baseline 문서로 취급한다. -- prompt 기반 stage command는 사용하지 않는다. -- `review-*` 단계는 별도 명령으로 두지 않고 대응 `confirm-*` 절차에 흡수된 상태를 전제로 한다. -- 사람 선택이 필요한 경계에서는 자동으로 넘기지 않고 중단한다. 대표적으로 `suggest-batch-reqs` 결과에서 어떤 후보를 채택할지 결정하는 단계가 그렇다. -- 단, 정확히 1개의 `Approved` REQ가 국소적이고 저위험 변경으로 판단되면 `minor-change` fast path로 `draft-batch`를 바로 제안할 수 있다. 이 경로의 batch profile은 `batch-lite`다. -- active unit가 아직 없고 baseline 문서 또는 active index가 비어 있으면, 첫 real Discovery 전에 `bootstrap-baseline`을 우선 추천한다. - -# Execution Procedure - -## 1. 입력 해석 - -- 입력이 `dcy-`로 시작하면 Discovery를 기준으로 현재 상태를 확인한다. -- 입력이 `req-` 목록이면 REQ 상태를 읽고 batch 생성 또는 추천 단계로 보낸다. -- 입력이 `bat-`로 시작하면 batch delivery 상태를 확인한다. -- 입력이 `rel-`로 시작하면 release 상태를 확인한다. -- 입력이 없으면 현재 저장소에서 첫 incomplete step을 판정한다. -- 어떤 입력이든 가능하면 `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 함께 확인한다. - -## 1.5 bootstrap baseline routing - -- active unit가 없고 `docs/project-structure.md`, `docs/runtime-flows.md`, active index 중 일부라도 비어 있으면 `bootstrap-baseline`을 다음 skill로 제안한다. -- 이 경우 Discovery를 자동 생성하지 않는다. -- baseline 초기화가 끝난 뒤 `new-discovery`를 다음 real Discovery entrypoint로 안내한다. - -## 2. Discovery 기준 routing - -- Discovery가 아직 `confirmed`가 아니면 `confirm-discovery`를 우선 실행한다. -- Discovery는 confirmed지만 연결된 REQ가 없으면 `draft-req`를 다음 단계로 사용한다. -- `Approved` REQ는 있으나 batch가 없으면 `suggest-batch-reqs`를 실행해 후보를 제시하고, 사용자가 후보를 선택한 뒤 `draft-batch`로 이어 가도록 멈춘다. -- batch가 이미 생성되었고 delivery chain을 한 번에 이어서 진행하려면 `run-batch-delivery`를 직접 entrypoint로 사용할 수 있다. -- baseline 문서 갭이 있으면 상태 요약에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 필요 여부를 함께 기록한다. - -## 3. REQ 기준 routing - -- `Proposed` REQ면 `confirm-req`를 다음 단계로 사용한다. -- 기존 REQ의 `Requirement`, `Acceptance Criteria`, 구현 전제가 바뀌어 Change Log 기반 변경 관리가 필요하면 `change-req`를 다음 단계로 사용한다. -- `Approved` REQ가 `release-candidate` 또는 `released` batch의 verification evidence와 연결돼 있으면 `confirm-req-implemented`를 다음 단계로 사용한다. -- 단일 `Approved` REQ이고 구조/인터페이스/런타임 영향이 없는 소규모 변경이면 `draft-batch`를 `minor-change -> batch-lite` 경로로 바로 제안한다. -- Approved REQ 묶음이 2개 이상이거나 사람 선택이 필요한 경우 `suggest-batch-reqs` 또는 `draft-batch`로 이어진다. -- baseline 문서 갱신이 REQ에 포함돼 있으면 상태 요약에 이를 반영한다. - -## 4. Batch 기준 routing - -- `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` -- batch profile이 `standard`이거나 `batch-lite`라도 planning의 `Design Gate`가 `yes`면 `draft-batch-design` -- batch profile이 `batch-lite`이고 `Design Gate`가 `no`면 design 없이 `run-batch-implementation` -- 구현 로그와 코드 변경이 아직 없으면 `run-batch-implementation` -- verification 문서가 없거나 불완전하면 `draft-batch-verification` -- verification evidence가 있지만 release-candidate가 아니면 `confirm-batch-verification` -- batch가 `release-candidate` 또는 `released`이고 포함 REQ 중 `Approved` 상태가 남아 있으면 `confirm-req-implemented` -- batch가 `release-candidate`이고 포함 REQ 구현 상태 동기화까지 끝났으면 `draft-release` -- batch design 또는 verification에 structure/flow 영향이 보이면 baseline 문서 갱신 필요 여부를 상태 요약에 포함한다. - -## 5. Release 기준 routing - -- release 문서가 draft면 profile(`docs-only` | `tooling` | `app-service`)에 맞는 근거가 채워졌는지 확인한 뒤 `confirm-release` -- `feedback-captured` release에 follow-up 입력이 있으면 `suggest-next-discovery` 또는 `change-req`를 다음 단계 후보로 제안한다. -- release가 confirmed 이후 운영 관찰이 남아 있지 않으면 `capture-release-feedback` - -# Output Expectations - -- 입력으로 해석한 단위 종류 -- 현재 상태 요약 -- baseline 문서 상태 (`present` | `missing` | `update-likely-required` | `bootstrap-required`) -- 바로 실행할 다음 skill 1개 또는 추천 후보 목록 -- 자동으로 진행하지 않고 멈춘 이유가 있으면 그 사유 - -# Validation - -- 다음 단계가 현재 문서 상태와 모순되지 않는지 확인한다. -- 제안한 다음 skill이 실제 `.stage-pilot/skills/` 아래 존재하는지 확인한다. -- baseline 문서가 필요한 상황에서 상태 요약 또는 추천 사유에 그 정보가 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/.github/skills/suggest-batch-reqs/SKILL.md b/.github/skills/suggest-batch-reqs/SKILL.md deleted file mode 100644 index d513969..0000000 --- a/.github/skills/suggest-batch-reqs/SKILL.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -name: suggest-batch-reqs -description: "Use when: recommending delivery batch groupings from approved requirements, running /suggest-batch-reqs with a Discovery ID or REQ list, assessing cohesion and delivery risk, or preparing input for /draft-batch without changing repository state." -version: 0.8.2 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 req-001 req-002 req-003" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, requirements, planning, sdlc] - related_skills: [draft-req, draft-batch, run-sdlc] ---- - -# Purpose - -This skill recommends one or more candidate batch groupings from approved requirements and reports rationale, exclusions, risk, and confidence without modifying any files. 입력이 없으면 현재 저장소에서 아직 delivery 입력으로 소모되지 않은 Approved REQ를 찾아 이번 batch 후보를 추천한다. - -# When to use - -- Approved REQ가 여러 개 쌓여 어떤 묶음으로 delivery를 시작할지 추천이 필요할 때 -- `/suggest-batch-reqs dcy-001` 또는 `/suggest-batch-reqs req-001 req-002`처럼 batch 후보안을 받아야 할 때 -- `/suggest-batch-reqs`처럼 인자 없이 현재 저장소의 Approved REQ 중 아직 구현 완료 또는 기존 batch 편성이 끝나지 않은 후보만 추려 이번 batch 권장안을 보고 싶을 때 -- `draft-batch` 실행 전에 포함/제외 REQ 근거를 미리 보고 싶을 때 - -## Do not use when - -- 아직 `Approved`되지 않은 REQ를 먼저 승인해야 할 때 (`confirm-req`가 더 적절함) -- 포함할 REQ 집합이 이미 확정되어 바로 batch 문서를 생성하면 될 때 (`draft-batch`가 더 적절함) -- 단일 저위험 `Approved` REQ라 minor-change 또는 `batch-lite` 경로가 명확할 때 (`draft-batch`를 바로 고려할 수 있음) -- batch 추천이 아니라 현재 저장소의 전체 다음 SDLC 단계를 알고 싶을 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- 선택 입력 - - `DISCOVERY_ID` 또는 Approved `REQ-ID` 목록 - - 입력이 없으면 `docs/srs/**/req-*.md`와 `docs/srs/index.md`를 읽어 Approved REQ를 찾는다. - - 입력이 없으면 `docs/batches/index.md`도 함께 읽어 이미 batch에 편성된 REQ를 제외한다. -- 선택 입력 - - batch 최대 크기 - - Type, Priority, Owner 필터 - - 제외할 REQ 목록 - - 강제로 함께 고려할 REQ 목록 - -# Core Rules - -- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. -- `Approved`가 아닌 REQ는 후보에서 제외한다. -- `Deprecated` REQ는 후보에서 제외한다. -- 입력이 없는 경우 `Implemented` 상태 REQ는 후보에서 제외한다. -- 입력이 없는 경우 `docs/batches/index.md` 기준으로 `draft`, `in-delivery`, `release-candidate`, `released` batch에 이미 포함된 REQ는 중복 추천하지 않는다. -- 상태가 REQ 본문과 index 사이에서 불일치하면, 추천 전에 그 불일치를 보고하고 기본적으로 후보에서 제외하거나 보류한다. -- 아래 조건을 많이 만족할수록 같은 batch 후보로 묶는다. - - 같은 사용자 가치 또는 기능 흐름 - - 같은 모듈, 인터페이스, 런타임 경로 - - 같은 설계 전제와 환경 가정 - - 같은 iteration 안에서 구현과 검증 가능 - - acceptance criteria를 함께 검증하는 것이 효율적임 - - release 시 함께 배포해도 위험이 과도하지 않음 -- 아래 조건이 있으면 같은 batch로 묶지 않는다. - - 핵심 설계 전제 충돌 - - 완전히 다른 verification 흐름 필요 - - 독립 배포가 필요한 고위험 변경 - - 미해결 정책 결정이나 외부 의존성으로 planning이 불안정함 - -## 사람 선택 규칙 - -- 추천 결과 중 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정한다. -- 이 skill은 후보를 자동 채택하거나 `draft-batch`를 자동 실행하지 않는다. -- 후보 간 우열이 애매하면 `기본안` 1개와 `보수적 대안` 1개 이상을 함께 제시하고 trade-off를 설명한다. -- 사람이 선택해야 하는 기준은 보통 다음 순서로 설명한다. - 1. 이번 iteration 안에 끝낼 수 있는가 - 2. 함께 검증할 때 verification cost가 실제로 줄어드는가 - 3. release risk가 한 번에 묶어도 감당 가능한가 - 4. 나중에 분리하면 오히려 재작업이 커지는가 - -## 후보별 batch profile 힌트 - -- `batch-lite` - - 단일 REQ 또는 아주 작은 저위험 묶음 - - 구조/인터페이스/런타임 영향이 좁음 - - planning/design/verification 서술이 짧고 명확함 -- `standard` - - 다수 REQ 또는 상호작용이 있는 묶음 - - 구조/인터페이스/런타임 영향이 중간 이상임 - - planning/design/verification 근거를 분리해서 적는 편이 안전함 -- 각 후보 출력에는 profile 이름만 적지 말고, 왜 그 profile이 맞는지 REQ 수, 변경 폭, verification 복잡도 기준으로 한 줄 이상 근거를 붙인다. - -# Execution Procedure - -1. 입력이 Discovery면 source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 읽고, 입력이 REQ 목록이면 각 REQ 문서를 읽는다. -2. Discovery 입력인데 연결 REQ가 없거나 연결 REQ가 모두 `Approved`가 아니면 추천을 만들지 않고 그 사실을 보고한다. -3. 입력이 없으면 `docs/srs/**/req-*.md`, `docs/srs/index.md`, `docs/batches/index.md`를 읽어 Approved REQ 전체를 찾고, `Implemented` 상태 또는 기존 batch에 이미 포함된 REQ를 제외해 현재 후보군을 만든다. -4. 각 REQ의 상태, Type, Impacted Area, Acceptance Criteria를 요약한다. -5. 후보군에서 Approved REQ만 추린 뒤 응집도와 delivery risk 기준으로 후보안을 1개 이상 만든다. -6. 입력이 없는 스캔 결과 후보군이 비어 있으면 억지로 batch 후보를 만들지 않는다. - - `Approved` REQ가 전혀 없거나 - - Approved REQ가 모두 기존 batch에 이미 포함돼 있거나 - - 남은 REQ가 전부 `Proposed`/`Implemented`/`Deprecated`라면 - `추천 가능한 신규 batch 없음`으로 보고한다. - - 이 경우 스캔한 REQ와 제외 사유를 함께 정리하고, 필요하면 먼저 `confirm-req` 또는 기존 batch 진행 상태 점검을 권장한다. - - 후보가 없는데도 형식상 `BAT-CANDIDATE-1`을 억지로 만들거나, 실제로 구성할 수 없는 `/draft-batch ...` 예시를 만들지 않는다. -7. 가능하면 최소 2개 후보를 제시한다. - - 응집도 중심 기본안 - - 더 작은 묶음 또는 보수적 대안 -8. 적절한 묶음이 없지만 단일 Approved REQ는 존재하면 `단일 REQ batch 권장`을 명시한다. -9. 각 후보에 대해 포함 REQ, 제외 REQ와 제외 이유, 추천 근거, 위험도, 신뢰도, 예상 batch profile(`standard` | `batch-lite`)을 정리한다. -10. 각 후보에 대해 profile 판단 근거(REQ 수, 구조/인터페이스/런타임 영향, verification 흐름 복잡도)를 함께 적는다. -11. 바로 사용할 수 있는 `/draft-batch ...` 입력 예시를 제시하되, 이는 추천 초안이며 최종 채택은 사람 선택임을 명시한다. 단, 추천 가능한 후보가 없으면 `/draft-batch` 예시는 생략하고 그 이유를 쓴다. - -# Output Expectations - -- 후보 `BAT-CANDIDATE-N` 목록 -- 입력이 없는 경우 스캔한 Approved REQ 목록 -- 후보별 포함 REQ -- 후보별 제외 REQ와 제외 이유 -- 후보별 추천 근거 -- 후보별 위험도 (`Low|Medium|High`) -- 후보별 신뢰도 (`High|Medium|Low`) -- 후보별 예상 batch profile (`standard` | `batch-lite`)과 판단 근거 -- `draft-batch` 추천 명령 예시 -- 최종 채택은 사람 선택이라는 안내 -- 단, 추천 가능한 신규 batch가 없으면 아래 형식으로 대체 보고할 수 있다. - - 스캔한 REQ 목록 - - 승인됨/제외됨/기존 batch 포함 상태 요약 - - `추천 가능한 신규 batch 없음` 결론 - - 먼저 필요한 선행 단계(`confirm-req`, 기존 batch 진행, release 정리 등) - -# Common Pitfalls - -1. `Approved`가 아닌 REQ를 후보에 섞는 실수 - - batch 추천은 delivery input이 확정된 REQ만 대상으로 해야 하므로 `Proposed`, `Implemented`, `Deprecated`를 구분해 제외한다. - -2. 이미 다른 batch에 포함된 REQ를 다시 추천하는 실수 - - `docs/batches/index.md`를 읽고 중복 편성 여부를 먼저 확인한다. - -3. 서로 다른 verification 흐름이 필요한 REQ를 단순 개수 맞추기로 한 batch에 묶는 실수 - - 응집도보다 검증 흐름 충돌이 크면 더 작은 보수적 대안을 함께 제시한다. - -4. Discovery 입력을 받았지만 실제 연결 REQ 근거 없이 임의로 묶는 실수 - - source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 근거로 삼는다. - -5. 추천 후보를 정답처럼 확정해 버리는 실수 - - 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정하며, 이 skill은 추천만 수행한다. - -6. 단일 저위험 REQ인데도 불필요하게 큰 batch 후보만 제시하는 실수 - - 필요하면 `단일 REQ batch 권장` 또는 `batch-lite` 대안을 함께 제시한다. - -# Verification Checklist - -- [ ] 후보에 `Approved`가 아닌 REQ가 포함되지 않았다. -- [ ] 입력이 없는 경우 `Implemented` 상태 REQ와 기존 batch 포함 REQ가 제외되었다. -- [ ] Discovery 입력이면 연결 REQ 근거가 명확하다. -- [ ] 상태 불일치 REQ는 보고되었고 무리하게 후보에 포함되지 않았다. -- [ ] 후보별 포함/제외 이유가 설명되어 있다. -- [ ] 가능하면 최소 1개의 보수적 대안 또는 `단일 REQ batch 권장`이 포함되어 있다. -- [ ] 후보별 예상 batch profile(`standard` | `batch-lite`)과 그 근거가 제시되었다. -- [ ] `/draft-batch` 예시 입력이 실제 후보 구성과 일치한다. -- [ ] 저장소 상태를 변경하지 않았다. -- [ ] 최종 채택이 사람 선택이라는 점이 명시되어 있다. \ No newline at end of file diff --git a/.github/skills/suggest-next-discovery/SKILL.md b/.github/skills/suggest-next-discovery/SKILL.md deleted file mode 100644 index 8c5b7a4..0000000 --- a/.github/skills/suggest-next-discovery/SKILL.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -name: suggest-next-discovery -description: "Use when: recommending the next Discovery candidate from release feedback, pending requirements, baseline gaps, or recent doctor reports, running /suggest-next-discovery with or without a REL ID, or deciding whether feedback should become a new Discovery or a change-req instead." -version: 0.6.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 인자 없이 /suggest-next-discovery" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, prioritization, planning, sdlc] - related_skills: [capture-release-feedback, new-discovery, run-sdlc] ---- - -# Purpose - -This skill analyzes release feedback, pending REQs, baseline gaps, and recent delivery signals to recommend the next improvement loop. It reports ranked candidates and tells the user whether to start a new Discovery or route work into `change-req` instead. - -# When to use - -- `/suggest-next-discovery`처럼 다음 반복 주제를 추천받고 싶을 때 -- `feedback-captured` release 이후 어떤 후속 Discovery를 열지 정해야 할 때 -- release feedback에 기존 REQ 수정과 신규 Discovery 후보가 섞여 있어 triage가 필요할 때 -- `stagepilot-doctor --report ...` 결과와 release feedback를 함께 보고 다음 개선 루프를 정하고 싶을 때 - -# Inputs - -- 선택 입력 - - `REL-ID` prefix 또는 release 경로 - - 입력이 없으면 `docs/releases/*.md`, `docs/srs/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`를 읽어 후보를 찾는다. -- 선택 입력 - - `stagepilot-doctor` Markdown report 경로 -- 관련 release 문서의 `Feedback Handoff` -- `docs/srs/index.md`와 미구현/변경 대기 REQ 상태 -- `docs/project-structure.md`, `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. -- release feedback의 후보는 아래 셋 중 하나로 분류한다. - - `new-discovery` - - `change-req` - - `no-action` -- 기존 REQ의 수정으로 닫히는 항목은 새 Discovery보다 `change-req`를 우선 추천한다. -- 서로 다른 가치 흐름이나 이해관계자를 가지는 후보는 별도 Discovery로 분리한다. -- 추천에는 가치, 위험, 노력의 상대 평가를 포함한다. - -# Execution Procedure - -1. 입력 release가 있으면 해당 문서를, 없으면 최근 release 문서들을 읽는다. -2. `Feedback Handoff`에서 Discovery Input, REQ Input, Change Request Input을 추린다. -3. `docs/srs/index.md`와 관련 REQ 상태를 읽어 기존 REQ 수정으로 해결 가능한지 판정한다. -4. baseline 문서 gap, 미구현 REQ, doctor report의 경고/오류가 후속 반복 후보인지 함께 판단한다. -5. 후보를 `new-discovery` 또는 `change-req`로 분류하고 가치/위험/노력 점수를 매긴다. -6. 우선순위 순으로 추천 목록과 바로 실행할 명령 예시를 제시한다. - -# Output Expectations - -- 후보 목록 (`CANDIDATE-1`, `CANDIDATE-2`, ...) -- 각 후보의 분류 (`new-discovery` | `change-req` | `no-action`) -- 근거 release 또는 REQ -- 가치/위험/노력 (`High|Medium|Low`) -- 추천 이유 -- 다음 명령 예시 (`/new-discovery ...` 또는 `/change-req ...`) - -# Validation - -- 동일 후보를 `new-discovery`와 `change-req`에 중복 배치하지 않았는지 확인한다. -- release feedback 근거가 없는 임의 후보를 만들지 않았는지 확인한다. -- 기존 REQ 수정으로 닫히는 항목을 새 Discovery로 과도하게 승격하지 않았는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/.gitignore b/.stage-pilot/.gitignore deleted file mode 100644 index 08958da..0000000 --- a/.stage-pilot/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 루트 전용 무시 (source/ 아래의 .claude·.github 템플릿은 추적해야 하므로 / 앵커 필수) -/.vscode/ -/.claude/ -/.github/ -/.stage-pilot/ -/.docs/ -/temp/ -/CLAUDE.md diff --git a/.stage-pilot/README.md b/.stage-pilot/README.md deleted file mode 100644 index dba94b3..0000000 --- a/.stage-pilot/README.md +++ /dev/null @@ -1,424 +0,0 @@ - -# 사용방법 - -## 최초 등록 - -```bash -# 리모트 등록 -git remote add stage-pilot https://github.com/genichin/stage-pilot.git -# subtree 추가 -git fetch stage-pilot main -git subtree add --prefix=.stage-pilot stage-pilot main --squash -# 설치 -bash .stage-pilot/tools/install.sh . -``` - -설치 직후에는 Copilot Chat에서 `/bootstrap-baseline`을 먼저 실행해 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, 각 active index를 초기화한다. 이 단계는 Discovery/REQ/Batch/Release 중 어느 것도 아니며, 첫 real Discovery 전에 수행하는 bootstrap 단계다. - -greenfield 저장소처럼 읽을 코드나 설정이 없으면 `bootstrap-baseline`은 사용자에게 최소 질문 세트와 필요한 선택 follow-up(`interface-contracts`, `interface-inputs`, `interface-outputs`, `interface-errors`, `core-entities`, `persistence-backend`, `compatibility-rules`)를 묻고, 답변을 `.stage-pilot/bootstrap/baseline.yaml`에 저장한 뒤 baseline 문서를 렌더링한다. - -seed 파일만 먼저 만들고 싶으면 helper를 사용할 수 있다. - -```bash -# vendor wrapper 사용 -bash .stage-pilot/tools/stagepilot.sh bootstrap-seed . - -# 설치된 script 직접 사용 -python3 .stage-pilot/tools/stagepilot-bootstrap-seed.py . -``` - -bootstrap-baseline 실행 결과 예시는 `examples/bootstrap-baseline/`에 포함돼 있다. - -## 업데이트 - -```bash -# 원격이 등록된 경우(stage-pilot remote 우선 사용) -bash .stage-pilot/tools/update.sh . - -# 충돌 파일을 보존하고 싶을 때(덮어쓰기 비활성화) -bash .stage-pilot/tools/update.sh --no-overwrite . - -# 원격 없이 URL로 직접 업데이트 -bash .stage-pilot/tools/update.sh --repo-url https://github.com/genichin/stage-pilot.git . - -# 설치 재적용 없이 subtree만 갱신 -bash .stage-pilot/tools/update.sh --skip-install . -``` - -## 검증 - -```bash -# .stage-pilot subtree에서 doctor 실행 -bash .stage-pilot/tools/stagepilot.sh doctor . - -# subtree의 scripts를 직접 실행 -python3 .stage-pilot/tools/stagepilot-doctor.py . - -# active docs가 반드시 있어야 하는 workspace라면 엄격 모드 사용 -python3 .stage-pilot/tools/stagepilot-doctor.py --strict-missing-docs . - -# Markdown 리포트를 파일로 저장 -python3 .stage-pilot/tools/stagepilot-doctor.py --report artifacts/stagepilot-doctor.md . -``` - -`stagepilot-doctor`는 아래 항목을 검사한다. - -- active 문서 루트 존재 여부 -- index와 본문 문서의 상태 동기화 -- REQ traceability matrix (`Discovery -> REQ -> Batch -> Release`) -- feedback loop summary (`Release -> Discovery/REQ/change-req input`) -- change-req 이후 상태 되돌림 누락 (`Status Recommendation`, 재검증 필요, 구현 무효화와 현재 REQ 상태의 불일치) -- orphan REQ (`Approved`인데 batch 미포함, release-candidate batch 미포함 등) -- feedback 미연결 release (`released`인데 `feedback-captured` 미전환 등) -- 남아 있는 placeholder -- 상대 markdown 링크 경로 -- REQ 타입 분류 일관성 -- `Next Requirement ID` 규칙 -- template-skill contract 불일치 -- bootstrap 완료 저장소에서 선택 cross-cutting baseline 문서(`docs/interface-contract.md`, `docs/data-model.md`) 누락 - -active 문서가 아직 없는 패키지 저장소나 초기 host 저장소에서는 package contract 검사만 수행하고, active docs 부재는 기본적으로 warning으로 보고한다. - -fresh host 저장소에서 baseline 초기화가 아직 끝나지 않았으면 doctor는 `INFO [bootstrap-required]`로 `/bootstrap-baseline` 실행과 누락된 bootstrap 파일 목록을 함께 안내한다. - -`--report`를 주면 findings와 traceability matrix를 별도 Markdown 리포트로 저장한다. 기본 동작은 stdout 출력만 유지한다. - -## 예제 - -- `examples/bootstrap-baseline/`: fresh host 저장소에서 `/bootstrap-baseline`을 실행한 직후의 baseline 문서와 sample output -- `examples/p3-change-management/`: change-req와 suggest-next-discovery가 연결되는 P3 sample workspace - -## Active 문서 루트와 Source of Truth - -### 필수 루트 - -| 경로 | 역할 | -| --- | --- | -| `docs/discovery/index.md` | Discovery 전역 인덱스 | -| `docs/discovery/*.md` | Discovery 본문 | -| `docs/srs/index.md` | REQ 전역 인덱스와 상태 정의 | -| `docs/srs//req-XXX_.md` | 개별 REQ 문서 | -| `docs/batches/index.md` | batch 전역 인덱스 | -| `docs/batches//planning.md` | batch 계획 | -| `docs/batches//design.md` | batch 설계 | -| `docs/batches//implementation.md` | batch 구현 로그 | -| `docs/batches//verification.md` | batch 검증 근거 | -| `docs/releases/index.md` | release 전역 인덱스 | -| `docs/releases/rel-XXX_*.md` | 개별 release 문서 | - -### 선택 cross-cutting 문서 - -| 경로 | 역할 | -| --- | --- | -| `docs/project-structure.md` | 저장소 구조, 모듈 경계, 의존 규칙 baseline | -| `docs/runtime-flows.md` | 대표 entry point와 실행 흐름 baseline | -| `docs/interface-contract.md` | 외부 API, CLI, 이벤트, 파일 I/O 계약 baseline | -| `docs/data-model.md` | 핵심 엔티티, 관계, 상태, persistence 규칙 baseline | - -이 문서들은 active unit 자체는 아니지만 여러 REQ와 batch에서 반복 참조하는 공통 기술 기준선이다. - -### 4.2 명령, 템플릿, 가이드 경로 - -| 경로 | 역할 | -| --- | --- | -| `.stage-pilot/skills/bootstrap-baseline/SKILL.md` | 프로젝트 시작 시 baseline 문서와 active index를 초기화하는 bootstrap entrypoint | -| `.stage-pilot/skills/` | active skill entrypoint 집합 | -| `.stage-pilot/skills/run-sdlc/SKILL.md` | 현재 상태를 읽고 다음 skill을 안내하는 orchestrator | -| `.stage-pilot/templates/bootstrap/baseline-seed.yaml` | bootstrap 질문 결과를 저장하는 seed 템플릿 | -| `.stage-pilot/templates/discovery/discovery.md` | Discovery 생성 템플릿 | -| `.stage-pilot/templates/project-structure.md` | baseline 구조 문서 템플릿 | -| `.stage-pilot/templates/runtime-flows.md` | baseline 실행 흐름 문서 템플릿 | -| `.stage-pilot/templates/interface-contract.md` | 선택형 인터페이스 계약 문서 템플릿 | -| `.stage-pilot/templates/data-model.md` | 선택형 데이터 모델 문서 템플릿 | -| `.stage-pilot/instructions/placeholder-guide.md` | placeholder 치환 규칙과 active 경로 가이드 | - -### 4.3 비활성 경로 정책 - -- prompt 기반 stage command는 active flow에 포함하지 않는다. -- legacy stage template는 active source of truth가 아니다. -- active 작업은 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 기준으로만 수행한다. - -## 5. 단위별 상태 모델 - -### 5.1 Discovery 상태 - -| 상태 | 의미 | -| --- | --- | -| `draft` | 초안 상태이며 REQ drafting 전 | -| `confirmed` | REQ drafting으로 넘길 수 있도록 확인 완료 | - -Discovery는 구현 완료 상태를 표현하는 단위가 아니다. Discovery의 역할은 문제 정의와 handoff 준비다. - -### 5.2 REQ 상태 - -| 상태 | 의미 | -| --- | --- | -| `Proposed` | 초안 상태이며 구현 기준으로 확정되지 않음 | -| `Approved` | 구현 기준으로 승인됨 | -| `Implemented` | 구현과 검증이 완료됨 | -| `Deprecated` | 더 이상 유지하지 않음 | - -### 5.3 Batch 상태 - -| 상태 | 의미 | -| --- | --- | -| `draft` | batch 초안만 생성된 상태 | -| `in-delivery` | planning, design, implementation, verification 중 일부가 진행 중 | -| `release-candidate` | verification 확인이 끝나 release 입력으로 사용 가능 | -| `released` | 하나 이상의 release에 포함됨 | -| `archived` | 더 이상 active delivery 단위로 사용하지 않음 | - -### 5.4 Release 상태 - -| 상태 | 의미 | -| --- | --- | -| `draft` | release 초안 상태 | -| `confirmed` | rollout, rollback, verification 준비가 승인됨 | -| `released` | 배포 실행이 완료됨 | -| `feedback-captured` | 운영 환류까지 기록 완료 | - -## 6. 기본 운영 흐름 - -### 6.0 프로젝트 시작 시 baseline 초기화 - -새 저장소에 StagePilot을 처음 적용할 때는 Discovery를 만들기 전에 `bootstrap-baseline`을 먼저 실행한다. - -1. `bootstrap-baseline` - 필요하면 최소 질문 세트(`project-summary`, `primary-domain`, `tech-stack`, `primary-runtime`, `primary-entrypoints`)와 선택 follow-up(`interface-contracts`, `core-entities`)를 묻고, 답을 `.stage-pilot/bootstrap/baseline.yaml`에 저장한 뒤 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`를 렌더링한다. - 이 단계는 bootstrap 단계이며, Discovery/REQ/Batch/Release 단위로 계산하지 않는다. - 샘플 결과는 `examples/bootstrap-baseline/bootstrap-baseline-output.md`를 참고한다. -2. `new-discovery` - baseline 초기화가 끝난 뒤 첫 real Discovery를 실제 제품/서비스 변경 주제로 생성한다. - -이미 코드와 실행 경로가 있는 기존 저장소에 StagePilot을 처음 적용할 때도 시작점은 동일하게 `bootstrap-baseline`이다. 차이는 bootstrap이 질문만으로 baseline을 만들지 않고, 현재 저장소 구조와 실행 흔적을 먼저 읽어 `observed` 또는 `mixed` baseline을 만든다는 점이다. - -1. 설치 직후 `python3 .stage-pilot/tools/stagepilot-doctor.py .` 또는 `bash .stage-pilot/tools/stagepilot.sh doctor .`로 누락된 bootstrap 파일과 active docs 상태를 확인한다. -2. active SDLC 문서가 아직 없다면 `/bootstrap-baseline`을 실행해 기존 저장소 기준의 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, active index를 만든다. -3. 저장소만으로 프로젝트 정체성이나 계획 runtime을 충분히 설명할 수 없으면 `python3 .stage-pilot/tools/stagepilot-bootstrap-seed.py .` 또는 `bash .stage-pilot/tools/stagepilot.sh bootstrap-seed .`로 `.stage-pilot/bootstrap/baseline.yaml`을 먼저 만들고, 그 다음 `/bootstrap-baseline`을 실행해 baseline을 `mixed`로 보강한다. -4. bootstrap이 끝난 뒤 첫 real Discovery는 baseline 생성 자체가 아니라 현재 진행하려는 기능 변경, 운영 이슈, 기술 결정 중 하나를 주제로 `new-discovery`에서 시작한다. -5. 이미 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 아래 active unit가 운영 중인 저장소라면 bootstrap을 광범위하게 다시 수행하지 말고 `run-sdlc ` 또는 해당 active unit의 skill로 이어서 처리한다. 이 경우 `bootstrap-baseline`은 단순 누락 복구에만 사용한다. - -### 6.1 Requirements Phase - -1. `new-discovery` - baseline 초기화 이후 사용자 요청, 운영 피드백, issue 입력을 실제 변경 주제의 Discovery 초안으로 만든다. -2. `confirm-discovery` - Discovery를 저장소 현실과 대조하고 REQ drafting 가능 상태로 `confirmed` 전환한다. - 선행 품질 점검이 필요하면 보조 helper인 `review-discovery`로 누락, OQ 후보, 중복, 불필요 서술을 먼저 정리할 수 있다. 이 helper는 상태 전환 없이 review만 수행한다. -3. `draft-req` - confirmed Discovery에서 승인 가능한 REQ 문서를 만든다. -4. `confirm-req` - REQ를 `Approved`로 전환한다. -5. `change-req` - 기존 REQ 수정이 필요할 때 Change Log 기반 변경 요청을 먼저 기록하고 상태 되돌림/재검증 범위를 판정한다. -6. `suggest-batch-reqs` - Approved REQ를 어떤 batch로 묶을지 후보를 제시한다. - `change-req` 이후에도 delivery로 이어질 REQ만 batch 후보로 본다. -7. `draft-batch` - 선택된 REQ 묶음으로 `standard` 또는 `batch-lite` profile의 batch 폴더와 기본 문서를 생성한다. - -### 6.2 Delivery Phase - -1. `draft-batch-planning` - batch 범위, 제외 범위, 의존성, 마일스톤, 리스크를 정리한다. -2. `draft-batch-design` - batch 수준의 architecture summary, changed areas, key decisions를 정리한다. `batch-lite`에서 구조 영향이 없으면 생략할 수 있다. -3. `run-batch-implementation` - 실제 코드 변경을 수행하고 implementation 문서에 changed files, execution log, validation을 기록한다. -4. `draft-batch-verification` - REQ acceptance criteria를 evidence에 연결한다. `batch-lite`는 planning과 implementation만으로도 검증할 수 있지만, 구조 영향이 생기면 design을 추가해야 한다. -5. `confirm-batch-verification` - evidence가 충분하면 batch를 `release-candidate`로 전환한다. -6. `confirm-req-implemented` - `release-candidate` 또는 `released` batch의 verification evidence를 기준으로 포함 REQ를 `Implemented`로 동기화한다. - -### 6.3 Release & Feedback Phase - -1. `draft-release` - 하나 이상의 `release-candidate` batch로 release 문서를 생성하고 `docs-only`, `tooling`, `app-service` 중 하나의 release profile을 선택한다. -2. `confirm-release` - rollout, rollback, verification checklist와 profile별 검증 항목을 점검하고 release를 `confirmed`로 전환한다. -3. `capture-release-feedback` - 배포 후 운영 관찰 결과와 후속 Discovery/REQ/change-req 입력을 release 문서에 남긴다. -4. `suggest-next-discovery` - release feedback, baseline gap, 미구현/변경 대기 REQ를 읽고 다음 반복 후보를 추천한다. - -### 6.4 경량 변경 경로 - -아래 조건을 모두 만족하면 `minor-change` fast path를 사용할 수 있다. - -- `Approved` REQ가 정확히 1개다. -- 범위가 문서, 템플릿, 스크립트, bootstrap, 설정처럼 국소적이다. -- 구조, 인터페이스, runtime flow 변경이 없다. -- 독립 release가 가능하고 사람 선택이 필요한 묶음 판단이 남아 있지 않다. - -이 경로에서는 `suggest-batch-reqs`를 생략하고 바로 `draft-batch`를 실행해 `Profile: batch-lite` batch를 만든다. `batch-lite`는 최초에 `index.md`와 `planning.md`만 만들고, implementation/verification 문서는 해당 단계 진입 시 추가한다. design은 planning의 `Design Gate`가 `yes`가 될 때만 생성한다. - -### 6.5 Orchestrator 사용 기준 - -`run-sdlc`는 현재 문서 상태를 읽고 다음에 실행할 skill을 1단계씩 안내하는 entrypoint다. - -- active unit가 아직 없고 baseline 문서나 active index가 비어 있으면 `bootstrap-baseline`을 먼저 제안한다. -- Discovery 입력이면 `confirm-discovery`, `draft-req`, `suggest-batch-reqs` 중 다음 단계를 제안한다. -- REQ 입력이면 `confirm-req`, `change-req`, `suggest-batch-reqs`, `draft-batch`, `confirm-req-implemented` 중 다음 단계를 제안한다. 단일 저위험 REQ면 `minor-change -> batch-lite` 경로를 우선 제안할 수 있다. -- Batch 입력이면 `draft-batch-planning`, `draft-batch-design`, `run-batch-implementation`, `draft-batch-verification`, `confirm-batch-verification`, `confirm-req-implemented`, `draft-release` 중 다음 단계를 제안한다. -- Release 입력이면 선택된 release profile과 feedback handoff 상태를 기준으로 `confirm-release`, `capture-release-feedback`, `suggest-next-discovery`, `change-req`를 제안한다. - -### 6.6 변경 관리와 개선 루프 - -- 기존 REQ의 `Requirement`, `Acceptance Criteria`, `Impacted Area`를 바꿀 때는 먼저 `change-req`로 새 Change Log 항목을 만든다. -- 변경이 기존 구현/검증을 무효화하면 REQ 상태를 `Implemented -> Approved` 또는 필요 시 `Proposed`로 되돌린다. -- release feedback는 `Discovery Input`, `REQ Input`, `Change Request Input`으로 나눠 기록한다. -- 다음 개선 루프의 우선순위만 먼저 정하고 싶으면 `suggest-next-discovery`를 사용한다. - -## 7. Phase Gate 기준 - -### 7.1 Requirements 완료 기준 - -아래 조건을 만족해야 Delivery로 넘어간다. - -- Discovery가 `confirmed` 상태다. -- 구현 대상으로 채택할 REQ가 `Approved` 상태다. -- REQ의 Acceptance Criteria가 구현 판단 가능한 수준이다. -- 사람 결정 필요 항목과 후속 backlog 항목이 분리되어 있다. -- 기존 REQ 변경이 있었다면 `change-req` 기록과 상태 동기화가 끝났다. - -### 7.2 Delivery 완료 기준 - -아래 조건을 만족해야 Release & Feedback으로 넘어간다. - -- batch planning, implementation, verification 문서가 존재한다. -- `standard` batch이거나 `batch-lite`에서 구조 영향이 생긴 경우 design 문서가 존재한다. -- batch에 포함된 REQ acceptance criteria가 evidence와 연결된다. -- verification blocker가 해소되었다. -- batch 상태가 `release-candidate`다. -- 구현과 검증이 완료된 포함 REQ는 필요 시 `Implemented` 상태로 동기화되었다. - -### 7.3 Release & Feedback 완료 기준 - -아래 조건을 만족해야 release 단위를 닫는다. - -- release 계획이 `confirmed` 상태를 통과했다. -- 배포 결과가 기록되었다. -- 운영 관찰 결과가 release 문서에 남았다. -- 후속 Discovery, REQ, change-req 입력이 정리되었다. - -## 8. 사람 판단이 필요한 경계 - -다음 항목은 AI가 임의로 확정하지 않는다. - -- 승인자 지정 -- Owner 지정 -- 정책 선택 -- 우선순위 승인 -- 외부 시스템 또는 외부 데이터 확인 결과 -- `suggest-batch-reqs` 결과 중 어떤 후보를 실제 batch로 채택할지에 대한 선택 - -특히 `suggest-batch-reqs`는 추천안을 만드는 단계이며, 최종 batch 묶음은 사람 선택이 필요하다. 따라서 orchestrator는 이 경계에서 자동 진행하지 않고 멈춘다. 단, 단일 저위험 REQ는 `minor-change` fast path로 바로 `batch-lite`를 만들 수 있다. - -## 9. 자동화 규칙 - -### 9.1 Discovery 자동 생성 확장 규칙 - -현재 StagePilot 기본 패키지에는 GitHub issue label 기반 Discovery 자동 생성 workflow/script가 포함되어 있지 않다. - -호스트 저장소가 별도 workflow 또는 script를 추가해 Discovery 자동 생성을 확장할 수 있으며, 그런 자동화는 아래 규칙을 따라야 한다. - -- Discovery ID는 `dcy-<3자리>__` 형식을 사용한다. -- 동일 issue에 대해 반복 실행해도 중복 Discovery를 만들지 않는다. -- discovery index에 문서 링크와 상태를 등록한다. -- 결과는 issue comment로 다시 보고한다. - -자동화를 추가하지 않는 경우에도 Discovery 생성과 index 갱신의 source of truth는 여전히 `new-discovery` skill과 `docs/discovery/` 문서다. - -### 9.2 Index 관리 규칙 - -- `docs/discovery/index.md`는 자동 생성 및 자동 갱신 결과를 포함할 수 있다. -- `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`는 각 confirm 또는 draft 단계에서 상태와 register를 함께 맞춘다. -- 상태 변경은 본문 문서와 index가 항상 함께 갱신되어야 한다. - -## 10. 표준 작업 절차 - -### 10.1 신규 기능 또는 운영 이슈가 들어온 경우 - -1. issue 또는 요청 내용을 기준으로 `new-discovery`를 실행한다. -2. Discovery를 저장소 현실과 맞춰 보강한 뒤 `confirm-discovery`를 실행한다. -3. Discovery를 REQ로 쪼개고 `draft-req`를 수행한다. -4. 각 REQ를 `confirm-req`로 승인한다. -5. REQ가 여러 개이거나 묶음 판단이 필요하면 `suggest-batch-reqs`로 batch 후보를 받고, 사람이 묶음을 선택한다. -6. 단일 저위험 REQ면 `draft-batch`를 `minor-change -> batch-lite` 경로로 바로 실행한다. -7. 그 외 경우 선택된 묶음으로 `draft-batch`를 실행한다. -8. planning, design(필요한 경우), implementation, verification을 순서대로 수행한다. -9. `confirm-batch-verification`으로 release-candidate를 만든다. -10. `confirm-req-implemented`로 구현/검증 완료 REQ를 `Implemented`로 동기화한다. -11. `draft-release`, `confirm-release`로 배포를 준비하고 실행한다. 이때 release profile은 `docs-only`, `tooling`, `app-service` 중 하나를 선택한다. -12. `capture-release-feedback`으로 운영 환류를 남긴다. -13. 필요하면 `suggest-next-discovery`로 다음 반복 후보를 정리하거나, 기존 REQ 수정이면 `change-req`로 바로 연결한다. - -### 10.2 이미 존재하는 문서를 이어서 처리하는 경우 - -1. 가장 가까운 식별자 하나를 선택한다. -2. `run-sdlc `로 현재 단위의 다음 skill을 확인한다. -3. 제안된 다음 skill을 실행한다. -4. 사람이 결정해야 하는 경계에 도달하면 그 결정만 먼저 확정한다. - -## 11. Hotfix 예외 경로 - -긴급 핫픽스는 Requirements와 Delivery 설계를 경량화할 수 있다. 단, 아래 4가지는 생략하지 않는다. - -1. 최소 요구사항 정의 - 증상, 영향, 성공 조건을 기록한다. -2. 최소 검증 - 재현 테스트 또는 핵심 시나리오 확인을 남긴다. -3. 배포 전 롤백 계획 - 실패 시 복구 방법을 명시한다. -4. 배포 후 Postmortem - 운영 결과를 기록하고 다음 sprint 입력으로 환류한다. - -## 12. 유지보수 규칙 - -이 메뉴얼을 수정해야 하는 경우는 아래와 같다. - -- skill 이름이 바뀌는 경우 -- active 문서 루트가 바뀌는 경우 -- 상태 모델이 바뀌는 경우 -- workflow 또는 자동 생성 경로가 바뀌는 경우 -- phase gate 기준이 바뀌는 경우 - -위 항목이 바뀌면 아래 파일들도 함께 점검한다. - -- `source/instruction.md` (설치 시 `.github/copilot-instructions.md`와 `CLAUDE.md`로 fan-out) -- `skills/run-sdlc/SKILL.md` -- 관련 `confirm-*`, `draft-*`, `run-*`, `capture-*` skill -- `docs/discovery/index.md` -- `docs/srs/index.md` -- `docs/batches/index.md` -- `docs/releases/index.md` - -### 12.1 새 skill 추가 시 체크리스트 - -skill을 새로 추가할 때는 아래 두 파일을 반드시 함께 추가한다. - -| 파일 | 역할 | -| --- | --- | -| `skills//SKILL.md` | skill 본문 (subtree로 `.stage-pilot/skills/`에 노출된다) | -| `source/.claude/commands/.md` | Claude Code용 wrapper (내용: `Follow the instructions in \`.stage-pilot/skills//SKILL.md\` exactly.`) | - -wrapper는 설치 시 `source/.claude/commands/`에서 호스트 `.claude/commands/`로 복사된다. skill을 삭제하거나 이름을 변경할 때도 두 경로를 함께 처리한다. - -## 13. 빠른 점검표 - -### 13.1 Requirements 진입 전 - -- 요청 또는 운영 이슈가 명확한가 -- 기존 Discovery를 갱신할지 새로 만들지 판단했는가 - -### 13.2 Delivery 진입 전 - -- 관련 REQ가 모두 `Approved`인가 -- batch 범위와 제외 범위가 정리되었는가 - -### 13.3 Release 진입 전 - -- batch가 `release-candidate`인가 -- rollout plan, rollback plan, verification checklist가 준비되었는가 \ No newline at end of file diff --git a/.stage-pilot/bootstrap/baseline.yaml b/.stage-pilot/bootstrap/baseline.yaml deleted file mode 100644 index 8459096..0000000 --- a/.stage-pilot/bootstrap/baseline.yaml +++ /dev/null @@ -1,141 +0,0 @@ -schema_version: 1 -capture_mode: mixed # declared | observed | mixed - -project: - repository_name: trex-core - display_name: Trex Core - summary: trex 제품군의 공용 베이스로, 첫 도메인으로 trex-orbit 의 일일 거래 슬롯 생명주기(slot) 순수 도메인을 storage-agnostic 하게 이식한 코어다. - primary_domain: 자동매매 일일 거래 슬롯 생명주기 도메인 (equity trading slot lifecycle) - -stack: - tech_stack: - - Python >=3.12 - - pyproject.toml 기반 패키지 (setuptools) - - pytest (단위 테스트) - - ruff (린트) - - storage-agnostic 순수 도메인 (현재 in-memory repository, PostgreSQL/psycopg 는 향후 영속화 단계에서 도입 예정) - -runtime: - primary_runtime: library - primary_entrypoints: - - name: SlotService - purpose: 슬롯 생명주기 도메인 서비스 — preallocate / transit / capacity / entry-count 의 in-process API - - name: SlotRepository (Protocol) - purpose: 저장소 추상화 경계 — 구현체(in-memory, 향후 PostgreSQL)를 주입받는 영속화 계약 - -interfaces: - contracts: - - name: SlotService - type: internal-service - actors: 상위 애플리케이션(예: trex-orbit 류 매매 데몬)의 매수/매도/주문 결정 경로 및 운영자 도구 - purpose: 슬롯 상태 전이를 레이스-프리하게 수행하고 일일 슬롯 가용성/재진입 카운트를 계산한다. - stability: internal - inputs: - - preallocate_daily_slots(trading_date, count) :: 거래일 idle 슬롯 멱등 사전 할당 - - transit_slot(slot_id, from_state, to_state, reason, actor, **fields) :: advisory lock 하 원자 상태 전이 - - get_today_entry_count(trading_date, symbol) :: 재진입 정책 입력 조회 - - compute_slot_capacity(trading_date, total_slots) :: idle/holding/pending + scarcity_ratio 스냅샷 - outputs: - - Slot :: 전이 후 슬롯 행 스냅샷 - - SlotCapacity :: 가용성 스냅샷 - - slot_transition_log append :: 모든 전이의 append-only 감사 기록 - errors: - - SlotTransitionError :: 허용되지 않은 (from_state, to_state) 전이 - - SlotStateMismatchError :: read-write 사이 상태 변동(레이스 감지) - - SlotNotFoundError :: 존재하지 않는 slot_id - compatibility_rules: - - 6 상태 / 8 전이 패밀리 계약을 깨는 변경은 breaking 으로 취급한다. - - 상태 변경 API 는 transactional_advisory_lock 안에서만 수행하고 lock 보유 중 외부 I/O 를 금지한다. - - name: SlotRepository (Protocol) - type: internal-service - actors: SlotService(소비) ↔ 저장소 구현체(InMemorySlotRepository / 향후 PostgreSQLSlotRepository) - purpose: 도메인을 특정 저장소 기술에서 분리하는 영속화 경계 계약. - stability: internal - inputs: - - transactional_advisory_lock(lock_id) :: 단일 트랜잭션 + advisory lock 컨텍스트 - - fetch_slot / update_slot / pre_allocate_slots / find_idle_slot :: daily_slot CRUD - - append_transition_log :: append-only 전이 로그 - - increment_symbol_entry / get_symbol_entry_count :: 재진입 카운터 - - count_slots_by_status :: 상태별 집계 - outputs: - - Slot / SlotTransitionRecord / 카운트 dict :: 도메인 모델 형태로 반환 - errors: - - SlotNotFoundError :: update 대상 row 부재 - compatibility_rules: - - 구현체는 advisory lock 직렬화와 append-only 전이 로그 의미를 반드시 보장한다. - shared_constraints: - - 도메인은 외부 의존(브로커, DB 드라이버 등)을 직접 호출하지 않는다(순수 도메인 경계). - - 모든 상태 변경은 단일 advisory lock(SLOT_ADVISORY_LOCK_ID)으로 직렬화한다. - current_gaps: - - 영속화 구현체(PostgreSQL)와 그에 딸린 SQL 스키마/마이그레이션은 이번 이관 범위 밖이며 향후 도입 예정. - - CLI / 설정(config) 표면은 아직 없음(trex-orbit 의 trex slot CLI 는 이식하지 않음). - - 패키지 네임스페이스는 trex_core.domain.slot 으로 확정(dcy-001 OQ-1, 배포명 trex-core). - -structure: - planned_top_level_areas: - - path: trex_core/domain/slot/ - responsibility: 슬롯 생명주기 순수 도메인(state/models/errors/repository protocol/service/in-memory repo) - - path: tests/domain/slot/ - responsibility: 슬롯 도메인 단위 테스트(상태머신/서비스) - - path: docs/ - responsibility: SDLC baseline 문서 및 active index(discovery/srs/batches/releases) - - path: pyproject.toml - responsibility: 패키지 메타 + pytest/ruff 설정 - -data_model: - persistence_backend: storage-agnostic (SlotRepository Protocol 경유) — 현재 in-memory, 향후 PostgreSQL 예정 - entities: - - name: Slot - purpose: 하나의 일일 거래 슬롯의 현재 생명주기 상태와 점유 정보를 담는 source of truth. - source_of_truth: daily_slot (향후 테이블) / 현재는 in-memory dict - lifecycle: idle -> pending_buy -> holding -> pending_sell -> closed -> (recovery) idle ; 모든 상태 -> error - key_fields: - - slot_id :: UUID :: 슬롯 식별자(PK) - - trading_date :: date :: 거래일 - - status :: SlotState :: 6 상태 중 현재 상태 - - symbol :: str? :: 점유 종목 - - qty / avg_price / entry_price / allocated_capital :: numeric? :: 포지션/배정 정보 - - strategy_id / sell_strategy_id :: str? :: 진입/청산 전략 식별 - - broker_order_id / portfolio_id / order_id :: str? :: 주문/포트폴리오 핸들 - - forced_close_at :: datetime? :: 강제 청산 마감선 - - created_at / updated_at :: datetime? :: 타임스탬프 - relationships: - - daily_symbol_entries :: closed->idle 복구 시 (trading_date, symbol) 카운트 +1 - - slot_transition_log :: 매 전이마다 append-only 기록 1행 - state_rules: - - 8 전이 패밀리에 없는 전이는 거부한다. - - 전이 전 current state == from_state 여야 한다(레이스 감지). - - closed->idle / pending_buy->idle 복구 시 점유 필드를 초기화한다. - - name: SlotTransitionRecord - purpose: 슬롯 상태 전이의 append-only 감사 기록. - source_of_truth: slot_transition_log (향후 테이블) / 현재는 in-memory list - lifecycle: append-only (UPDATE/DELETE 금지) - key_fields: - - slot_id :: UUID :: 대상 슬롯 - - from_state / to_state :: SlotState :: 전이 전후 상태 - - ts :: datetime :: 전이 시각(UTC) - - reason / actor :: str :: 전이 사유와 주체 - - extra :: dict :: 부가 컨텍스트(예: symbol) - relationships: - - Slot :: slot_id 로 연결 - state_rules: - - 오직 INSERT 만 허용한다. - consistency_rules: - - (trading_date, symbol) 재진입 카운트는 closed->idle 복구와 원자적으로 증가한다. - - 모든 상태 변경과 전이 로그 append 는 동일 advisory-locked 트랜잭션 안에서 일어난다. - persistence_notes: - - 현재는 InMemorySlotRepository(threading.Lock 으로 advisory lock 모사)만 제공한다. - - 향후 PostgreSQL 구현 시 부록의 컬럼 계약(plan.md 부록 A)을 만족해야 한다. - current_gaps: - - daily_slot / daily_symbol_entries / slot_transition_log 의 물리 스키마(DDL)는 미이식 — 영속화 단계에서 작성. - -notes: - known_unknowns: - - SLOT_ADVISORY_LOCK_ID 의 소속(순수 도메인 잔류 vs 영속화 계층 이동) - - 영속화 backend 선택 및 SQL 스키마 재구성 시점 - - trex-orbit 과의 향후 코드 수렴 여부(현재는 병행) - -metadata: - captured_at_kst: 2026-06-03 00:00 - source: bootstrap-baseline-questionnaire - updated_from: mixed diff --git a/.stage-pilot/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml b/.stage-pilot/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml deleted file mode 100644 index ef31bc4..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml +++ /dev/null @@ -1,115 +0,0 @@ -schema_version: 1 -capture_mode: declared - -project: - repository_name: sample-host-project - display_name: Sample Host Project - summary: Python-based API service for curated market data analysis. - primary_domain: financial analytics - -stack: - tech_stack: - - Python - - FastAPI - - PostgreSQL - -runtime: - primary_runtime: api-service - primary_entrypoints: - - name: uvicorn src.main:app - purpose: serve the HTTP API - - name: python -m src.jobs.refresh_market_data - purpose: refresh curated market data snapshots - -interfaces: - contracts: - - name: HTTP market data API - type: http-api - actors: internal dashboards and downstream service clients - purpose: serve curated market data and derived analytics to clients - stability: experimental - inputs: - - client requests for curated market data and analytics - outputs: - - JSON market snapshot payloads and derived analytics payloads - errors: - - 404 not_found :: symbol is not available in the curated dataset - - 503 upstream_unavailable :: refreshed data is temporarily unavailable - compatibility_rules: - - response payload changes should remain backward compatible within the same API version - - name: Refresh job command - type: cli - actors: operators and scheduled automation - purpose: update stored market data snapshots from upstream sources - stability: internal - inputs: - - python -m src.jobs.refresh_market_data :: trigger a refresh of curated market data snapshots - outputs: - - refreshed local snapshot artifacts persisted for later API reads - errors: - - non-zero exit code :: refresh failed and data should be treated as stale - compatibility_rules: - - command arguments and exit-code semantics should remain stable for automation callers - shared_constraints: - - all interfaces assume symbol identifiers are validated before persistence or response rendering - - refresh failures must not silently overwrite previously known-good snapshots - current_gaps: - - authentication and authorization rules are not defined yet - - API versioning strategy is still undecided - -structure: - planned_top_level_areas: - - path: src/ - responsibility: application code and background jobs - - path: scripts/ - responsibility: developer and operator helper commands - - path: tests/ - responsibility: automated validation and regression coverage - -data_model: - persistence_backend: PostgreSQL - entities: - - name: Market Snapshot - purpose: represent the latest curated market data for a tracked symbol - source_of_truth: PostgreSQL market_snapshots table - lifecycle: collected -> normalized -> published -> superseded - key_fields: - - symbol :: string :: tracked market identifier - - captured_at :: timestamp :: time the upstream data snapshot was collected - - payload :: json :: normalized market data stored for API responses and analytics inputs - relationships: - - Analytics Snapshot :: derived from one published Market Snapshot - state_rules: - - only one published snapshot per symbol should be treated as current - - superseded snapshots remain queryable for audit or replay use cases - - name: Analytics Snapshot - purpose: represent derived metrics computed from a market snapshot - source_of_truth: PostgreSQL analytics_snapshots table - lifecycle: queued -> computed -> published -> superseded - key_fields: - - symbol :: string :: tracked market identifier - - derived_at :: timestamp :: time analytics were computed - - metrics :: json :: derived indicators and summary values - relationships: - - Market Snapshot :: source snapshot used to compute analytics - state_rules: - - published analytics must reference an existing published or superseded market snapshot - - analytics should be recomputed whenever a new published market snapshot replaces the current one - consistency_rules: - - symbol identifiers must remain consistent across snapshot and analytics entities - - analytics records must not exist without a corresponding source market snapshot - persistence_notes: - - refresh jobs write Market Snapshot rows before recomputing Analytics Snapshot rows - - future retention and archival policies are not defined yet - current_gaps: - - exact table schema and indexing strategy are still undecided - - historical retention policy is still undecided - -notes: - known_unknowns: - - deployment target is undecided - -metadata: - captured_at_kst: 2026-04-30 16:20 - source: bootstrap-baseline-questionnaire - updated_from: user-answer \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/README.md b/.stage-pilot/examples/bootstrap-baseline/README.md deleted file mode 100644 index 4097e59..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Bootstrap Baseline Example - -이 예시는 fresh host 저장소에 StagePilot을 설치한 뒤 `/bootstrap-baseline`을 실행했을 때 남아야 하는 최소 active baseline 상태를 보여 준다. - -## Scenario - -1. host 저장소에 StagePilot을 설치했다. -2. 아직 Discovery, REQ, Batch, Release 문서는 하나도 없었다. -3. doctor를 실행하면 `INFO [bootstrap-required]`로 `/bootstrap-baseline`이 필요하다는 안내가 나온다. -4. `/bootstrap-baseline`이 최소 질문 세트를 받고 `.stage-pilot/bootstrap/baseline.yaml`을 만든 뒤 baseline 문서와 active index를 초기화했다. -5. 첫 real Discovery는 아직 시작하지 않았고, 이 예시는 그 직후 상태를 보여 준다. - -## Files to inspect - -- `doctor-before-bootstrap-output.md` - - bootstrap 이전 fresh host 저장소에서 기대하는 doctor 출력 예시다. -- `bootstrap-baseline-output.md` - - `/bootstrap-baseline` 실행 결과 예시다. -- `.stage-pilot/bootstrap/baseline.yaml` - - bootstrap 질문 답변을 저장한 seed file 예시다. -- `docs/project-structure.md` - - bootstrap으로 생성된 구조 baseline 초안 예시다. -- `docs/runtime-flows.md` - - bootstrap으로 생성된 runtime flow baseline 초안 예시다. -- `docs/interface-contract.md` - - bootstrap으로 생성된 인터페이스 계약 baseline 초안 예시다. -- `docs/data-model.md` - - bootstrap으로 생성된 데이터 모델 baseline 초안 예시다. -- `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md` - - 첫 real Discovery 전까지 비어 있어도 되는 register 골격이다. - -## Validation - -이 예시 루트는 doctor로 독립 검증할 수 있다. - -```bash -python3 .stage-pilot/tools/stagepilot-doctor.py examples/bootstrap-baseline -``` \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/bootstrap-baseline-output.md b/.stage-pilot/examples/bootstrap-baseline/bootstrap-baseline-output.md deleted file mode 100644 index 9cb4972..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/bootstrap-baseline-output.md +++ /dev/null @@ -1,45 +0,0 @@ -# Sample Output: bootstrap-baseline - -- Input: - - fresh host repository after StagePilot install -- Asked Questions: - - `project-summary` -> `Python-based API service for curated market data analysis` - - `primary-domain` -> `financial analytics` - - `tech-stack` -> `Python, FastAPI, PostgreSQL` - - `primary-runtime` -> `api-service` - - `primary-entrypoints` -> `uvicorn src.main:app :: serve the HTTP API`, `python -m src.jobs.refresh_market_data :: refresh curated market data snapshots` - - `interface-contracts` -> `HTTP market data API :: http-api :: internal dashboards and downstream service clients :: serve curated market data and derived analytics to clients`, `Refresh job command :: cli :: operators and scheduled automation :: update stored market data snapshots from upstream sources` - - `interface-inputs` -> `HTTP market data API :: GET /markets/{symbol} :: request the current curated snapshot for a symbol`, `HTTP market data API :: GET /analytics/{symbol} :: request derived analytics for a symbol`, `Refresh job command :: python -m src.jobs.refresh_market_data :: trigger a refresh of curated market data snapshots` - - `interface-outputs` -> `HTTP market data API :: JSON market snapshot payload :: current normalized market data for the requested symbol`, `HTTP market data API :: JSON analytics payload :: derived analytics and summary metrics for the requested symbol`, `Refresh job command :: refreshed local snapshot artifacts :: normalized data persisted for later API reads` - - `interface-errors` -> `HTTP market data API :: 404 not_found :: symbol is not available in the curated dataset`, `HTTP market data API :: 503 upstream_unavailable :: refreshed data is temporarily unavailable`, `Refresh job command :: non-zero exit code :: refresh failed and data should be treated as stale` - - `core-entities` -> `Market Snapshot :: represent the latest curated market data for a tracked symbol`, `Analytics Snapshot :: represent derived metrics computed from a market snapshot` - - `persistence-backend` -> `PostgreSQL` - - `compatibility-rules` -> `HTTP market data API :: response payload changes should remain backward compatible within the same API version`, `Refresh job command :: command arguments and exit-code semantics should remain stable for automation callers` -- Created Files: - - `.stage-pilot/bootstrap/baseline.yaml` - - `docs/discovery/index.md` - - `docs/srs/index.md` - - `docs/batches/index.md` - - `docs/releases/index.md` - - `docs/project-structure.md` - - `docs/runtime-flows.md` - - `docs/interface-contract.md` - - `docs/data-model.md` -- Seed Status: - - `.stage-pilot/bootstrap/baseline.yaml`: created -- Baseline Status: - - `docs/project-structure.md`: created - - `docs/runtime-flows.md`: created - - `docs/interface-contract.md`: created - - `docs/data-model.md`: created -- Index Status: - - `docs/discovery/index.md`: created - - `docs/srs/index.md`: created - - `docs/batches/index.md`: created - - `docs/releases/index.md`: created -- Notes: - - baseline docs were rendered from `.stage-pilot/bootstrap/baseline.yaml` - - no Discovery document was created - - no REQ, Batch, or Release unit was opened -- Next Action: - - `/new-discovery ` \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/batches/index.md b/.stage-pilot/examples/bootstrap-baseline/docs/batches/index.md deleted file mode 100644 index 8df4c90..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/batches/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Batch Index - -## Batch Register - -| BAT ID | Profile | Status | Included REQ | Discovery | Folder | -| --- | --- | --- | --- | --- | --- | \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/data-model.md b/.stage-pilot/examples/bootstrap-baseline/docs/data-model.md deleted file mode 100644 index ce77eb4..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/data-model.md +++ /dev/null @@ -1,97 +0,0 @@ -# Sample Host Project Data Model - -- Baseline Mode: declared -- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml -- Status: confirmed -- Owner: Platform Team -- Last Updated (KST): 2026-04-30 16:30 -- Source Discovery / Batch: bootstrap-baseline - -## Project Summary -- Python-based API service for curated market data analysis. - -## Primary Domain -- financial analytics - -## Primary Persistence Backend -- PostgreSQL - -## Purpose -- This document captures the initial data model baseline after StagePilot bootstrap. -- It provides a shared reference for later REQ, delivery, migration, and verification work. - -## Scope -- Covers core entities, relationships, lifecycle rules, and persistence assumptions for the sample host project. -- Does not replace ORM code, migrations, or detailed acceptance criteria. - -## Model Summary - -- Market Snapshot :: normalized raw and curated market data for a tracked symbol -- Analytics Snapshot :: derived metrics computed from a Market Snapshot - -## Entity: `Market Snapshot` - -- Purpose: represent the latest curated market data for a tracked symbol -- Source of Truth: PostgreSQL `market_snapshots` table -- Lifecycle: collected -> normalized -> published -> superseded - -### Key Fields - -- `symbol` :: string :: tracked market identifier -- `captured_at` :: timestamp :: time the upstream data snapshot was collected -- `payload` :: json :: normalized market data stored for API responses and analytics inputs - -### Relationships - -- `Analytics Snapshot` :: derived from one published Market Snapshot - -### State Rules - -- only one published snapshot per symbol should be treated as current -- superseded snapshots remain queryable for audit or replay use cases - -## Entity: `Analytics Snapshot` - -- Purpose: represent derived metrics calculated from a market snapshot -- Source of Truth: PostgreSQL `analytics_snapshots` table -- Lifecycle: queued -> computed -> published -> superseded - -### Key Fields - -- `symbol` :: string :: tracked market identifier -- `derived_at` :: timestamp :: time analytics were computed -- `metrics` :: json :: derived indicators and summary values - -### Relationships - -- `Market Snapshot` :: source snapshot used to compute analytics - -### State Rules - -- published analytics must reference an existing published or superseded market snapshot -- analytics should be recomputed whenever a new published market snapshot replaces the current one - -## Shared Consistency Rules - -- `symbol` identifiers must remain consistent across snapshot and analytics entities -- analytics records must not exist without a corresponding source market snapshot - -## Persistence / Integration Notes - -- refresh jobs write Market Snapshot rows before recomputing Analytics Snapshot rows -- future retention and archival policies are not defined yet - -## Current Gaps / Planned Changes - -- exact table schema and indexing strategy are still undecided -- historical retention policy is still undecided - -## Update Triggers - -- Update this document when a core entity, key field, or relationship changes. -- Update this document when lifecycle or persistence behavior changes. -- Update this document when a batch introduces a migration, backfill, or breaking data assumption. - -## Change Log - -- 2026-04-30: bootstrap-baseline created the initial data model baseline from declared project inputs. \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/discovery/index.md b/.stage-pilot/examples/bootstrap-baseline/docs/discovery/index.md deleted file mode 100644 index 8c9f028..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/discovery/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Discovery 전체 인덱스 - - - -## Discovery 목록 - -| ID | 날짜 | 이슈명 | 문서 | 상태 | -|---|---|---|---|---| \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/interface-contract.md b/.stage-pilot/examples/bootstrap-baseline/docs/interface-contract.md deleted file mode 100644 index fb816ec..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/interface-contract.md +++ /dev/null @@ -1,97 +0,0 @@ -# Sample Host Project Interface Contract - -- Baseline Mode: declared -- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml -- Status: confirmed -- Owner: Platform Team -- Last Updated (KST): 2026-04-30 16:30 -- Source Discovery / Batch: bootstrap-baseline -- Primary Runtime: api-service - -## Project Summary -- Python-based API service for curated market data analysis. - -## Primary Domain -- financial analytics - -## Purpose -- This document captures the initial interface contract baseline after StagePilot bootstrap. -- It provides a shared reference for future REQ, design, implementation, and verification work. - -## Scope -- Covers the planned API and job interfaces exposed by the sample host project. -- Does not duplicate detailed acceptance criteria or generated OpenAPI/schema artifacts. - -## Interface Summary - -- HTTP market data API :: serve curated market data and derived analytics to clients -- Refresh job command :: update stored market data snapshots from upstream sources - -## Interface: `HTTP market data API` - -- Type: http-api -- Consumers / Producers: internal dashboards and downstream service clients -- Purpose: serve curated market data and derived analytics to clients -- Stability: experimental - -### Inputs - -- `GET /markets/{symbol}` :: request the current curated snapshot for a symbol -- `GET /analytics/{symbol}` :: request derived analytics for a symbol - -### Outputs - -- JSON market snapshot payload :: current normalized market data for the requested symbol -- JSON analytics payload :: derived analytics and summary metrics for the requested symbol - -### Error Contract - -- `404 not_found` :: symbol is not available in the curated dataset -- `503 upstream_unavailable` :: refreshed data is temporarily unavailable - -### Compatibility Rules - -- response payload changes should remain backward compatible within the same API version - -## Interface: `Refresh job command` - -- Type: cli -- Consumers / Producers: operators and scheduled automation -- Purpose: update stored market data snapshots from upstream sources -- Stability: internal - -### Inputs - -- `python -m src.jobs.refresh_market_data` :: trigger a refresh of curated market data snapshots - -### Outputs - -- refreshed local snapshot artifacts :: normalized data persisted for later API reads - -### Error Contract - -- non-zero exit code :: refresh failed and data should be treated as stale - -### Compatibility Rules - -- command arguments and exit-code semantics should remain stable for automation callers - -## Shared Constraints - -- all interfaces assume symbol identifiers are validated before persistence or response rendering -- refresh failures must not silently overwrite previously known-good snapshots - -## Current Gaps / Planned Changes - -- authentication and authorization rules are not defined yet -- API versioning strategy is still undecided - -## Update Triggers - -- Update this document when an externally visible API, event, CLI, or file contract changes. -- Update this document when a batch changes compatibility, validation, or error behavior. -- Update this document when a new external consumer or producer is introduced. - -## Change Log - -- 2026-04-30: bootstrap-baseline created the initial interface contract baseline from declared project inputs. \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/project-structure.md b/.stage-pilot/examples/bootstrap-baseline/docs/project-structure.md deleted file mode 100644 index 70bc4c9..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/project-structure.md +++ /dev/null @@ -1,91 +0,0 @@ -# Sample Host Project Structure - -- Baseline Mode: declared -- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml -- Status: confirmed -- Owner: Platform Team -- Last Updated (KST): 2026-04-30 16:30 -- Source Discovery / Batch: bootstrap-baseline - -## Project Summary -- Python-based API service for curated market data analysis. - -## Primary Domain -- financial analytics - -## Tech Stack -- Python -- FastAPI -- PostgreSQL - -## Primary Runtime -- Type: api-service -- Planned Entry Points: -- uvicorn src.main:app :: serve the HTTP API -- python -m src.jobs.refresh_market_data :: refresh curated market data snapshots - -## Purpose -- This document captures the initial repository structure baseline after StagePilot bootstrap. -- It becomes a shared reference before the first real Discovery starts. - -## Scope -- Covers top-level repository layout and broad module boundaries. -- Does not define feature-specific acceptance criteria or delivery units. - -## Top-Level Structure - -```text -. -├── .github/ -├── .stage-pilot/ -├── docs/ -├── src/ -├── scripts/ -├── tests/ -└── README.md -``` - -## Package / Module Responsibilities - -- `.github/` - - StagePilot skills, templates, and host instructions installed into the repository. -- `.stage-pilot/` - - bootstrap declaration seed used to render the baseline documents. -- `docs/` - - Active SDLC docs, index files, and cross-cutting baseline references. -- `src/` - - Product and service implementation code. -- `scripts/` - - developer and operator helper commands. -- `tests/` - - Automated checks and regression coverage. - -## Dependency Rules - -- Delivery documents under `docs/` must not be treated as implementation modules. -- `src/` can evolve independently, but structure changes should update this baseline. -- `.stage-pilot/bootstrap/baseline.yaml` should be updated when the declared project identity changes before code exists. -- Tooling automation under `.stage-pilot/` must not overwrite host source code. - -## Shared Boundaries - -- `.stage-pilot/` provides SDLC workflow assets. -- `.stage-pilot/` stores bootstrap declarations outside active SDLC units. -- `docs/` is the source of truth for active SDLC state. -- `src/`, `scripts/`, and `tests/` remain host-project owned implementation surfaces. - -## Current Gaps / Planned Changes - -- The first real Discovery has not been created yet. -- Deployment target is still undecided. -- Actual domain module boundaries will be refined once the first delivery topic is defined. - -## Update Triggers - -- Update this document when top-level directories change. -- Update this document when module ownership boundaries change. -- Update this document when a batch changes the approved structure baseline. - -## Change Log - -- 2026-04-30: bootstrap-baseline created the initial repository structure baseline from `.stage-pilot/bootstrap/baseline.yaml`. \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/releases/index.md b/.stage-pilot/examples/bootstrap-baseline/docs/releases/index.md deleted file mode 100644 index a7717ce..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/releases/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Release Index - -## Release Register - -| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | -| --- | --- | --- | --- | --- | --- | \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/runtime-flows.md b/.stage-pilot/examples/bootstrap-baseline/docs/runtime-flows.md deleted file mode 100644 index 0e3780e..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/runtime-flows.md +++ /dev/null @@ -1,68 +0,0 @@ -# Sample Host Project Runtime Flows - -- Baseline Mode: declared -- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml -- Status: confirmed -- Owner: Platform Team -- Last Updated (KST): 2026-04-30 16:30 -- Source Discovery / Batch: bootstrap-baseline -- Primary Runtime: api-service - -## Project Summary -- Python-based API service for curated market data analysis. - -## Purpose -- This document captures the initial command and runtime flow baseline after StagePilot bootstrap. -- It gives later Discovery and Batch work a stable reference before product-specific flows exist. - -## Scope -- Covers bootstrap, discovery start, and validation entry points. -- Does not define detailed feature behavior. - -## Covered Entry Points - -- uvicorn src.main:app :: serve the HTTP API -- python -m src.jobs.refresh_market_data :: refresh curated market data snapshots -- Copilot Chat /bootstrap-baseline :: initialize declared baseline docs - -## Shared Components - -- .stage-pilot/bootstrap/baseline.yaml -- `.stage-pilot/skills/` -- `docs/project-structure.md` -- `docs/*/index.md` - -## Flow: `bootstrap baseline initialization` - -1. StagePilot assets are installed into the host repository. -2. `/bootstrap-baseline` collects the minimum declaration set and writes `.stage-pilot/bootstrap/baseline.yaml`. -3. baseline docs and active index skeletons are rendered from that seed. -4. The repository becomes ready for the first real Discovery. - -## Flow: `planned host runtime` - -1. Operator starts `uvicorn src.main:app` to serve the planned HTTP API. -2. Background refresh can run with `python -m src.jobs.refresh_market_data`. -3. Detailed production flows remain subject to later Discovery and delivery work. - -## Flow Constraints - -- Fresh host repositories should complete baseline initialization before the first real Discovery. -- Declared runtime assumptions must stay in sync with `.stage-pilot/bootstrap/baseline.yaml` until observed implementation evidence exists. -- Structure or orchestration changes must update this document or `docs/project-structure.md`. - -## Current Gaps / Planned Changes - -- Product-specific runtime orchestration is not defined yet. -- Deployment target is undecided. -- Delivery and release flows will be added by later Discovery and Batch work. - -## Update Triggers - -- Update this document when command entry points change. -- Update this document when runtime orchestration changes. -- Update this document when a batch changes the approved flow baseline. - -## Change Log - -- 2026-04-30: bootstrap-baseline created the initial runtime flow baseline from `.stage-pilot/bootstrap/baseline.yaml`. \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/docs/srs/index.md b/.stage-pilot/examples/bootstrap-baseline/docs/srs/index.md deleted file mode 100644 index be0d471..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/docs/srs/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# Software Requirement Specification Index - -## 목적 -- 이 문서는 bootstrap-baseline 직후의 requirement register 골격 예시다. -- 첫 real Discovery 이후 `draft-req`가 실제 requirement를 추가한다. - -## Next Requirement ID - -- Current: `REQ-001` -- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - -## Requirement Register - -| ID | Title | Type | Status | Priority | Owner | Link | -| --- | --- | --- | --- | --- | --- | --- | - -## Recent Change Log Summary - -- none \ No newline at end of file diff --git a/.stage-pilot/examples/bootstrap-baseline/doctor-before-bootstrap-output.md b/.stage-pilot/examples/bootstrap-baseline/doctor-before-bootstrap-output.md deleted file mode 100644 index 4181d8d..0000000 --- a/.stage-pilot/examples/bootstrap-baseline/doctor-before-bootstrap-output.md +++ /dev/null @@ -1,9 +0,0 @@ -# Sample Output: doctor before bootstrap-baseline - -fresh host 저장소에서 아직 baseline이 초기화되지 않은 경우 doctor는 아래처럼 bootstrap 안내를 함께 출력한다. - -```text -WARN [missing-active-docs] .: No active SDLC docs were found under docs/. Workspace-specific checks were skipped. -INFO [bootstrap-required] .: Fresh host repos should run /bootstrap-baseline in Copilot Chat before the first real Discovery. Missing bootstrap files: docs/discovery/index.md, docs/srs/index.md, docs/batches/index.md, docs/releases/index.md, docs/project-structure.md, docs/runtime-flows.md -stagepilot-doctor: 0 error(s), 1 warning(s), 1 info message(s) -``` \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/README.md b/.stage-pilot/examples/p3-change-management/README.md deleted file mode 100644 index 326255a..0000000 --- a/.stage-pilot/examples/p3-change-management/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# P3 Example: change-req and suggest-next-discovery - -이 예시는 release feedback 이후 개선 루프를 어떻게 닫는지 보여 주는 self-contained sample workspace다. - -## Scenario - -1. `rel-001` 배포가 `feedback-captured` 상태로 닫혔다. -2. 운영 피드백에서 두 종류의 후속 작업이 나왔다. - - 기존 REQ 강화: 문서 release checklist의 acceptance criteria를 더 엄격하게 바꿔야 한다. - - 신규 반복 후보: release feedback를 기반으로 follow-up triage를 자동 추천하는 개선이 필요하다. -3. `suggest-next-discovery rel-001`는 위 두 항목을 각각 `change-req`와 `new-discovery`로 분류한다. -4. 이 예시의 active docs는 그 결과 `change-req req-001`를 이미 한 번 적용한 뒤의 상태를 보여 준다. - -## Files to inspect - -- `docs/releases/rel-001_20260428_docs_release_hardening.md` - - `Feedback Handoff`에 `Discovery Input`, `REQ Input`, `Change Request Input`이 모두 있다. -- `docs/srs/Documentation/req-001_docs_release_checklist.md` - - 두 번째 `Change Log` 항목이 `change-req` 실행 결과를 나타낸다. - - 구현 근거가 무효화되어 상태가 `Approved`로 되돌아간 상태다. -- `suggest-next-discovery-output.md` - - `suggest-next-discovery rel-001`의 예시 결과다. -- `change-req-output.md` - - `change-req req-001`의 예시 결과다. - -## Validation - -이 예시 루트는 doctor로 독립 검증할 수 있다. - -```bash -python3 .stage-pilot/tools/stagepilot-doctor.py examples/p3-change-management -``` \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/change-req-output.md b/.stage-pilot/examples/p3-change-management/change-req-output.md deleted file mode 100644 index 0bea906..0000000 --- a/.stage-pilot/examples/p3-change-management/change-req-output.md +++ /dev/null @@ -1,22 +0,0 @@ -# Sample Output: change-req req-001 - -- Target REQ: `docs/srs/Documentation/req-001_docs_release_checklist.md` -- New Change Entry: `CHG-2026-04-30-02` represented as `CHG-20260430-02` -- Change Summary: - - docs-only release profile에서도 broken-link 검증을 필수 acceptance criteria로 승격 - - delivery trace에 기존 batch/release 영향과 재검증 필요 여부를 추가 -- Impacted Delivery Trace: - - Batches: `bat-001` - - Releases: `rel-001` -- Revalidation Impact: - - Reverification Needed: `yes` - - Existing Implementation Invalidated: `yes` - - Status Recommendation: `revert-to-approved` -- Result: - - REQ status changed from `Implemented` to `Approved` - - `docs/srs/index.md` register status changed to `Approved` - - `Recent Change Log Summary` updated with the new change entry - -## Rationale - -이 변경은 기존 release evidence만으로는 새 acceptance criteria를 충족했다고 볼 수 없으므로 `Implemented` 상태를 유지할 수 없다. 따라서 REQ는 `Approved`로 되돌아가고, 후속 batch verification을 다시 거쳐야 한다. \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md b/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md deleted file mode 100644 index 22684fe..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md +++ /dev/null @@ -1,23 +0,0 @@ -# bat-001 Design - -- Status: confirmed - -## Architecture Summary -- docs-only release는 app-service health signal 대신 문서 품질 증거를 사용한다. - -## Changed Areas -- release template -- feedback handoff structure - -## Key Decisions -- docs-only release는 broken-link와 release note update를 핵심 evidence로 남긴다. - -## Edge Cases -- release 후 feedback로 acceptance criteria가 강화되면 REQ 상태를 되돌릴 수 있다. - -## Architecture Impact -- runtime-flows - -## Reference Doc Update Plan -- project-structure.md: none -- runtime-flows.md: docs-only release feedback flow 반영 \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md b/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md deleted file mode 100644 index 29e2d4e..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md +++ /dev/null @@ -1,22 +0,0 @@ -# bat-001 Implementation - -- Status: completed -- Batch: bat-001 - -## Plan Summary -- release template와 docs-only verification structure를 구현했다. - -## Changed Files -- .stage-pilot/templates/releases/release.md -- README.md - -## Execution Log -1. docs-only release profile 섹션 추가 -2. feedback handoff를 Discovery/REQ로 나누어 기록 - -## Validation -- manual release review -- doctor sample workspace check - -## Remaining Risks -- 후속 feedback가 REQ 변경으로 이어질 수 있다. \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md b/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md deleted file mode 100644 index 9899f86..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# bat-001: docs release hardening - -- Status: released -- Profile: standard -- Source Discovery: dcy-001 -- Included REQ: req-001 -- Excluded REQ: 없음 - -## Summary -- docs-only release checklist를 requirement와 release evidence로 연결하는 batch다. - -## Scope -- In Scope: - - release template update - - docs-only verification structure -- Out of Scope: - - runtime health checks - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- release feedback 이후 req-001이 다시 `Approved`로 되돌아갔다. \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md b/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md deleted file mode 100644 index 0fd10cd..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md +++ /dev/null @@ -1,30 +0,0 @@ -# bat-001 Planning - -- Status: confirmed -- Batch: bat-001 -- Profile: standard - -## Included REQ -- req-001 - -## Delivery Plan -- release template와 docs-only release evidence 구조를 정리한다. - -## Dependencies -- Internal: - - release template - - README validation guide -- External: - - none - -## Design Gate -- Design Required Before Implementation: yes -- Reason: docs-only release profile과 evidence structure를 새로 정의해야 한다. - -## Milestones -1. release template 정리 -2. release evidence 반영 -3. feedback handoff 확인 - -## Risks -- docs-only validation 기준이 release 후 강화될 수 있다. \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md b/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md deleted file mode 100644 index 0132a0f..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md +++ /dev/null @@ -1,22 +0,0 @@ -# bat-001 Verification - -- Status: confirmed -- Batch: bat-001 - -## Acceptance Mapping -- req-001: - - AC1 -> docs-only release checklist evidence - - AC2 -> feedback handoff structure - -## Evidence -- Test: - - doctor sample validation passed before release -- Manual: - - docs-only release note and checklist reviewed -- Logs: - - release review notes - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음 \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/batches/index.md b/.stage-pilot/examples/p3-change-management/docs/batches/index.md deleted file mode 100644 index 4930146..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/batches/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Batch Index - -## Batch Register - -| BAT ID | Profile | Status | Included REQ | Discovery | Folder | -| --- | --- | --- | --- | --- | --- | -| bat-001 | standard | released | req-001 | dcy-001 | [batch](./bat-001_20260427_docs_release_hardening) | \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md b/.stage-pilot/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md deleted file mode 100644 index 085d8ba..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md +++ /dev/null @@ -1,149 +0,0 @@ -# Discovery - -- 상태: confirmed -- 문서 ID: dcy-001_20260425_docs_release_quality_loop -- 이슈명: Docs release quality loop -- 작성 시각(KST): 2026-04-25 10:00 -- 마지막 갱신 시각(KST): 2026-04-30 13:00 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: req-001 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 없음 -- 해석: - - docs-only release에서도 문서 검증 품질이 release 판단 근거에 직접 연결돼야 한다. - - 운영 피드백으로 checklist 강화를 반복적으로 흡수할 수 있어야 한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - 기존 release checklist는 docs-only profile 세분화 기준이 부족했다. - - follow-up feedback를 기존 REQ와 연결할 필요가 있었다. - -# 3. 문제점의 요약 - -- 현재 상태 - - docs release는 수동 검토 메모에 의존한다. - - broken-link 점검과 release note 점검이 acceptance criteria로 명확히 드러나지 않는다. -- 기대 상태 - - release checklist가 명시적 acceptance criteria와 evidence로 연결된다. - - 운영 피드백이 후속 REQ 또는 Discovery로 자연스럽게 이어진다. -- 이해관계자 - - maintainer - - reviewer - - docs consumer -- 제약사항 - - 문서 패키지는 app-service 운영 지표를 사용하지 않는다. - - doctor 검증과 release 문서만으로도 추적 가능해야 한다. - -## 주요 사용자 시나리오 - -- maintainer가 docs-only release 전 broken-link와 release note 상태를 확인한다. -- reviewer가 release feedback를 보고 기존 REQ 변경인지 신규 반복인지 판단한다. - -# 4. 이번에 정의할 변경 - -- docs release checklist를 REQ와 release evidence에 연결한다. -- feedback handoff에서 후속 작업 분류 기준을 남긴다. - -## 영향 범위 - -- 코드 영향: - - 없음 -- 문서 영향: - - docs/srs/Documentation/req-001_docs_release_checklist.md - - docs/releases/rel-001_20260428_docs_release_hardening.md -- 운영 영향: - - docs-only release review 절차 강화 - -# 5. 요구사항 목록 - -## 기능 요구사항 -- FR-1: docs-only release는 broken-link 검증을 acceptance criteria로 포함해야 한다. -- FR-2: release feedback는 후속 Discovery/REQ/change-req 입력으로 분리되어야 한다. - -## 비기능 요구사항 -- NFR-1: 변경 이력은 Change Log와 release feedback 양쪽에서 추적 가능해야 한다. - -## 범위 경계 -- In Scope - - docs release checklist - - feedback handoff structure -- Out of Scope - - app-service runtime health check - - 외부 배포 자동화 - -# 6. 리스크/가정 목록 - -## 리스크 -- R-1 (Medium): checklist 강화 시 기존 구현 증거가 부족해질 수 있다. - - 완화 방안: change-req로 상태 되돌림과 재검증 계획을 남긴다. -- R-2 (Low): feedback 분류가 모호하면 follow-up 경로가 섞일 수 있다. - - 완화 방안: suggest-next-discovery에서 분류 근거를 명시한다. - -## 가정 -- A-1: docs-only release는 doctor와 수동 리뷰 결과로 검증 가능하다. -- A-2: feedback handoff는 release 문서가 source of truth다. - -# 7. 초기 성공 기준 - -- S-1: docs checklist REQ가 release feedback까지 추적된다. -- S-2: feedback에서 change-req와 new-discovery가 구분된다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: doctor traceability 확인 / 데이터 출처: docs/srs, docs/releases -- S-2 측정 방식: feedback handoff review / 데이터 출처: release 문서 - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: docs checklist와 feedback 구조만 포함 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: doctor와 release 문서로 추적 가능 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: maintainer가 후속 변경 관리 수행 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: open question 없음 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: maintainer confirmed - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-001_20260425_docs_release_quality_loop.md -- 참조 문서: - - docs/srs/Documentation/req-001_docs_release_checklist.md - - docs/releases/rel-001_20260428_docs_release_hardening.md - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE -- 없음 - -## CONFIRM -- 없음 - -## DATA -- 없음 - -# 11. Discovery Freeze - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: docs release quality loop를 REQ와 release feedback에 연결할 준비가 됨 -- Ready for REQ Drafting: true -- Confirmed By: maintainer -- Confirmed At (KST): 2026-04-25 11:00 \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/discovery/index.md b/.stage-pilot/examples/p3-change-management/docs/discovery/index.md deleted file mode 100644 index 3dde566..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/discovery/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Discovery 전체 인덱스 - -## Discovery 목록 - -| ID | 날짜 | 이슈명 | 문서 | 상태 | -|---|---|---|---|---| -| dcy-001 | 20260425 | Docs release quality loop | [Discovery](./dcy-001_20260425_docs_release_quality_loop.md) | confirmed | \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/releases/index.md b/.stage-pilot/examples/p3-change-management/docs/releases/index.md deleted file mode 100644 index f454a80..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/releases/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Release Index - -## Release Register - -| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | -| --- | --- | --- | --- | --- | --- | -| rel-001 | docs-only | feedback-captured | bat-001 | dcy-001, req-001 | [release](./rel-001_20260428_docs_release_hardening.md) | \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md b/.stage-pilot/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md deleted file mode 100644 index f630951..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md +++ /dev/null @@ -1,94 +0,0 @@ -# rel-001: docs release hardening - -- Status: feedback-captured -- Profile: docs-only -- Included Batch: bat-001 - -## Scope -- Included: - - docs-only release profile evidence - - feedback handoff structure -- Excluded: - - app-service rollout automation - -## Rollout Plan -1. merge release template updates -2. publish package update - -## Rollback Plan -- revert package update and release note changes - -## Verification Checklist -- docs-only release note updated -- broken-link verification reviewed -- feedback handoff recorded - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-04-28T09:00:00Z -- Completed At: - - 2026-04-28T09:12:00Z -- Environment: - - repository package -- Operator: - - docs maintainer -- Revision or Artifact: - - main@HEAD -- Commands or Procedure: - - merge release prep and publish package update - -### Profile-Specific Checks -- docs-only: - - Published or Packaged Targets: - - release template and README guidance - - Link or Rendering Check: - - doctor broken-link validation reviewed during release - - Navigation or Consumer Update: - - release note updated for docs-only profile -- tooling: - - Install or Upgrade Smoke: - - none - - CLI or Script Execution: - - none - - Compatibility Note: - - none -- app-service: - - Health Endpoint: - - n/a - - Startup Log Fields: - - n/a - - Request Log Fields: - - n/a - - Smoke or Manual Verification: - - n/a - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - yes -- Rationale: - - docs-only release evidence was sufficient at release time -- Recorded By: - - docs maintainer -- Recorded At: - - 2026-04-28T09:12:00Z - -## Feedback Handoff -- Observation Summary: - - docs consumers requested stricter broken-link evidence and clearer follow-up triage -- Discovery Input: - - release feedback triage automation for maintainers -- REQ Input: - - release note checklist for docs-only profile -- Change Request Input: - - req-001 docs release checklist needs stricter broken-link acceptance criteria \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md b/.stage-pilot/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md deleted file mode 100644 index da1d307..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md +++ /dev/null @@ -1,110 +0,0 @@ -# req-001: Docs release checklist - -- Status: Approved -- Type: Documentation -- Priority: Medium -- Owner: Docs Maintainer - -## Intent -- docs-only release에서도 소비자 관점의 품질 게이트를 문서화하고 검증 근거로 남긴다. - -## Requirement -- docs-only release는 release note 반영과 broken-link 검증 결과를 release evidence에 포함해야 한다. - -## Acceptance Criteria -1. Given docs-only release가 준비되었을 때, When release evidence를 검토하면, Then broken-link 검증 결과가 명시되어야 한다. -2. Given release feedback가 수집되었을 때, When follow-up를 분류하면, Then Discovery/REQ/change-req 입력이 구분되어야 한다. - -## Impacted Area -- Docs: docs/releases/rel-001_20260428_docs_release_hardening.md, README.md -- Modules: tools/stagepilot.sh, .stage-pilot/tools/stagepilot-doctor.py -- Tests: doctor release feedback summary validation - -## Notes -- `CHG-20260430-02`로 acceptance criteria가 강화되어 기존 구현 증거는 재검증이 필요하다. - -## Change Log - -### CHG-20260426-01 -- Date: 2026-04-26 -- Author: Docs Maintainer - -#### Change Summary -- initial requirement created - -#### Intent -- docs-only release checklist를 requirement로 관리한다. - -#### Acceptance Criteria Delta -- Added: - - release evidence must include release note update result -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - release template -- Modules: - - none -- Tests: - - manual release review - -#### Delivery Trace Delta -- Batches: - - bat-001 -- Releases: - - rel-001 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: implement through bat-001 - -#### Validation Plan -- verify release checklist before approval - -### CHG-20260430-02 -- Date: 2026-04-30 -- Author: Docs Maintainer - -#### Change Summary -- strengthen docs-only release criteria to require broken-link evidence and explicit feedback triage - -#### Intent -- release feedback에서 드러난 품질 gap을 기존 REQ에 반영한다. - -#### Acceptance Criteria Delta -- Added: - - broken-link 검증 결과가 release evidence에 포함되어야 한다. - - feedback handoff는 Discovery/REQ/change-req 입력을 구분해야 한다. -- Updated: - - release note update alone is no longer sufficient for docs-only release verification. -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/releases/rel-001_20260428_docs_release_hardening.md - - README.md -- Modules: - - .stage-pilot/tools/stagepilot-doctor.py -- Tests: - - doctor feedback summary sample - -#### Delivery Trace Delta -- Batches: - - bat-001 needs reverification or successor batch is required -- Releases: - - rel-001 remains historical evidence only - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: yes -- Status Recommendation: revert-to-approved -- Follow-up Action: prepare a follow-up batch to reverify docs-only release criteria - -#### Validation Plan -- rerun doctor on a follow-up batch and verify release feedback summary plus broken-link evidence \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/docs/srs/index.md b/.stage-pilot/examples/p3-change-management/docs/srs/index.md deleted file mode 100644 index dc68960..0000000 --- a/.stage-pilot/examples/p3-change-management/docs/srs/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# Software Requirement Specification Index - -## 목적 -- 이 문서는 프로젝트의 SRS 운영 기준과 requirement 목록을 관리하는 entry point이다. -- 개별 requirement의 상세 내용은 타입별 폴더 하위 `req-XXX_*.md` 파일에서 관리한다. - -## 사용 방법 -1. 새로운 requirement가 필요하면 `Next Requirement ID`를 확인하고 `docs/srs/req-template.md`를 복사해 `docs/srs//req-XXX_.md` 파일을 생성한다. -2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. -3. 작성 또는 수정 후 누락과 모호성을 점검한다. -4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다. - -## Next Requirement ID - -- Current: `REQ-002` -- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - -## Requirement Register - -| ID | Title | Type | Status | Priority | Owner | Link | -| --- | --- | --- | --- | --- | --- | --- | -| REQ-001 | Docs release checklist | Documentation | Approved | Medium | Docs Maintainer | [req-001](./Documentation/req-001_docs_release_checklist.md) | - -## Recent Change Log Summary - -- 2026-04-30 `REQ-001` `CHG-20260430-02`: broken-link acceptance criteria 강화, 상태 `Implemented -> Approved` \ No newline at end of file diff --git a/.stage-pilot/examples/p3-change-management/suggest-next-discovery-output.md b/.stage-pilot/examples/p3-change-management/suggest-next-discovery-output.md deleted file mode 100644 index cd722ae..0000000 --- a/.stage-pilot/examples/p3-change-management/suggest-next-discovery-output.md +++ /dev/null @@ -1,44 +0,0 @@ -# Sample Output: suggest-next-discovery rel-001 - -- Input Release: `rel-001` -- Release Status: `feedback-captured` -- Basis: - - `Discovery Input`: `release feedback triage automation for maintainers` - - `REQ Input`: `release note checklist for docs-only profile` - - `Change Request Input`: `req-001 docs release checklist needs stricter broken-link acceptance criteria` - -## CANDIDATE-1 - -- Classification: `change-req` -- Target: `req-001` -- Value: High -- Risk: Medium -- Effort: Low -- Reason: - - 피드백이 기존 REQ acceptance criteria 강화로 닫힌다. - - 기존 batch/release trace를 유지한 채 REQ 상태를 재검증 대상으로 되돌릴 수 있다. -- Next Command: - - `/change-req req-001` - -## CANDIDATE-2 - -- Classification: `new-discovery` -- Target: `release feedback triage automation for maintainers` -- Value: Medium -- Risk: Low -- Effort: Medium -- Reason: - - release feedback를 다음 반복 후보로 자동 분류하는 기능은 기존 REQ 수정만으로 닫히지 않는다. - - 독립 Discovery로 추적하는 편이 범위와 성공 기준을 명확히 유지한다. -- Next Command: - - `/new-discovery release feedback triage automation for maintainers` - -## CANDIDATE-3 - -- Classification: `no-action` -- Target: `release note checklist for docs-only profile` -- Value: Low -- Risk: Low -- Effort: Low -- Reason: - - 현재 `req-001` 변경에 흡수 가능하므로 별도 반복으로 분리하지 않는다. \ No newline at end of file diff --git a/.stage-pilot/skills/CHANGELOG.md b/.stage-pilot/skills/CHANGELOG.md deleted file mode 100644 index 6048c0d..0000000 --- a/.stage-pilot/skills/CHANGELOG.md +++ /dev/null @@ -1,155 +0,0 @@ -# Changelog - -All notable changes to the StagePilot skill pack should be recorded in this file. - -## 2026-05-22 - -### run-batch-delivery `0.7.0` - -- added new orchestration skill `run-batch-delivery` -- defined a 6-step delivery chain for an existing batch: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` -- clarified that the skill resumes from the first incomplete delivery stage and stops at approval blockers instead of forcing progress -- clarified that release drafting remains a separate next step and is recommended, not auto-executed - -### draft-req `1.0.3` - -- bumped `draft-req` from `1.0.2` to `1.0.3` -- normalized the template path section to the same logical `stage-pilot/templates/...` + common resolution format used across the pack -- clarified source vs target path distinction for SRS index and REQ document generation - -### bootstrap-baseline `1.0.1` - -- bumped `bootstrap-baseline` from `1.0.0` to `1.0.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for bootstrap seed, index, and baseline document generation - -### new-discovery `1.0.1` - -- bumped `new-discovery` from `1.0.0` to `1.0.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for discovery document, discovery index, and baseline reference templates - -### draft-release `0.8.1` - -- bumped `draft-release` from `0.8.0` to `0.8.1` -- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths -- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) -- clarified source vs target path distinction for release document and release index generation - -### draft-batch `0.9.1` - -- bumped `draft-batch` from `0.9.0` to `0.9.1` -- added `When to use` and `Do not use when` guidance to separate batch creation from recommendation, approval, downstream drafting, and SDLC routing -- clarified that `draft-batch` operates only after people have selected the REQ set to include -- replaced the fixed batch template path wording with logical `stage-pilot/templates/batches/...` paths plus explicit resolution order -- clarified that `minor-change` is an input fast path while the persisted batch profile remains `batch-lite` -- added explicit source-to-target template mapping, source discovery recording rules, `Common Pitfalls`, and a `Verification Checklist` - -### suggest-batch-reqs `0.8.2` - -- bumped `suggest-batch-reqs` from `0.8.0` to `0.8.2` -- added `Do not use when` guidance to separate recommendation from REQ approval, direct batch drafting, minor-change fast paths, and top-level SDLC routing -- added a dedicated `사람 선택 규칙` section so batch candidate adoption stays explicitly human-decided -- strengthened Discovery input handling so linked REQs must be explicit and approved before recommendation proceeds -- added explicit candidate-level batch profile hints for `standard` vs `batch-lite`, including required reasoning -- added `Common Pitfalls` and a `Verification Checklist` - -### confirm-req `0.9.1` - -- bumped `confirm-req` from `0.9.0` to `0.9.1` -- added `Do not use when` guidance to separate approval from drafting, change management, implementation confirmation, and batch planning -- made approval prerequisites explicit, including blocker vs non-blocker handling for `Notes` and open questions -- clarified that approval `Change Log` entries are appended and not written for blocked REQs -- added a local `docs/srs/index.md` preservation rule so existing repository wording/format is updated in place rather than overwritten from template wording -- added `Common Pitfalls` and a `Verification Checklist` - -### draft-req `1.0.2` - -- bumped `draft-req` from `1.0.1` to `1.0.2` -- added `Do not use when` guidance to separate `draft-req` from discovery review, confirmation, change-req, and batch planning stages -- made the confirmed Discovery prerequisite explicit and documented the limited draft-only exception case -- added a `Common Pitfalls` section covering confirmation, NFR splitting, baseline gaps, path confusion, index updates, and source Discovery back-references -- replaced the old validation bullets with a `Verification Checklist` for operational verification after drafting - -### draft-req `1.0.1` - -- bumped `draft-req` from `1.0.0` to `1.0.1` -- replaced fixed template path references with logical template source paths under `stage-pilot/templates/...` -- added explicit template path resolution rules for workspace-local, vendor/subtree, and Hermes external installs -- clarified resolution priority: workspace-local -> vendor/subtree -> Hermes external -- clarified the distinction between template source paths and generated target paths under `docs/srs/` -- added validation checks for logical-path-to-physical-path resolution and source/target path confusion - -### confirm-discovery `0.9.1` - -- bumped `confirm-discovery` from `0.9.0` to `0.9.1` -- applied wording cleanup (`Skill` -> `skill`) for local style consistency -- clarified that `Confirmed By` is never auto-generated or inferred by AI -- clarified when to use `review-discovery` or `new-discovery` instead of `confirm-discovery` -- added baseline cross-check guidance for `docs/project-structure.md` and `docs/runtime-flows.md` -- added explicit pitfalls and a verification checklist -- clarified how to add a missing `docs/discovery/index.md` row while preserving register format - -### Metadata normalization - -Applied long-term management metadata across all StagePilot skills under `~/.stage-pilot/skills`. - -Changed for every skill: - -- added or normalized `author: Justin Ko` -- added `license: private` -- added `metadata.hermes.tags` -- added `metadata.hermes.related_skills` -- normalized frontmatter layout for long-term reuse - -### Version reclassification - -Assigned workflow-maturity-based semantic versions instead of a uniform initial version. - -#### Stable core (`1.0.0`) - -- `bootstrap-baseline` -- `new-discovery` -- `draft-req` - -#### Near-stable (initial classification) - -- `confirm-discovery` → later bumped to `0.9.1` -- `confirm-req` → `0.9.0` -- `draft-batch` → `0.9.0` - -#### Maturing workflow (`0.8.0`) - -- `change-req` -- `confirm-batch-verification` -- `confirm-release` -- `draft-batch-design` -- `draft-batch-planning` -- `draft-batch-verification` -- `draft-release` -- `review-discovery` -- `suggest-batch-reqs` - -#### Active evolution / orchestration (`0.7.0`) - -- `confirm-req-implemented` -- `run-batch-delivery` -- `run-batch-implementation` -- `run-sdlc` - -#### Early-support (`0.6.0`) - -- `capture-release-feedback` -- `suggest-next-discovery` - -### Notes - -Current intent of the version scale: - -- `0.6.x` — useful early-support skill, likely to evolve significantly -- `0.7.x` — execution/orchestration behavior still evolving -- `0.8.x` — maturing drafting/decision workflow -- `0.9.x` — near-stable, only moderate refinement expected -- `1.0.x` — stable core workflow skill diff --git a/.stage-pilot/skills/README.md b/.stage-pilot/skills/README.md deleted file mode 100644 index 78cdaca..0000000 --- a/.stage-pilot/skills/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# StagePilot Skills - -Personal StagePilot skill pack for Hermes external skills. - -This directory is configured as an external skill root and contains the reusable skills, shared conventions, and cross-skill metadata for the StagePilot SDLC workflow. - -## Purpose - -StagePilot organizes work into a document-driven flow around: - -- baseline bootstrap -- discovery -- requirements -- batch planning / design / implementation / verification -- release -- release feedback and next-discovery planning - -These skills are intended to be reusable across repositories and machines, with versioned frontmatter so changes can be tracked over time. - -## Installation / usage on another machine - -1. Copy or clone this directory to the target machine. -2. Point Hermes `config.yaml` external skills setting at `~/.stage-pilot/skills`. -3. Reload skills: - - `/reload-skills` in-session, or - - restart Hermes / start a new session. - -## Skill inventory - -### Stable core skills - -- `bootstrap-baseline` — `1.0.1` -- `new-discovery` — `1.0.1` -- `draft-req` — `1.0.3` - -These are treated as the most reusable, foundational StagePilot workflow skills. - -### Near-stable skills - -- `confirm-discovery` — `0.9.1` -- `confirm-req` — `0.9.1` -- `draft-batch` — `0.9.1` - -These are close to stable but may still get light structural refinement. - -### Maturing workflow skills - -- `change-req` — `0.8.0` -- `confirm-batch-verification` — `0.8.0` -- `confirm-release` — `0.8.0` -- `draft-batch-design` — `0.8.0` -- `draft-batch-planning` — `0.8.0` -- `draft-batch-verification` — `0.8.0` -- `draft-release` — `0.8.1` -- `review-discovery` — `0.8.0` -- `suggest-batch-reqs` — `0.8.2` - -These are structured and usable now, but are expected to evolve as StagePilot usage expands. - -### Active evolution / orchestration skills - -- `confirm-req-implemented` — `0.7.0` -- `run-batch-delivery` — `0.7.0` -- `run-batch-implementation` — `0.7.0` -- `run-sdlc` — `0.7.0` - -These involve orchestration or execution-heavy behavior and are expected to change more than drafting skills. - -### Early-support skills - -- `capture-release-feedback` — `0.6.0` -- `suggest-next-discovery` — `0.6.0` - -These are useful but still relatively early in the overall workflow maturity. - -## Versioning policy - -Semantic versioning is used loosely to describe workflow maturity: - -- `0.6.x` — useful early-support skill, likely to evolve significantly -- `0.7.x` — active execution/orchestration logic still evolving -- `0.8.x` — maturing drafting/decision workflow -- `0.9.x` — near-stable and expected to change lightly -- `1.0.x` — stable core skill suitable for broad reuse - -Practical interpretation: - -- patch bump (`x.y.Z`) → wording fixes, metadata tweaks, examples, minor clarifications -- minor bump (`x.Y.z`) → meaningful behavior/structure expansion without redefining the role of the skill -- major bump (`X.y.z`) → significant workflow or contract change - -## Metadata conventions - -Each StagePilot skill should include these frontmatter fields: - -- `name` -- `description` -- `version` -- `author: Justin Ko` -- `license: private` -- `metadata.hermes.tags` -- `metadata.hermes.related_skills` - -Optional but commonly retained: - -- `argument-hint` -- `user-invocable` - -## Recommended repository hygiene - -If this directory is tracked in git, recommended companions are: - -- `README.md` — this file -- `CHANGELOG.md` — top-level release notes for the skill pack -- commits for every meaningful skill change -- optional tags when you want to snapshot a known-good pack revision - -## Last metadata normalization - -- Date: 2026-05-22 -- Author normalized to: `Justin Ko` -- Version fields reclassified by workflow maturity -- Cross-skill tags and related-skill metadata added to all StagePilot skills diff --git a/.stage-pilot/skills/bootstrap-baseline/SKILL.md b/.stage-pilot/skills/bootstrap-baseline/SKILL.md deleted file mode 100644 index 8841863..0000000 --- a/.stage-pilot/skills/bootstrap-baseline/SKILL.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -name: bootstrap-baseline -description: "Use when: initializing StagePilot in a project before the first real Discovery, running /bootstrap-baseline to create baseline documents and active indexes, or backfilling missing docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, and docs/data-model.md without creating a Discovery unit." -version: 1.0.1 -author: Justin Ko -license: private -argument-hint: "예: 신규 저장소 baseline 초기화, StagePilot init, baseline 문서/인덱스 생성" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, bootstrap, baseline, documentation, sdlc] - related_skills: [new-discovery, run-sdlc] ---- - -# Purpose - -This skill initializes the StagePilot baseline outside the normal Discovery -> REQ -> Batch -> Release flow. - -`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, 그리고 active index 문서는 첫 real Discovery 전에 준비하는 bootstrap 산출물이다. 이 skill은 baseline과 index를 먼저 만들고, 첫 Discovery가 실제 제품/서비스 변경 주제로 시작되도록 만든다. - -greenfield 저장소처럼 읽을 코드나 설정이 아직 없으면, 이 skill은 사용자에게 최소 질문 세트를 묻고 `.stage-pilot/bootstrap/baseline.yaml` seed를 만든 뒤 baseline 문서를 렌더링한다. - -# When to use - -다음 상황에서 사용한다. - -- StagePilot을 새 프로젝트에 처음 적용한 직후 baseline 문서와 index를 만들 때 -- `/bootstrap-baseline` 또는 유사한 init 요청으로 baseline 초기화를 수행할 때 -- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md` 중 일부가 없어서 SDLC를 시작하기 전에 뼈대를 복구해야 할 때 -- 첫 real Discovery를 기능, 운영 문제, 기술 결정 같은 실제 변경 주제로 시작하고 싶을 때 - -다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. - -- 이미 진행 중인 Discovery/REQ/Batch/Release가 있고, baseline 변경 책임이 특정 active unit에 속하는 경우 -- 실제 제품/서비스 변경 주제가 이미 명확하여 Discovery 초안 생성이 우선인 경우 - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- 현재 저장소 루트와 디렉터리 구조 -- `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 존재 여부 -- `.stage-pilot/bootstrap/baseline.yaml` 존재 여부 -- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` 존재 여부 -- 템플릿의 논리 경로 - - `stage-pilot/templates/bootstrap/baseline-seed.yaml` - - `stage-pilot/templates/discovery/index.md` - - `stage-pilot/templates/srs/index.md` - - `stage-pilot/templates/batches/index.md` - - `stage-pilot/templates/releases/index.md` - - `stage-pilot/templates/project-structure.md` - - `stage-pilot/templates/runtime-flows.md` - - `stage-pilot/templates/interface-contract.md` - - `stage-pilot/templates/data-model.md` -- 현재 시각 (KST) - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `.stage-pilot/bootstrap/baseline.yaml`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` - -# Core Rules - -## 0. 단위 경계 - -- `bootstrap-baseline`은 Discovery, REQ, Batch, Release 중 어느 것도 아니다. -- 이 skill은 초기 운영 기준선을 만드는 bootstrap 단계다. -- 이 skill은 Discovery 문서를 생성하지 않는다. -- baseline 초기화가 끝난 뒤 첫 real Discovery는 실제 제품/서비스 변경, 운영 이슈, 기술 결정 같은 주제로 시작한다. - -## 1. 생성 대상 - -- 아래 경로가 없으면 생성한다. - - `.stage-pilot/bootstrap/baseline.yaml` - - `docs/discovery/index.md` - - `docs/srs/index.md` - - `docs/batches/index.md` - - `docs/releases/index.md` - - `docs/project-structure.md` - - `docs/runtime-flows.md` - - `docs/interface-contract.md` - - `docs/data-model.md` -- 이미 존재하는 파일은 임의로 덮어쓰지 않는다. -- 일부만 없는 경우에는 누락된 파일만 생성한다. - -## 1.5 최소 질문 세트 - -- 저장소 관찰만으로 baseline 핵심 정보가 부족하면 먼저 질문을 수행한다. -- 질문은 아래 다섯 개를 기본 세트로 사용한다. - - `project-summary` - - 질문: `이 프로젝트를 한 문장으로 설명하면 무엇인가?` - - 답변 형식: 한 문장 자유 텍스트 - - `primary-domain` - - 질문: `이 프로젝트의 주요 도메인은 무엇인가?` - - 답변 형식: 짧은 명사구 또는 분야명 - - `tech-stack` - - 질문: `계획 중인 주 언어, 프레임워크, 핵심 인프라는 무엇인가?` - - 답변 형식: 쉼표로 구분한 목록 - - `primary-runtime` - - 질문: `계획 중인 주 실행 형태는 무엇인가?` - - 답변 형식: `cli` | `api-service` | `web-app` | `worker` | `library` | `mixed` | `other` - - `primary-entrypoints` - - 질문: `대표 진입점 1~3개를 적어 달라.` - - 답변 형식: 줄마다 `name :: purpose` -- 아래 질문은 선택 입력이다. - - `planned-top-level-areas` - - 질문: `이미 알고 있는 top-level 경로가 있으면 적어 달라.` - - 답변 형식: 줄마다 `path :: responsibility` - - `interface-contracts` - - 질문: `대표 인터페이스 1~2개가 있으면 적어 달라.` - - 답변 형식: 줄마다 `name :: type :: actors :: purpose` - - `interface-inputs` - - 질문: `각 인터페이스의 대표 input을 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: input contract or invocation note` - - `interface-outputs` - - 질문: `각 인터페이스의 대표 output을 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: output contract or artifact` - - `interface-errors` - - 질문: `각 인터페이스의 대표 error를 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: error code or failure behavior` - - `core-entities` - - 질문: `핵심 엔티티 1~2개가 있으면 적어 달라.` - - 답변 형식: 줄마다 `name :: purpose` - - `persistence-backend` - - 질문: `주 persistence backend가 있으면 적어 달라.` - - 답변 형식: 짧은 backend 또는 storage 이름 - - `compatibility-rules` - - 질문: `인터페이스 호환성 규칙이 있으면 적어 달라.` - - 답변 형식: 줄마다 `interface-name :: rule` 또는 `* :: rule` - - `known-unknowns` - - 질문: `아직 미정인 핵심 항목이 있으면 적어 달라.` - - 답변 형식: 줄마다 항목 하나 -- 이미 읽을 수 있는 저장소 증거가 있으면 질문 수를 줄이고, seed의 비어 있는 값만 보충한다. -- `interface-contracts`, `interface-inputs`, `interface-outputs`, `interface-errors`, `core-entities`, `persistence-backend`, `compatibility-rules`가 비어 있더라도 bootstrap은 `runtime`, `primary-entrypoints`, `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 기본 초안을 유도해 seed를 보강할 수 있다. - -## 1.6 seed 파일 source-of-truth 규칙 - -- bootstrap 질문 결과와 저장소 관찰 결과의 합성 source of truth는 `.stage-pilot/bootstrap/baseline.yaml`이다. -- seed 파일은 active SDLC unit가 아니라 bootstrap 선언 파일이다. -- seed 파일이 있으면 baseline 문서 생성과 갱신 시 이를 우선 읽는다. -- seed 파일이 없고 저장소 증거가 충분하지 않으면 질문 없이 baseline 문서를 추정 생성하지 않는다. -- 저장소 관찰값과 사용자 답변이 함께 있으면 `capture_mode`는 `mixed`다. -- 저장소 관찰값만으로 충분하면 `capture_mode`는 `observed`다. -- 사용자 선언이 중심이고 저장소 관찰값은 보조면 `capture_mode`는 `declared`다. - -## 2. baseline 문서 작성 원칙 - -- `docs/project-structure.md`는 seed의 `project`, `stack`, `runtime`, `structure` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. -- `docs/runtime-flows.md`는 seed의 `runtime`과 `notes` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. -- `docs/interface-contract.md`는 seed의 `interfaces` 섹션을 먼저 반영하고, 값이 부족하면 `runtime`, `primary-entrypoints`, `known-unknowns`를 바탕으로 대표 외부 API, CLI, 이벤트, 파일 I/O 계약의 기본 초안을 구체화한다. -- `docs/data-model.md`는 seed의 `data_model` 섹션을 먼저 반영하고, 값이 부족하면 `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 핵심 엔티티, 관계, 상태, persistence 규칙의 기본 초안을 구체화한다. -- 입력만으로 알 수 없는 항목은 의미 있는 플레이스홀더로 남기되, 추론 가능한 구조 정보는 실제 문장으로 채운다. -- baseline 문서의 `Source Discovery / Batch`는 bootstrap 산출물임을 드러내는 값으로 채운다. -- baseline 문서는 `declared`, `observed`, `mixed` 중 어느 모드로 렌더링되었는지와 seed 경로를 드러내야 한다. - -## 3. index 작성 원칙 - -- index 문서는 템플릿을 기반으로 생성한다. -- 초기화 시점에는 register 본문만 만들고 실제 Discovery/REQ/Batch/Release 항목은 비워 둔다. -- index 생성은 active unit 생성으로 간주하지 않는다. - -## 4. 진행 중 unit가 있는 경우 - -- 진행 중인 Discovery/REQ/Batch/Release가 이미 있고 baseline 문서만 일부 비어 있다면, 먼저 현재 active unit가 그 변경을 소유해야 하는지 판정한다. -- 특정 active unit의 요구사항이나 설계/검증 근거로 baseline 변경이 필요한 경우에는 그 unit 안에서 처리한다. -- 단순 bootstrap 누락 복구이고 active unit 책임이 아니라면 `bootstrap-baseline`으로 누락 파일만 보강할 수 있다. - -# Execution Procedure - -1. 저장소에서 active docs 루트와 baseline 파일 존재 여부를 확인한다. -2. `.stage-pilot/bootstrap/baseline.yaml`이 없거나 핵심 필드가 비어 있으면 저장소 관찰로 채울 수 있는 값과 질문이 필요한 값을 분리한다. -3. 저장소 관찰만으로 부족하면 최소 질문 세트를 사용해 사용자 선언 입력을 수집한다. -4. `stage-pilot/templates/bootstrap/baseline-seed.yaml`에 해당하는 실제 template source를 해석한 뒤 `.stage-pilot/bootstrap/baseline.yaml`을 생성 또는 보강한다. -5. 누락된 디렉터리가 있으면 `docs/discovery`, `docs/srs`, `docs/batches`, `docs/releases`를 먼저 준비한다. -6. 필요한 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤, 누락된 index 파일을 각 템플릿으로 생성한다. -7. seed 파일과 저장소 상태를 반영해 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`를 생성 또는 보강한다. -8. 이미 존재하는 파일은 보존하고, 필요한 경우에만 누락 복구 또는 seed 반영 사실을 결과에 기록한다. -9. 완료 후 다음 단계로 `new-discovery`를 사용해 첫 real Discovery를 시작하도록 안내한다. - -# Output Expectations - -- 생성 또는 복구한 파일 목록 -- seed 파일 상태 (`created` | `preserved` | `updated`) -- baseline 문서 상태 (`created` | `preserved` | `missing-information`) -- index 문서 상태 (`created` | `preserved`) -- 첫 real Discovery에서 다뤄야 할 주제 후보 또는 다음 action - -# Validation - -- 생성한 파일 경로가 active docs 구조와 일치하는지 확인한다. -- `.stage-pilot/bootstrap/baseline.yaml`의 필수 필드가 비어 있지 않은지 확인한다. -- baseline 문서와 index에 남아 있는 플레이스홀더가 정말 사람 결정이 필요한 값인지 점검한다. -- baseline 문서의 `Project Summary`, `Primary Domain`, `Tech Stack`, `Primary Runtime`와 공통 계약 정보가 seed 및 저장소 관찰과 모순되지 않는지 확인한다. -- Discovery 문서를 생성하지 않았는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/capture-release-feedback/SKILL.md b/.stage-pilot/skills/capture-release-feedback/SKILL.md deleted file mode 100644 index 9be543f..0000000 --- a/.stage-pilot/skills/capture-release-feedback/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: capture-release-feedback -description: "Use when: recording post-release observations, running /capture-release-feedback with a REL ID, appending feedback and follow-up inputs to docs/releases/rel-XXX_*.md, or marking included batches as released after deployment feedback is captured." -version: 0.6.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, feedback, retrospective, sdlc] - related_skills: [draft-release, confirm-release, suggest-next-discovery] ---- - -# Purpose - -This skill records post-release observations, converts them into follow-up Discovery or REQ inputs, and closes the release with captured feedback. - -# Inputs - -- `REL-ID` 또는 release 경로 -- 운영 관찰 결과 -- 대상 release 문서 -- 포함 batch 목록 -- `docs/releases/index.md` -- `docs/batches/index.md` - -# Core Rules - -- 운영 관찰 결과는 사실과 후속 입력으로 분리해 기록한다. -- 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 구분한다. -- release feedback을 남긴 뒤 release 상태는 `feedback-captured`로 전환할 수 있다. -- 포함 batch 상태는 필요하면 `released`로 갱신한다. - -# Execution Procedure - -1. release 문서와 포함 batch를 읽는다. -2. 운영 관찰 결과를 release 문서의 Feedback Handoff 섹션에 반영한다. -3. 후속 Discovery 입력, 신규 REQ 입력, 기존 REQ 변경 후보를 분리해 기록한다. -4. release index 상태를 `feedback-captured`로 갱신하고, 포함 batch 상태를 `released`로 조정한다. - -# Validation - -- release 문서에 운영 관찰 결과와 `Discovery Input`, `REQ Input`, `Change Request Input`이 모두 남았는지 확인한다. -- release index 상태와 batch index 상태가 문서 본문과 일치하는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/change-req/SKILL.md b/.stage-pilot/skills/change-req/SKILL.md deleted file mode 100644 index 8b2036a..0000000 --- a/.stage-pilot/skills/change-req/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: change-req -description: "Use when: updating an existing requirement after approval or implementation, running /change-req with a REQ ID or file path, recording a change-request entry before overwriting a REQ, analyzing impacted batches/releases, or deciding whether an Implemented REQ must be downgraded and reverified." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 docs/srs/Documentation/req-001_minor-docs.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, change-management, documentation, sdlc] - related_skills: [draft-req, confirm-req, draft-batch] ---- - -# Purpose - -This skill applies a controlled change to an existing REQ by recording a new change-request entry before editing the requirement body, analyzing downstream delivery impact, and updating status/index state when the prior implementation is no longer valid. - -# When to use - -- `/change-req req-001`처럼 기존 REQ의 Requirement, Acceptance Criteria, Impacted Area를 수정해야 할 때 -- 운영 피드백이나 release feedback에서 기존 REQ 수정 필요가 확인됐을 때 -- `Implemented` 또는 `Approved` REQ가 더 이상 현재 구현/검증 증거와 맞지 않는지 판단해야 할 때 -- `docs/srs/index.md`의 REQ 상태와 실제 변경 영향 범위를 다시 동기화해야 할 때 - -# Inputs - -- 선택 입력 - - `REQ-ID` prefix - - REQ 파일 경로 -- 변경 요청 근거 - - 사용자 요청 - - release feedback - - batch verification 또는 implementation 결과 -- 대상 REQ 문서 -- `docs/srs/index.md` -- 관련 `docs/batches/index.md` -- 관련 `docs/releases/index.md` -- 영향이 의심되는 batch/release 문서들 - -# Core Rules - -## 1. 입력 해석 - -- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- 동일 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. -- REQ가 `Deprecated`면 기본적으로 새 변경 요청 대상으로 삼지 않는다. 복구가 필요하면 근거를 먼저 명시한다. - -## 2. 변경 요청 기록 규칙 - -- 기존 `Requirement`, `Acceptance Criteria`, `Impacted Area`를 덮어쓰기 전에 새 `Change Log` 항목을 추가한다. -- 새 변경 항목 ID는 `CHG-YYYYMMDD-NN` 형식을 사용하고 같은 날짜 내에서 순차 증가시킨다. -- 새 항목에는 최소한 아래를 모두 기록한다. - - `Change Summary` - - `Intent` - - `Acceptance Criteria Delta` - - `Impacted Area Delta` - - `Delivery Trace Delta` - - `Revalidation Impact` - - `Validation Plan` -- 과거 `Change Log` 항목은 삭제하거나 덮어쓰지 않는다. - -## 3. 상태 영향 규칙 - -- 변경이 기존 구현 또는 verification evidence를 무효화하지 않으면 현재 상태를 유지할 수 있다. -- `Implemented` REQ의 기존 구현/검증 근거가 깨지면 상태를 `Approved`로 되돌린다. -- 변경이 승인 전제 자체를 다시 검토하게 만들면 상태를 `Proposed`로 되돌릴 수 있다. -- 상태를 되돌릴 때는 이유를 `Revalidation Impact`와 `Change Log`에 함께 남긴다. -- 상태가 바뀌면 `docs/srs/index.md`의 Register와 `Recent Change Log Summary`도 함께 갱신한다. - -## 4. 영향 분석 규칙 - -- 관련 batch가 있으면 어떤 batch planning/design/verification이 다시 검토되어야 하는지 기록한다. -- 관련 release가 있으면 release 문서를 직접 덮어쓰지 말고 영향 범위와 후속 action을 REQ의 `Change Log`에 남긴다. -- 사람 판단이 필요한 범위 재정의, 우선순위 변경, 정책 변경은 임의 확정하지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 경로를 확정한다. -2. REQ 현재 상태와 최근 `Change Log`를 읽는다. -3. `docs/batches/index.md`, `docs/releases/index.md`, 관련 batch/release 문서를 읽어 현재 delivery trace를 요약한다. -4. 변경 요청 근거를 기존 구현/검증 상태와 비교한다. -5. 새 `Change Log` 항목을 먼저 추가하고 delta, trace, revalidation impact를 채운다. -6. 그 다음 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`)을 필요한 만큼 수정한다. -7. 상태 유지 또는 상태 되돌림 필요 여부를 결정해 REQ 본문과 `docs/srs/index.md`를 갱신한다. -8. `Recent Change Log Summary`에 최신 변경 항목을 반영한다. - -# Output Expectations - -- 대상 REQ 경로 -- 새 `Change Log` 항목 ID -- 변경 요약 -- 영향 받는 batch/release 목록 -- 재검증 필요 여부 -- 상태 유지 또는 상태 되돌림 결과 -- `docs/srs/index.md` 갱신 결과 - -# Common Pitfalls - -1. 활성 batch 안에서 계약/필드 순서 같은 외부 계약을 바꾸면서 REQ만 수정하고 delivery 산출물과 baseline 문서를 같이 재정렬하지 않는 실수 - - 예: persistence flat slot 순서, CTSP read/write 슬롯 순서, revision migration 시 기본값 채움 순서 변경. - - 이런 변경은 `change-req` 후에 source Discovery, 연결된 Approved REQ들, 현재 batch의 `planning/design/implementation/verification`, 그리고 `CTSP_PROTOCOL`/`CONFIGURATION`/`runtime-flows` 같은 기준 문서를 같은 변경 묶음으로 갱신해야 한다. - - verification evidence가 다시 필요해질 수 있으므로, 검증 전에는 batch를 `release-candidate`로 올리거나 REQ를 `Implemented`로 확정하지 않는다. - -# Validation - -- `Change Log` 새 항목이 REQ 본문 수정보다 앞선 시간순 근거로 남았는지 확인한다. -- `Acceptance Criteria Delta`, `Delivery Trace Delta`, `Revalidation Impact`가 모두 채워졌는지 확인한다. -- 상태 되돌림이 필요한 경우 REQ 문서와 `docs/srs/index.md` 상태가 함께 바뀌었는지 확인한다. -- 관련 batch/release 영향이 `Change Log`에 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/confirm-batch-verification/SKILL.md b/.stage-pilot/skills/confirm-batch-verification/SKILL.md deleted file mode 100644 index 6906e6d..0000000 --- a/.stage-pilot/skills/confirm-batch-verification/SKILL.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: confirm-batch-verification -description: "Use when: approving a batch verification result, running /confirm-batch-verification with a BAT ID, promoting docs/batches/ to release-candidate, or updating docs/batches/index.md after verification passes." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, verification, approval, sdlc] - related_skills: [draft-batch-verification, confirm-req-implemented, draft-release] ---- - -# Purpose - -This skill checks whether a batch verification document has enough evidence to release the batch and, if so, promotes the batch to `release-candidate`. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//verification.md` -- 포함된 REQ 문서들 -- `docs/batches/index.md` - -# Core Rules - -- verification에 미해결 blocker가 있으면 승인하지 않는다. -- verification에 미해결 blocker가 있으면 승인하지 않는다. -- 단, verification 문서에 명시적 `Human Approval Memo`가 있고 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거가 모두 적혀 있으면 그 메모가 명시적으로 수용한 blocker는 residual risk로 보고 승인할 수 있다. 이 경우 승인 결과에는 사람이 수용한 예외 범위를 함께 요약한다. -- 포함된 REQ의 acceptance criteria가 evidence와 연결돼야 한다. -- `batch-lite`는 design 문서 없이도 승인할 수 있지만, planning의 `Design Gate`가 design 불필요를 명시하고 verification이 구조 영향 없음 또는 baseline 영향 없음을 확인해야 한다. -- 승인 성공 시 batch status는 `release-candidate`가 된다. - -# Execution Procedure - -1. batch와 verification 문서를 읽는다. -2. 필요하면 planning과 design을 함께 읽어 profile과 구조 영향 여부를 확인한다. -3. Acceptance Mapping, Evidence, Blocking Issues를 점검한다. -4. 승인 가능하면 verification 상태와 batch index 상태를 갱신한다. -5. 승인 불가면 상태는 유지하고 blocker를 보고한다. -6. verification에 `Human Approval Memo`가 있으면, 메모가 수용한 blocker와 여전히 승인 불가한 blocker를 분리해서 판단한다. 사람이 수용한 항목만 남아 있다면 verification을 `approved`로 올리고 batch를 `release-candidate`로 승격할 수 있다. - -# Validation - -- 승인 성공인 경우 batch index 상태가 `release-candidate`인지 확인한다. -- 승인 보류인 경우 blocker가 명시됐는지 확인한다. -- `batch-lite` 승인 성공인 경우 design 부재가 검증 근거와 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/confirm-discovery/SKILL.md b/.stage-pilot/skills/confirm-discovery/SKILL.md deleted file mode 100644 index d322109..0000000 --- a/.stage-pilot/skills/confirm-discovery/SKILL.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -name: confirm-discovery -description: "Use when: running /confirm-discovery with dcy-xyz or a Discovery path, re-checking the current repository state against docs/discovery/*.md, filling AI-resolvable placeholders or inconsistencies, and promoting the Discovery to confirmed when it is ready for REQ drafting in the Requirements phase." -version: 0.9.1 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, approval, quality, sdlc] - related_skills: [new-discovery, review-discovery, draft-req] ---- - -# Purpose - -This skill revalidates an existing Discovery document against the current repository state, fills anything the AI can resolve from local evidence, and promotes the document to `confirmed` only when REQ drafting handoff has no remaining blockers. - -이 skill의 결과는 아래 두 경우로 나뉜다. - -- 확인 결과 승인 가능이면 대상 Discovery 문서를 `confirmed`로 전환하고 `docs/discovery/index.md`의 해당 행 상태를 함께 갱신한다. -- 승인까지는 불가하더라도 AI가 근거 있게 채울 수 있는 항목이 있으면 문서를 먼저 보강한다. 단, 이 경우 `confirmed` 전환과 REQ drafting 준비 플래그 변경은 하지 않는다. - -# When to use - -다음 상황에서 사용한다. - -- `/confirm-discovery dcy-xyz` 형태로 기존 Discovery를 최종 확인해야 할 때 -- `docs/discovery/` 아래 Discovery 초안 문서를 REQ 초안 작성 가능 상태로 승격해야 할 때 -- 현재 저장소 구현 상태를 다시 확인해 Discovery 본문의 사실 관계를 정정하거나 보강해야 할 때 -- 문서 안의 플레이스홀더 중 AI가 근거 있게 채울 수 있는 항목을 먼저 정리한 뒤 승인 가능 여부를 판정해야 할 때 -- `docs/discovery/index.md`의 상태가 실제 Discovery 상태와 일치하는지 함께 맞춰야 할 때 - -다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. - -- Discovery 품질 점검, 누락 점검, OQ 후보 정리만 먼저 하고 싶을 때 - - 이 경우 `review-discovery`를 사용한다. -- 현재 문서가 사실상 새 Discovery 생성 또는 대규모 범위 재정의를 필요로 할 때 - - 이 경우 `new-discovery`로 새 주기를 시작하거나 기존 Discovery를 재작성해야 한다. -- 승인자 지정, 정책 확정, 외부 확인 결과처럼 사람 판단 또는 외부 근거가 먼저 필요한 상태일 때 - - 이 경우 confirm 없이 보류하고 필요한 결정을 먼저 해소한다. - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- Discovery 식별자 - - `dcy-001` 같은 축약 ID - - `dcy-001_20260424_topic-slug` 같은 전체 Discovery ID - - `docs/discovery/.md` 같은 직접 경로 -- Discovery 문서 본문 -- `docs/discovery/index.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) -- 현재 저장소 구현 상태 - - 관련 소스 파일 - - 설정 파일 - - 관련 문서 -- 현재 시각 (KST) - -# Core Rules - -## 1. 입력 해석 규칙 - -- 입력이 `dcy-001`처럼 prefix만 주어지면 `docs/discovery/` 아래에서 해당 prefix로 시작하는 문서를 찾는다. -- 입력이 전체 Discovery ID면 `docs/discovery/.md`를 직접 찾는다. -- 입력이 파일 경로면 해당 경로를 그대로 사용한다. -- 매칭 결과가 0개면 자동 진행하지 않고 사용자에게 입력을 재확인하도록 알린다. -- 같은 prefix에 대해 여러 문서가 매칭되면 임의 선택하지 않고 후보 목록을 보여 준 뒤 사용자 확인을 요청한다. -- `.stage-pilot/` 경로는 탐색 대상에서 제외한다. - -## 2. 현재 구현 상태 재확인 규칙 - -- 현재 구현 상태 확인은 요구사항이 이미 모두 구현되었는지 검사하는 절차가 아니다. -- 이 확인의 목적은 Discovery의 `현재 상태`, `영향 범위`, `성공 기준`, `체크 항목`, `사용자 결정 필요 항목`이 저장소 현실과 맞는지 검증하는 것이다. -- `docs/project-structure.md`, `docs/runtime-flows.md`가 존재하면 Discovery의 구조 설명, 영향 범위, runtime flow 설명이 baseline과 모순되지 않는지도 함께 확인한다. -- 구현 부재 자체는 차단 사유가 아니다. Discovery가 애초에 신규 작업을 정의하는 문서라면 코드가 없는 상태도 정상일 수 있다. -- 차단이 되는 경우는 아래와 같다. - - 문서가 이미 존재한다고 적은 구현이 실제 저장소에는 없어서 본문 사실 관계가 틀린 경우 - - 저장소 현실이 문서의 핵심 범위, 사용자 시나리오, 성공 기준을 다시 써야 할 정도로 바뀐 경우 - - 문서의 남은 결정 항목이 여전히 사람 판단을 필요로 하는데 승인 상태로 올리려는 경우 - -## 3. 승인 전 보강 규칙 - -승인 판정 전에 먼저 아래 보강 절차를 수행한다. - -### 3-1. 일관성 검토 - -- 용어 혼용, 범위 충돌, 중복 서술, 번호 누락을 확인한다. -- `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 확인한다. -- 문장만 다르고 같은 의미를 반복하는 부분은 간결하게 정리한다. -- 문법, 오타, 링크 형식, 체크리스트 표현을 함께 정리한다. - -### 3-2. 증거 기반 보강 - -- Discovery 본문에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계를 읽고 키워드를 추출한다. -- 키워드를 바탕으로 저장소에서 관련 파일, 설정, 문서를 탐색한다. -- 탐색 결과는 최소한 아래 판단으로 정리한다. - - 이미 존재함 - - 일부 존재함 - - 아직 없음 - - 문서와 모순됨 -- AI가 아래 근거로 확정 가능한 항목은 실제 문장으로 치환한다. - - 같은 Discovery 문서 안 다른 섹션에 이미 답이 있는 항목 - - 현재 저장소 파일 구조, 설정, 문서에서 직접 확인 가능한 항목 - - 승인 행위 시각처럼 현재 실행 시점에 자동 확정 가능한 항목 -- 아래 항목은 추론만으로 확정하지 않는다. - - 정책 선택 - - 우선순위 승인 - - 외부 시스템 또는 외부 데이터 확인 결과 - - 승인자 지명처럼 사람 책임을 수반하는 결정 - - `Confirmed By`처럼 승인 책임 주체를 명시하는 값 - -### 3-3. 플레이스홀더 분류 기준 - -- A. 승인 시점 자동 채움 - - 예: `{{DATA_CONFIRMED_AT_KST:...}}`, `{{CONFIRMED_AT_KST:...}}` - - 승인 성공 시 현재 시각으로 채운다. -- B. 증거 기반 해소 가능 - - 문서 다른 섹션, 저장소 구조, 이미 확정된 값으로 해소 가능한 플레이스홀더다. - - 승인 성공 여부와 무관하게 먼저 채운다. -- C. 사람 결정 또는 외부 확인 필요 - - `{{DECIDE_*}}`, `{{CONFIRM_*}}`, `{{DATA_*}}` 중 아직 문서와 저장소 근거만으로 해소되지 않는 항목이다. - - 승인 차단 대상이다. - -### 3-4. 책임자 지정과 저장소 범위 DATA 처리 기준 - -- `REQ/검토 책임자 지정`, `구현/검토 책임자 지정`, `해소 책임자 지정`처럼 사람 책임 배정이 필요한 항목은 AI가 자동으로 채우지 않는다. -- 이런 항목이 Discovery의 범위/정책/성공 기준 자체를 결정하는 질문이 아니라 이후 REQ 또는 batch planning 단계의 담당자 할당 문제라면, `# 10. 사용자 결정 필요 항목 요약`에 남겨 Discovery confirm을 가로막는 질문으로 유지하지 말고 `# 8. REQ로 넘기기 전 확인 체크` 또는 `# 11. Discovery Freeze`의 보류 사유로 정리한다. -- 다만 사람이 이미 대화에서 승인자/책임자/범위 결정을 명시해 주었고, 그 값이 문서에 직접 반영 가능한 형태라면 AI는 그 사람 결정을 문서에 옮길 수 있다. 이 경우에도 AI가 새 값을 추정하면 안 되고, 사용자가 제공한 사람 이름/결정만 그대로 기록해야 한다. -- 저장소 내부에서 확인 가능한 DATA 항목은 repo-wide consumer 탐색으로 먼저 해소를 시도한다. 예를 들어 특정 명령의 응답 길이에 의존하는 호스트 도구/스크립트 목록이 필요한 경우, 관련 토큰(`VISIONCFG`, `visioncfg` 등)으로 코드/문서/QA 스크립트 전체를 검색해 in-repo 소비처를 먼저 열거한다. -- 측정 경로와 검증 방법은 저장소 근거로 이미 확정됐지만 실제 수치가 구현/운영 단계에서만 수집 가능한 경우, 그 DATA 항목이 REQ 초안 작성 자체를 막는지 분리해서 판단한다. 대표 검증셋, 범위, 성공 기준, 사람 승인자가 확정돼 있으면 runtime baseline 수치 같은 운영 데이터는 REQ 단계 계측 항목으로 넘기고 confirm을 진행할 수 있다. -- 이 과정에서 저장소 안에서 길이 의존 구현이 확인되지 않으면 `현 저장소 기준 확인된 소비처는 없음`처럼 범위를 한정해 정리할 수 있다. 단, 저장소 밖 배포 도구나 외부 시스템까지 없다고 확대 해석하지 않는다. - -- 승인 판정 직전에는 A와 B만 남아 있어야 한다. -- B로 분류한 항목을 채운 뒤에도 `# 1`부터 `# 10` 사이에 C 유형 플레이스홀더가 남아 있으면 승인하지 않는다. -- 단, `DATA_*` 성격의 항목이 "REQ 또는 delivery 단계에서 계측·확정할 운영 데이터"임이 문서 안에 명시되고, Discovery 본문만으로도 FR/NFR, 범위, 성공 기준, 검증 시나리오를 REQ 초안으로 내리기에 충분하다면 confirm 차단 사유로 유지하지 않는다. 이런 경우에는 `# 8. REQ로 넘기기 전 확인 체크`와 `# 10. 사용자 결정 필요 항목 요약`에 "REQ 단계 계측/확정" 또는 동등한 표현으로 넘기고, Discovery Freeze rationale에도 비차단 운영 데이터라는 점을 적는다. - -## 4. 최종 승인 게이트 - -아래 조건을 모두 만족해야 `confirmed`로 전환한다. - -- 문서 상태가 아직 `confirmed`가 아니어야 한다. -- B 유형 보강 이후 C 유형 플레이스홀더가 0개여야 한다. -- `# 6. 리스크/가정 목록`의 오픈 질문에 `상태: Open` 항목이 없어야 한다. -- `# 8. REQ로 넘기기 전 확인 체크`에 남아 있는 미완료 항목이 있다면, 그 항목이 REQ 초안 작성을 막지 않는다고 문서에 명시되어 있어야 한다. 그렇지 않으면 차단한다. -- `# 10. 사용자 결정 필요 항목 요약`에 남은 항목이 모두 `없음`이거나, 문서 안에서 이미 답이 반영되어 제거 가능해야 한다. -- `# 11. Discovery Freeze`에 `Confirmed By`가 비어 있지 않아야 한다. -- 현재 저장소 상태가 Discovery의 핵심 범위나 요구사항을 다시 정의해야 할 정도로 모순되지 않아야 한다. -- runtime budget baseline, 허용 상한, 운영 계측값처럼 구현 전에는 원천적으로 확정되지 않는 수치가 남아 있더라도, 그 값이 REQ의 acceptance/verification에서 수집할 evidence로 이미 위치가 정리돼 있으면 confirm 차단 사유로 과도하게 남기지 않는다. - -`Confirmed By` 관련 규칙: - -- `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 의미한다. -- AI는 `Confirmed By`를 새로 생성하거나 추정해서 채우지 않는다. -- `Confirmed By`가 비어 있으면 승인 차단 사유로 유지한다. - -다음 경우는 `confirmed` 전환 대신 보류한다. - -- Discovery 초안이 아니라 사실상 새 Discovery 생성 또는 대규모 범위 재정의가 필요한 경우 -- 확인 과정에서 FR/NFR, In Scope/Out of Scope, 성공 기준을 실질적으로 다시 써야 하는 경우 -- 승인자 또는 필수 결정 사항이 여전히 문서 밖 사람 판단에 의존하는 경우 - -## 5. 허용되는 수정 범위 - -이 skill은 아래 수정만 수행한다. - -- AI가 근거 있게 채울 수 있는 플레이스홀더 치환 -- 현재 저장소 상태에 맞춘 사실 관계 보정 -- 표현, 번호, 체크리스트, 링크의 일관성 정리 -- 실제 문서 변경이 있었다면 `마지막 갱신 시각(KST)` 갱신 -- 승인 가능 시 아래 상태 전환 수행 - - `# 0. 문서 상태`의 `상태`를 `confirmed`로 변경 - - `# 11. Discovery Freeze`의 `Handoff Decision`을 `승인`으로 변경 - - `# 11. Discovery Freeze`의 `Ready for REQ Drafting`을 `true`로 변경 - - `Confirmed At (KST)`를 현재 시각으로 채움 - - `docs/discovery/index.md`의 해당 Discovery 행 상태를 `confirmed`로 갱신 - -단, 아래 항목은 자동으로 채우지 않는다. - -- `Confirmed By` - - 사람 승인자 또는 사람 책임 주체가 이미 문서에 명시되어 있어야 한다. - - 비어 있으면 승인 전환 없이 보류한다. - -아래 상황이면 상태 전환 없이 보강만 하고 종료한다. - -- C 유형 플레이스홀더가 남아 있는 경우 -- 오픈 질문 또는 사용자 결정 항목이 남아 있는 경우 -- 승인 자체보다 먼저 Discovery 내용 재정비가 필요한 경우 - -# Repository Exploration - -현재 구현 상태를 다시 확인할 때는 아래 순서로 저장소를 탐색한다. - -1. Discovery 문서에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계 키워드를 뽑는다. -2. 키워드 기준으로 관련 소스, 설정, 문서를 찾는다. - - `docs/project-structure.md`, `docs/runtime-flows.md`가 있으면 baseline 참조 문서로 함께 읽는다. -3. 아래 항목은 기본적으로 제외한다. - - 테스트 파일 - - 의존성 잠금 파일 - - `.stage-pilot/` 하위 파일 -4. 탐색 결과를 아래 형태로 정리한다. - - 관련 파일 경로 목록 - - 이미 구현된 내용 - - 일부만 구현된 내용 - - 아직 없는 내용 - - 문서와 모순되는 내용 - - baseline 문서와 일치하는 내용 / 충돌하는 내용 -5. 탐색 결과는 Discovery 보강과 승인 게이트 판단에만 사용한다. - -# Execution Procedure - -다음 순서대로 작업한다. - -1. 입력에서 Discovery 문서 경로를 확정한다. -2. 대상 Discovery 문서와 `docs/discovery/index.md`를 읽는다. -3. Discovery의 핵심 범위, FR/NFR, 오픈 질문, 체크리스트, 사용자 결정 필요 항목을 추출한다. -4. 위 `Repository Exploration` 규칙에 따라 현재 저장소 상태를 확인한다. -5. 문서 전체를 검토해 일관성 오류와 플레이스홀더를 나열한다. -6. B 유형 플레이스홀더와 명백한 사실 관계 오류를 먼저 수정한다. -7. 문서 내용이 바뀌었다면 `마지막 갱신 시각(KST)`를 현재 시각으로 갱신한다. -8. 다시 한 번 `최종 승인 게이트`를 평가한다. -9. 게이트를 통과하면 A 유형 플레이스홀더를 채우고 문서를 `confirmed`로 전환한다. -10. 게이트를 통과하면 `docs/discovery/index.md`의 해당 행 상태도 `confirmed`로 갱신한다. -11. `docs/discovery/index.md`에 해당 Discovery 행이 없으면 현재 문서 메타데이터를 기준으로 기존 register/header 형식과 열 순서를 유지한 채 행을 추가한다. - - 최소한 Discovery ID, 제목/이슈명, 상태, 링크, 마지막 갱신 시각에 해당하는 값을 채운다. - - 표가 아닌 다른 register 형식이면 그 형식을 그대로 따른다. -12. 게이트를 통과하지 못하면 상태는 유지하고, 보강된 내용과 남은 차단 항목만 보고한다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 대상 `DISCOVERY_ID`와 문서 경로 -- 현재 저장소 상태 재확인 결과 요약 -- AI가 자동으로 채운 항목 목록 -- 남겨 둔 항목 목록과 남긴 이유 -- 최종 판정 - - `confirmed 전환 완료` - - `보강만 수행, 승인 보류` - - `문서 변경 없이 승인 보류` -- `docs/discovery/index.md` 갱신 결과 -- REQ 초안 작성으로 넘기기 전에 남은 작업 요약 - -# Common Pitfalls - -1. 구현이 아직 없다는 이유만으로 승인 차단하기 - - 신규 작업을 정의하는 Discovery라면 구현 부재는 정상일 수 있다. 차단 사유는 문서와 저장소 사실 관계 모순 또는 사람 결정 미해소여야 한다. - -2. `Confirmed By`를 AI가 추정해서 채우기 - - `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 뜻한다. AI가 새로 만들거나 추정하면 안 된다. - -3. 사실상 새 Discovery가 필요한 문서를 confirm으로 밀어 올리기 - - 확인 과정에서 FR/NFR, 범위 경계, 성공 기준을 다시 써야 한다면 confirm보다 재작성 또는 새 Discovery 판단이 우선이다. - -4. `review-discovery`에서 할 품질 점검과 `confirm-discovery`의 승인 게이트를 혼동하기 - - 단순 누락 점검과 구조 품질 확인이 목적이면 먼저 `review-discovery`를 사용한다. - -5. Discovery 본문만 바꾸고 index 또는 freeze 필드를 함께 확인하지 않기 - - 승인 완료 시 본문 상태, Freeze 필드, `docs/discovery/index.md` 상태가 함께 일치해야 한다. - -# Verification Checklist - -완료 전에 아래를 확인한다. - -- [ ] 대상 경로가 `docs/discovery/.md` 형식을 만족하는지 -- [ ] 대상 Discovery 문서가 실제로 존재하는지 -- [ ] `docs/project-structure.md`, `docs/runtime-flows.md`가 있다면 필요한 baseline cross-check가 반영되었는지 -- [ ] 보강 후에도 C 유형 플레이스홀더와 `상태: Open` 오픈 질문이 정확히 식별되었는지 -- [ ] 승인 완료인 경우 `Confirmed By`가 사람에 의해 채워져 있고, 문서 상태와 `docs/discovery/index.md` 상태가 모두 `confirmed`인지 -- [ ] 승인 보류인 경우 `confirmed`, `승인`, `true` 같은 승인 전환 값이 잘못 들어가지 않았는지 -- [ ] 문서 본문 보강이 새로운 요구사항 생성이나 범위 재정의로 번지지 않았는지 -- [ ] 결과 보고에 자동 처리 항목과 차단 항목이 모두 포함되는지 \ No newline at end of file diff --git a/.stage-pilot/skills/confirm-release/SKILL.md b/.stage-pilot/skills/confirm-release/SKILL.md deleted file mode 100644 index c062437..0000000 --- a/.stage-pilot/skills/confirm-release/SKILL.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: confirm-release -description: "Use when: approving a release plan, running /confirm-release with a REL ID or release path, promoting docs/releases/rel-XXX_*.md from draft to confirmed, or validating rollout and rollback readiness before deployment." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, approval, quality, sdlc] - related_skills: [draft-release, capture-release-feedback, suggest-next-discovery] ---- - -# Purpose - -This skill validates a release document and promotes it to `confirmed` only when rollout, rollback, and verification checks are ready. - -# Inputs - -- `REL-ID` 또는 release 경로 -- 대상 release 문서 -- `docs/releases/index.md` -- 포함 batch 문서들 - -# Core Rules - -- Included Batch가 모두 release-candidate여야 한다. -- Rollout Plan, Rollback Plan, Verification Checklist가 비어 있으면 승인하지 않는다. -- release profile에 맞는 검증 항목이 비어 있으면 승인하지 않는다. -- `docs-only`는 링크/렌더링/패키징 검증으로 충분하며 health endpoint를 요구하지 않는다. -- `tooling`은 설치/업데이트/CLI 또는 스크립트 smoke 결과가 있어야 한다. -- `app-service`는 health, 로그, smoke 또는 동등한 운영 확인 근거가 있어야 한다. -- 승인 성공 시 release 상태를 `confirmed`로 바꾸고 index를 갱신한다. - -# Execution Procedure - -1. release 문서와 포함 batch 상태를 확인한다. -2. rollout, rollback, verification 준비도와 release profile의 필수 검증 항목을 점검한다. -3. 게이트 통과 시 release 상태와 index를 `confirmed`로 갱신한다. -4. 미통과 시 상태는 유지하고 blocker를 보고한다. - -# Validation - -- 승인 성공인 경우 release 문서와 index 상태가 모두 `confirmed`인지 확인한다. -- 승인 보류인 경우 누락 항목이 명시됐는지 확인한다. -- profile별 검증 항목이 release 문서의 선택된 profile과 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/confirm-req-implemented/SKILL.md b/.stage-pilot/skills/confirm-req-implemented/SKILL.md deleted file mode 100644 index 93ad197..0000000 --- a/.stage-pilot/skills/confirm-req-implemented/SKILL.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: confirm-req-implemented -description: "Use when: confirming that approved requirements have implementation and verification evidence, running /confirm-req-implemented with a REQ ID, BAT ID, or file path, promoting docs/srs//req-XXX_.md from Approved to Implemented, or updating docs/srs/index.md after batch verification passes." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 bat-001 또는 docs/srs/Interface/req-001_picker-cli-run.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, implementation, validation, sdlc] - related_skills: [draft-batch-verification, confirm-batch-verification, draft-release] ---- - -# Purpose - -This skill validates that an approved requirement has implementation and verification evidence, then promotes it to `Implemented` only when every acceptance criterion is traceable to delivery evidence. - -`Implemented`는 배포 완료 자체를 뜻하지 않는다. 기본 기준은 구현과 검증 증거가 확보된 상태이며, 일반적으로 `release-candidate` 또는 `released` batch의 verification 결과를 근거로 삼는다. - -# When to use - -- `/confirm-req-implemented req-001`처럼 특정 REQ를 `Implemented`로 전환해야 할 때 -- `/confirm-req-implemented bat-001`처럼 batch verification 이후 포함 REQ 상태를 동기화해야 할 때 -- `/confirm-req-implemented`처럼 입력 없이 현재 저장소의 `Approved` REQ 중 구현 완료로 승격 가능한 대상을 일괄 점검해야 할 때 -- `docs/srs/index.md`의 REQ 상태가 실제 batch verification evidence와 어긋나 있을 때 - -# Inputs - -- 선택 입력 - - `REQ-ID` prefix - - REQ 파일 경로 - - `BAT-ID` prefix - - batch 경로 -- 입력이 없으면 `docs/srs/**/req-*.md` 전체에서 `Status: Approved` REQ를 후보로 찾는다. -- 대상 REQ 문서 또는 대상 batch 문서 -- `docs/srs/index.md` -- `docs/batches/index.md` -- 관련 `docs/batches//index.md` -- 관련 `docs/batches//verification.md` -- 필요하면 관련 `docs/batches//implementation.md` - -# Core Rules - -## 1. 입력 해석 - -- `req-001` 같은 prefix가 주어지면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- `bat-001` 같은 prefix가 주어지면 `docs/batches/` 아래 matching batch를 찾고, 포함 REQ 중 `Approved` 상태만 후보로 삼는다. -- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 읽고, 그중 `Status: Approved` 문서를 후보로 삼는다. -- 같은 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. -- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 게이트로 평가한다. - -## 2. 구현 완료 전환 게이트 - -- 아래 조건을 모두 만족해야 `Implemented`로 전환한다. - - 대상 REQ의 현재 `Status`가 `Approved`다. - - 연결된 batch가 `release-candidate` 또는 `released` 상태다. - - batch `verification.md`에 해당 REQ의 `Acceptance Mapping`이 존재한다. - - REQ의 모든 `Acceptance Criteria`가 verification evidence와 연결된다. - - batch verification의 `Blocking Issues`가 이 REQ를 미통과 상태로 남기지 않는다. -- release 배포 완료는 필수 조건이 아니다. 구현과 검증 evidence가 충분하면 `release-candidate` 상태에서도 `Implemented` 전환이 가능하다. -- 연결된 batch를 찾지 못하거나 evidence가 부족하면 상태를 바꾸지 않는다. -- 하나의 REQ가 여러 batch에 걸쳐 있고 어떤 batch를 근거로 삼아야 할지 명확하지 않으면 임의로 전환하지 않는다. - -## 3. 허용되는 수정 - -- AI가 근거 있게 정리할 수 있는 오타, 링크, 표현 불일치는 보강할 수 있다. -- 전환 성공 시 아래 변경을 수행한다. - - REQ 문서의 `Status`를 `Implemented`로 변경 - - `Change Log`에 구현 완료 전환 기록 추가 - - `docs/srs/index.md`의 해당 Register 행 상태를 `Implemented`로 갱신 - - 필요하면 `Recent Change Log Summary`를 갱신 -- evidence 자체를 새로 만들어 내거나, batch verification의 blocker를 임의 해소하지 않는다. -- 사람 판단이 필요한 범위 변경, acceptance criteria 재정의, batch 재매핑은 임의로 확정하지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 하나, REQ 목록, 또는 batch 경로를 확정한다. -2. batch 입력인 경우 batch `index.md`에서 Included REQ를 읽고 `Approved` REQ만 후보로 만든다. -3. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Approved` 문서를 후보 목록으로 만든다. -4. 각 후보 REQ에 대해 `docs/batches/index.md`와 batch 문서를 읽어 연결된 batch를 찾는다. -5. 연결된 batch의 `verification.md`에서 REQ acceptance mapping, evidence, blocking issue를 점검한다. -6. 게이트를 통과한 REQ만 `Implemented`로 전환하고 `Change Log`, `docs/srs/index.md`를 갱신한다. -7. 게이트를 통과하지 못한 REQ는 상태를 유지하고 남은 blocker를 수집한다. -8. 입력이 batch 또는 무인자 일괄 모드인 경우, 성공 목록과 보류 목록을 분리해 보고한다. - -# Output Expectations - -- 단일 입력 모드 - - 대상 REQ 또는 batch 경로 - - 구현 완료 전환 성공 여부 - - 사용한 근거 batch와 verification evidence 요약 - - 자동 보강한 항목 목록 - - 남은 blocker 또는 미해결 항목 - - `docs/srs/index.md` 갱신 결과 -- batch/무인자 일괄 모드 - - 검토한 REQ 전체 목록 - - `Implemented` 전환 성공한 REQ 목록 - - 전환 보류한 REQ 목록과 각 blocker - - `docs/srs/index.md` 갱신 결과 - -# Validation - -- 전환 성공인 경우 REQ 문서와 `docs/srs/index.md` 상태가 모두 `Implemented`인지 확인한다. -- 전환 보류인 경우 상태가 잘못 바뀌지 않았는지 확인한다. -- 사용한 verification evidence가 실제 REQ acceptance criteria와 연결되는지 확인한다. -- batch 입력 모드에서는 batch에 포함된 `Approved` REQ만 처리되었는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/confirm-req/SKILL.md b/.stage-pilot/skills/confirm-req/SKILL.md deleted file mode 100644 index 38b0486..0000000 --- a/.stage-pilot/skills/confirm-req/SKILL.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -name: confirm-req -description: "Use when: reviewing a proposed REQ for approval, running /confirm-req with a REQ ID or file path, running /confirm-req without arguments to review all unapproved REQs, promoting docs/srs//req-XXX_.md from Proposed to Approved, updating docs/srs/index.md after approval, or when the user asks in Korean to '요구 사항 명세를 검토하고 승인해줘', '요구사항 명세를 검토하고 승인해줘', 'REQ를 검토하고 승인해줘', or '승인되지 않은 전체 req를 검토하고 승인해줘'." -version: 0.9.1 -author: Justin Ko -license: private -argument-hint: "예: req-001 또는 docs/srs/Interface/req-001_picker-cli-run.md 또는 인자 없이 /confirm-req" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, approval, quality, sdlc] - related_skills: [draft-req, change-req, draft-batch] ---- - -# Purpose - -This skill validates a proposed requirement document and promotes it to `Approved` only when it is ready to be used as delivery input. - -# When to use - -- `/confirm-req req-001`처럼 REQ 승인 게이트를 통과시켜야 할 때 -- `/confirm-req`처럼 입력 없이 승인되지 않은 전체 REQ를 일괄 검토하고 승인 가능한 항목만 승격해야 할 때 -- REQ 문서의 필수 필드와 Acceptance Criteria 충족 여부를 다시 확인해야 할 때 -- `docs/srs/index.md` 상태를 REQ 문서와 맞춰야 할 때 - -## Do not use when - -- REQ가 아직 실질적으로 초안 수준이라 먼저 새로 정리하거나 다시 써야 할 때 (`draft-req`가 더 적절함) -- 기존 `Approved` 또는 `Implemented` REQ의 Requirement, Acceptance Criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) -- 승인된 REQ를 구현/검증 evidence 기준으로 `Implemented`로 전환하려는 단계일 때 (`confirm-req-implemented`가 더 적절함) -- 여러 `Approved` REQ를 delivery batch로 묶고 planning/design 단계로 넘기려는 시점일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) - -# Inputs - -- 선택 입력: REQ 식별자 - - `req-001` 같은 prefix - - 전체 REQ 파일 경로 -- 입력이 없으면 `docs/srs/**/req-*.md` 전체를 스캔해 승인되지 않은 REQ를 대상으로 삼는다. -- 대상 REQ 문서 또는 대상 REQ 문서 목록 -- `docs/srs/index.md` - -# Core Rules - -## 0. 승인 전제 - -- 대상 REQ의 현재 `Status`는 기본적으로 `Proposed`여야 한다. -- REQ는 delivery input으로 넘길 수 있을 만큼 구체적이어야 하며, 단순 질문 목록이나 TODO 모음 수준이면 승인하지 않는다. -- source Discovery 또는 상위 변경 맥락과 명백히 모순되는 REQ는 승인하지 않는다. -- `Notes`에 남은 항목은 승인 blocker인지, 승인 후 후속 작업으로 남길 수 있는 메모인지 구분해서 판단한다. -- `Priority`, `Owner`, 핵심 범위, 승인 판단에 영향을 주는 open question이 비어 있거나 사람 결정을 필요로 하면 승인하지 않는다. -- 다만 승인 이후 batch planning에서 더 구체화 가능한 비핵심 운영 메모는 `Notes`에 남아 있어도 승인 blocker가 아닐 수 있다. - -## 1. 입력 해석 - -- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. -- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 찾고, 그중 `Status: Proposed`인 문서를 승인 후보로 삼는다. -- 후보가 0개면 진행하지 않는다. -- 후보가 여러 개면 임의 선택하지 않는다. -- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 승인 게이트로 평가한다. - -## 2. 승인 게이트 - -- 아래 항목이 모두 있어야 승인 가능하다. - - `Status`, `Type`, `Priority`, `Owner` - - `Intent` - - `Requirement` - - 구체적인 `Acceptance Criteria` - - `Impacted Area` - - `Change Log` -- 미해결 placeholder, 사람 결정 필요 메모, blocker가 남아 있으면 승인하지 않는다. -- `Status`가 이미 `Approved`, `Implemented`, `Deprecated`이면 중복 승인하지 않는다. -- 일괄 모드에서는 승인 불가한 문서가 있더라도 전체 실행을 중단하지 않는다. 승인 가능한 문서만 승격하고, 나머지는 blocker와 함께 별도 보고한다. - -## 3. 허용되는 수정 - -- AI가 근거 있게 바로 고칠 수 있는 표현, 오타, 누락 제목 정리는 보강할 수 있다. -- 승인 성공 시 아래 변경을 수행한다. - - REQ 문서의 `Status`를 `Approved`로 변경 - - `Change Log`에 승인 기록을 새 항목으로 추가 - - `docs/srs/index.md`의 해당 Register 행 상태 갱신 - - 필요하면 `Recent Change Log Summary` 갱신 -- `Change Log` 승인 기록은 기존 항목을 덮어쓰지 않고 append한다. -- 승인 보류인 REQ에는 승인 기록을 남기지 않는다. -- `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. - - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. - - 필요한 상태값과 요약 항목만 in-place로 갱신한다. -- 사람 판단이 필요한 우선순위, 범위, Owner는 임의로 확정하지 않는다. -- 일괄 모드에서도 허용되는 수정 범위는 동일하며, 승인 불가 문서의 상태는 바꾸지 않는다. - -# Execution Procedure - -1. 입력에서 대상 REQ 경로 하나 또는 대상 REQ 문서 목록을 확정한다. -2. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Proposed` 문서를 승인 후보 목록으로 만든다. -3. 대상 REQ들과 `docs/srs/index.md`를 읽는다. -4. 각 REQ마다 필수 필드, Acceptance Criteria, Notes의 blocker 여부를 확인한다. -5. AI가 근거 있게 정리할 수 있는 사소한 일관성 문제를 먼저 고친다. - - 승인 기록을 append 하거나 문서 말미를 patch할 때는 대상 REQ 파일을 전체 기준으로 다시 읽었는지 확인한다. offset/limit 기반 부분 읽기나 도구 축약 출력만 본 상태에서 Change Log를 덧붙이면 `Change Summary` 같은 제목/본문이 잘린 채 저장될 수 있다. - - 승인 패치 직후에는 최소한 (a) 대상 REQ의 새 Change Log tail, (b) `Status: Approved`, (c) `docs/srs/index.md`의 해당 Register 행을 다시 읽어 실제 저장 결과를 검증한다. -6. source Discovery가 명시돼 있거나 `생성된 REQ 참조`로 추적 가능한 경우, 승인 전에 Discovery의 범위/결정/오픈 질문과 REQ가 모순되지 않는지 확인한다. - - 특히 Discovery의 `사용자 결정 필요 항목 요약`, `REQ로 넘기기 전 확인 체크`, `범위 경계`, `리스크/가정 목록`에서 승인 blocker가 남아 있지 않은지 본다. - - Discovery에 `Open` 상태 질문, 미확정 DECIDE/CONFIRM/DATA 항목, handoff 미승인 상태가 남아 있으면 승인 보류를 우선 검토한다. -7. 각 REQ에 대해 승인 게이트를 다시 평가한다. -8. 게이트 통과한 REQ만 `Approved`로 전환하고 인덱스를 갱신한다. -9. 게이트 미통과한 REQ는 상태를 유지하고 남은 blocker를 수집한다. -10. 입력이 없는 경우 승인 성공 목록과 승인 불가 목록을 분리해 함께 보고한다. - -# Output Expectations - -- 단일 입력 모드: - - 대상 REQ 경로 - - 승인 성공 여부 - - 자동 보강한 항목 목록 - - 남은 blocker 또는 미해결 항목 - - `docs/srs/index.md` 갱신 결과 -- 무인자 일괄 모드: - - 검토한 REQ 전체 목록 - - 승인 성공한 REQ 목록 - - 승인 불가한 REQ 목록과 각 blocker - - 자동 보강한 항목 목록 - - `docs/srs/index.md` 갱신 결과 - -# Common Pitfalls - -1. 필수 섹션은 있지만 내용이 너무 비어 있는 REQ를 승인하는 실수 - - 필드 존재 여부만 보지 말고, 실제로 구현/검증 판단에 쓸 수 있을 정도로 구체적인지 확인한다. - -2. `Notes`의 open question이나 사람 결정 필요 항목을 blocker로 처리하지 않고 승인하는 실수 - - Priority, Owner, 핵심 범위, 승인 판단에 영향을 주는 질문은 해소 전까지 승인하지 않는다. - -3. 이미 `Approved`, `Implemented`, `Deprecated`인 REQ를 다시 confirm 대상으로 다루는 실수 - - 이 경우 중복 승인 대신 현재 상태 유지 또는 `change-req`, `confirm-req-implemented` 경로를 검토한다. - -4. `docs/srs/index.md` 상태만 바꾸고 REQ 본문 상태를 같이 안 바꾸는 실수 - - 승인 성공 시 REQ 본문과 index register 상태를 함께 `Approved`로 맞춰야 한다. - -5. 일괄 모드에서 일부 blocker가 있다고 전체를 중단하는 실수 - - 일괄 모드에서는 각 REQ를 독립 게이트로 평가하고, 승인 가능한 문서만 승격한다. - -6. 승인 단계에서 사람이 정해야 할 Priority, Owner, 범위를 AI가 임의로 확정하는 실수 - - 사람이 결정해야 하는 항목은 자동으로 메우지 말고 blocker로 남긴다. - -# Verification Checklist - -- [ ] 대상 REQ 경로가 유일하게 확정되었거나, 일괄 모드 후보 집합이 올바르게 수집되었다. -- [ ] 대상 REQ의 현재 상태가 `Proposed`인지 확인했다. -- [ ] 필수 필드(`Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`)가 모두 존재한다. -- [ ] `Acceptance Criteria`가 구현/검증에 사용할 수 있을 만큼 구체적이다. -- [ ] 승인 blocker인 placeholder, open question, 사람 결정 필요 항목이 남아 있지 않다. -- [ ] source Discovery가 있으면 handoff 상태와 open question 정리가 승인 전제와 모순되지 않는다. -- [ ] 사람이 판단해야 하는 `Priority`, `Owner`, 범위를 AI가 임의 확정하지 않았다. -- [ ] 승인 성공 시 REQ 본문과 `docs/srs/index.md` 상태가 둘 다 `Approved`로 갱신되었다. -- [ ] 승인 보류 시 상태가 잘못 바뀌지 않았다. -- [ ] 일괄 모드에서는 승인 가능한 REQ만 승격되었다. -- [ ] `docs/srs/index.md`는 로컬 형식을 유지한 채 필요한 상태값만 in-place로 갱신되었다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-batch-design/SKILL.md b/.stage-pilot/skills/draft-batch-design/SKILL.md deleted file mode 100644 index 16cbc19..0000000 --- a/.stage-pilot/skills/draft-batch-design/SKILL.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: draft-batch-design -description: "Use when: drafting the design document for an existing batch, running /draft-batch-design with a BAT ID, filling docs/batches//design.md, recording architecture and interface decisions before implementation, or planning updates to docs/project-structure.md and docs/runtime-flows.md." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, design, architecture, sdlc] - related_skills: [draft-batch, draft-batch-planning, run-batch-implementation] ---- - -# Purpose - -This skill writes the design document for a batch by summarizing architecture, changed areas, key decisions, and edge cases from the batch plan and included REQs. - -`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, 이 skill은 해당 baseline 문서를 읽고 이번 batch의 구조 영향과 흐름 영향을 비교해야 한다. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//planning.md` -- 포함된 REQ 문서들 -- `docs/batches//design.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- planning이 없거나 범위가 비어 있으면 설계를 진행하지 않는다. -- 설계는 구현 전에 결정해야 할 구조와 인터페이스를 중심으로 적는다. -- `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 없다면 design 단계는 생략할 수 있다. -- REQ 간 공통 설계 전제가 있다면 batch design에 끌어올린다. -- baseline 문서가 있으면 이번 batch 설계는 baseline 대비 변경점과 유지점이 무엇인지 명시해야 한다. -- `design.md`에는 최소한 아래 내용을 포함해야 한다. - - Architecture Summary - - Changed Areas - - Key Decisions - - Edge Cases - - Architecture Impact (`none` | `project-structure` | `runtime-flows` | `both`) - - Reference Doc Update Plan -- 기존 템플릿에 위 섹션이 없으면 설계 문서에 추가해 채운다. - -# Execution Procedure - -1. batch 경로를 확정한다. -2. planning과 포함 REQ를 읽는다. -3. planning의 `Design Gate`가 `no`이고 batch profile이 `batch-lite`면 design 없이 진행 가능한지 다시 확인하고, 그대로면 design 생략 결정을 유지한다. -4. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고 현재 baseline과 batch 범위를 비교한다. -5. `design.md`에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases를 채운다. -6. Architecture Impact와 Reference Doc Update Plan을 명시한다. -7. 설계상 blocker가 있으면 문서에 명시하고 구현으로 넘기지 않는다. - -# Validation - -- design 문서에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases가 모두 있는지 확인한다. -- planning에서 정의한 핵심 범위가 design에서 누락되지 않았는지 확인한다. -- 구조 또는 흐름 영향이 있는 batch라면 Architecture Impact와 Reference Doc Update Plan이 채워졌는지 확인한다. -- `batch-lite`에서 design을 생략한 경우 planning의 `Design Gate`와 batch index profile이 그 결정을 설명하는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-batch-planning/SKILL.md b/.stage-pilot/skills/draft-batch-planning/SKILL.md deleted file mode 100644 index 1217014..0000000 --- a/.stage-pilot/skills/draft-batch-planning/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: draft-batch-planning -description: "Use when: drafting the planning document for an existing batch, running /draft-batch-planning with a BAT ID, populating docs/batches//planning.md, or setting batch scope and dependency notes before design." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, planning, implementation, sdlc] - related_skills: [draft-batch, draft-batch-design, run-batch-implementation] ---- - -# Purpose - -This skill writes the planning document for a batch by consolidating included REQs, scope, dependencies, milestones, and key risks. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//index.md` -- 포함된 REQ 문서들 -- `docs/batches//planning.md` - -# Core Rules - -- planning은 batch 수준 범위, 의존성, 순서를 명확히 해야 한다. -- REQ 문서를 다시 쓰지 말고 delivery 관점의 묶음 계획으로 요약한다. -- planning은 design이 구현 전에 필요한지 여부를 명시해야 한다. -- `batch-lite`라면 planning에 왜 design을 생략할 수 있는지 또는 언제 design이 필요해지는지 남긴다. -- planning 작성 후 batch 상태는 `in-delivery`로 올릴 수 있다. - -# Execution Procedure - -1. 입력에서 batch 경로를 확정한다. -2. batch index와 포함 REQ를 읽는다. -3. `planning.md`에 scope, out-of-scope, dependency, milestone, risk를 채운다. -4. `planning.md`의 `Design Gate`에 design 필요 여부와 근거를 적는다. -4. 필요하면 batch `index.md`의 Status를 `in-delivery`로 갱신한다. - -# Validation - -- planning 문서에 Included REQ, Delivery Plan, Dependencies, Design Gate, Milestones, Risks가 모두 존재하는지 확인한다. -- batch index와 planning의 포함 REQ 목록이 일치하는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-batch-verification/SKILL.md b/.stage-pilot/skills/draft-batch-verification/SKILL.md deleted file mode 100644 index 66f5348..0000000 --- a/.stage-pilot/skills/draft-batch-verification/SKILL.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: draft-batch-verification -description: "Use when: drafting verification evidence for a batch, running /draft-batch-verification with a BAT ID, filling docs/batches//verification.md, mapping REQ acceptance criteria to evidence before release approval, or verifying sync with docs/project-structure.md and docs/runtime-flows.md." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, verification, testing, sdlc] - related_skills: [draft-batch, run-batch-implementation, confirm-batch-verification] ---- - -# Purpose - -This skill drafts the verification document for a batch by mapping included REQ acceptance criteria to available evidence and identifying remaining blockers. - -batch design 또는 REQ가 baseline 문서 생성/갱신 또는 구조/흐름 영향이 있음을 나타내면, verification은 `docs/project-structure.md`와 `docs/runtime-flows.md`가 코드와 설계에 맞게 동기화되었는지도 확인해야 한다. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- 포함된 REQ 문서들 -- `docs/batches//design.md` (존재하는 경우) -- `docs/batches//planning.md` -- `docs/batches//implementation.md` -- `docs/batches//verification.md` -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- verification은 Discovery 성공 기준이 아니라 REQ acceptance criteria에 직접 연결한다. -- evidence가 없는 항목은 통과로 간주하지 않는다. -- stateful runtime/PYDEBUG binding 검증에서는 같은 입력을 연속 호출해도 내부 상태가 누적될 수 있음을 먼저 확인한다. REQ가 deterministic candidate/order evidence를 요구하면 '같은 입력 + 같은 초기 상태'를 보장하는 reset hook, 재초기화 step, 또는 동등한 통제 절차를 verification evidence에 포함한다. -- 확인되지 않은 항목은 `Blocking Issues`에 명시한다. -- `batch-lite`에서 design 문서가 없다면 planning의 `Design Gate`와 implementation 결과를 기준으로 구조 영향이 실제로 없었는지 확인해야 한다. -- design의 Architecture Impact가 `none`이 아니거나 baseline 문서 생성/갱신이 batch 범위에 있으면, baseline 문서 동기화 evidence를 verification에 포함해야 한다. -- baseline 문서 동기화가 필요한데 evidence가 없으면 release-ready로 간주하지 않는다. -- hardware/manual 검증이 남아 있는 상태에서 코드/문서 구현만 끝났다면 `Ready for Release: false`를 유지하고, 누락된 실장치 증거를 `Missing evidence`와 `Blocking Issues`에 명시해 confirm 단계로 성급히 올리지 않는다. -- 설정 persistence를 실장치로 검증할 때는 장치가 노출하는 실제 제어 경로를 먼저 확인한다. CTSP 설정의 재부팅 persistence를 확인하는 목적이면 가능할 때 CTSP `REBOOT`/`RESET` 명령으로 재부팅하고, 재부팅 전후 동일 CTSP readback(`DATE`, 대상 설정 명령 등)으로 복귀와 설정 유지 여부를 캡처한다. 테스트 중 설정을 바꿨다면 verification evidence를 수집한 뒤 baseline 값을 다시 써서 원복 여부도 함께 남긴다. -- CTSP 기반 실장치 검증에서는 먼저 비파괴 명령(예: `DATE`, 읽기 전용 `VISIONCFG`)으로 연결과 응답 형식을 확인한 뒤 쓰기/재부팅 테스트로 넘어간다. `VISIONCFG`처럼 값 목록이 단일 payload 안에 `/`로 직렬화되는 명령은 raw response, 분해 후 값 개수, slot별 해석(예: ratio 27번째, trailing reserved 28~32번째)을 함께 기록해야 한다. -- 업그레이드/migration 검증에서는 업그레이드 직전 legacy readback 원문과 기대 26개 값을 먼저 고정하고, 업그레이드 직후 첫 current 32값 readback의 1~26 슬롯을 그 legacy 캡처와 직접 대조해야 한다. ratio=`10`, trailing reserved=`0xFFFFFFFF`만 맞고 1~26 슬롯이 업그레이드 전 값과 다르면 migration 성공으로 간주하지 말고, 설정 영역 보존 실패/플래시 경로 문제/fixture 주입 실패 가능성을 blocker로 기록한다. -- 업그레이드 직후 boot log에 `current revision size mismatch (...)` 뒤 `vision config default`가 보이면, 이는 rev0 migration 성공 증거가 아니라 "current revision으로 해석된 저장본의 binary schema/길이가 현 코드와 다름"을 뜻한다. 이런 경우에는 downgrade 후 legacy `VISIONCFG` readback을 다시 캡처해 기존 26개 값이 되살아나는지 확인하고, 값이 복원되면 storage erase보다 revision-schema drift 또는 current-loader incompatibility를 우선 원인 후보로 기록한다. -- 업그레이드 경로 진단에서는 image slot과 `storage_partition`을 분리해서 해석한다. firmware loader가 image slot만 erase/write하는 구조라면, migration 실패 후 default current 값이 보였더라도 곧바로 "설정이 지워졌다"고 결론내리지 말고, downgrade/legacy readback 또는 raw property dump로 storage 보존 여부를 먼저 구분한다. -- verification에 사람이 명시적으로 잔여 리스크를 수용하기로 했다면 `Human Approval Memo` 섹션을 추가해 승인 결정, 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거를 구조적으로 남긴다. 이후 confirm 단계에서는 이 메모가 수용한 blocker와 미수용 blocker를 분리해서 판단할 수 있어야 한다. -- 재부팅 persistence를 자동 검증할 때는 가능하면 ext-proto reboot보다 CTSP `REBOOT`/`RESET`처럼 실제 설정 검증 경로와 같은 제어면을 우선 사용하고, 재부팅 전후 `DATE`와 대상 설정 readback을 같이 캡처한다. - -# Execution Procedure - -1. batch와 포함 REQ를 확정한다. -2. REQ acceptance criteria를 목록화한다. -3. design 문서가 있으면 Architecture Impact와 Reference Doc Update Plan을 읽고, 없으면 planning의 `Design Gate`를 읽는다. -4. implementation 결과와 테스트 로그를 근거로 evidence를 연결한다. -5. baseline 문서 동기화가 필요하면 `docs/project-structure.md`와 `docs/runtime-flows.md`의 생성/갱신 여부를 evidence에 포함한다. -6. `batch-lite`에서 design이 없었다면 verification에 `no architecture impact confirmed` 여부를 남긴다. -7. `verification.md`의 Acceptance Mapping, Evidence, Result를 채운다. -8. 재부팅 persistence나 CTSP contract 계열 실장치 검증을 수행했다면 `references/opspro-visioncfg-hardware-verification.md` 같은 session note를 남겨, 실제로 성공한 명령/응답/원복 순서를 future batch verification에 재사용할 수 있게 한다. OpsPro `VISIONCFG` 작업에서는 current 32값 round-trip, legacy 26값 호환, invalid ratio reject, reboot persistence, migration failure signature(`current revision size mismatch`)뿐 아니라, 순수 legacy device에서의 성공한 first-boot migration / post-reboot current direct-load evidence와 downgrade/re-upgrade schema-drift exclusion rule까지 그 reference에 누적한다. - -# Validation - -- 포함 REQ마다 acceptance mapping이 존재하는지 확인한다. -- evidence 없는 항목이 있으면 Result에 blocker로 반영했는지 확인한다. -- Architecture Impact가 있는 batch라면 baseline 문서 동기화 evidence 또는 blocker가 명시됐는지 확인한다. -- `batch-lite`에서 design이 없는 경우 planning과 implementation을 기준으로 구조 영향 없음 또는 design 누락 blocker 중 하나가 명시됐는지 확인한다. -- 실장치/manual evidence가 필요한 REQ인데 코드 diff나 빌드 결과만 있는 경우, `Ready for Release`가 false로 유지되고 confirm 단계 보류 사유가 적혀 있는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md b/.stage-pilot/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md deleted file mode 100644 index 694142a..0000000 --- a/.stage-pilot/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md +++ /dev/null @@ -1,58 +0,0 @@ -# OpsPro VISIONCFG hardware verification reference - -Use this reference when drafting or updating verification evidence for OpsPro `VISIONCFG` CTSP and migration work. - -## CTSP hardware verification patterns - -1. Start with non-destructive probes. - - `DATE` confirms port/CTSP responsiveness. - - `VISIONCFG` read confirms payload shape before any write. -2. For `VISIONCFG`, always capture: - - raw request - - raw response - - parsed value count - - slot interpretation (1..26 legacy/public, 27 ratio, 28..32 reserved for current 32-value contract) -3. When write tests are allowed, use this order: - - current 32-value round-trip - - legacy 26-value compatibility write - - invalid ratio reject / no partial write - - reboot persistence - - migration from pure legacy device/state -4. After write-based evidence capture, restore baseline values and confirm readback. - -## Migration verification lessons - -### Exclude downgrade/re-upgrade drift from pure legacy migration evidence -If a device has already booted a newer firmware that can write a `current` `visioncfg`, then later downgrade/re-upgrade behavior is not clean REQ-005 legacy migration evidence. Treat that as a separate compatibility/schema-drift investigation, not as the primary legacy-to-current migration acceptance path. - -### Pure legacy migration acceptance path -For REQ-005 migration evidence, use a device that has never been updated to the newer firmware revision being tested. - -Required sequence: -1. Confirm legacy firmware returns 26-value `VISIONCFG`. -2. Write distinctive 26-value legacy test data. -3. Capture legacy readback before upgrade. -4. Upgrade to target firmware and reboot. -5. On first boot, read `VISIONCFG` and confirm: - - 32 values total - - slots 1..26 exactly match pre-upgrade legacy values - - slot 27 is `10` - - slots 28..32 are `-1/-1/-1/-1/-1` -6. Reboot via CTSP `REBOOT` and confirm current readback is unchanged. -7. Restore baseline current values after evidence capture. - -### Diagnostic logging for schema issues -When investigating migration failures, instrument `_load_vision_config()` to log: -- `sizeof(struct prop_header)` -- `sizeof(struct ocv_rect)` -- `sizeof(struct _vision_config_rev0)` -- `sizeof(struct _vision_config)` -- header probe `rc`, `type`, `revision` -- current read expected size vs `rc` -- revision-0 read expected size vs `rc` - -Interpretation rule: -- `current revision size mismatch (...)` followed by `vision config default` means the stored blob was interpreted as current revision but its binary size/layout did not match the current firmware's expected current schema. Do not misclassify that as successful legacy migration. - -## ABI / struct-layout caution -Do not infer target firmware ABI from `PYDEBUG`-only fallback definitions or host-side binding builds. For firmware storage compatibility analysis, trust target-firmware definitions and on-device logs first. diff --git a/.stage-pilot/skills/draft-batch/SKILL.md b/.stage-pilot/skills/draft-batch/SKILL.md deleted file mode 100644 index 5789833..0000000 --- a/.stage-pilot/skills/draft-batch/SKILL.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -name: draft-batch -description: "Use when: creating a new batch from approved REQ IDs, running /draft-batch with one or more req IDs, scaffolding docs/batches// files, or updating docs/batches/index.md for a new delivery unit." -version: 0.9.2 -author: Justin Ko -license: private -argument-hint: "예: req-001 req-002 req-003" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, planning, documentation, sdlc] - related_skills: [suggest-batch-reqs, draft-batch-planning, draft-batch-design, draft-batch-verification, run-batch-delivery] ---- - -# Purpose - -This skill creates a new batch delivery unit from already-selected approved requirements, chooses the right delivery profile, scaffolds its folder and documents, and updates the batch register. - -# When to use - -- 포함할 `Approved` REQ 집합이 이미 확정되어 새 batch를 실제로 생성해야 할 때 -- `/draft-batch req-001 req-002`처럼 batch 문서 scaffold와 register entry가 필요할 때 -- 단일 저위험 `Approved` REQ를 `minor-change` fast path로 바로 batch-lite batch로 시작할 때 -- `suggest-batch-reqs` 결과에서 사람이 후보를 선택한 뒤 실제 batch 문서를 만들 차례일 때 - -## Do not use when - -- 아직 REQ가 `Approved`되지 않아 먼저 승인 절차가 필요할 때 (`confirm-req`가 더 적절함) -- 어떤 REQ를 함께 묶을지 아직 사람 선택이 끝나지 않았을 때 (`suggest-batch-reqs`가 더 적절함) -- 이미 존재하는 batch의 planning/design/verification 내용을 채우는 단계일 때 (`draft-batch-planning`, `draft-batch-design`, `draft-batch-verification`이 더 적절함) -- 현재 저장소의 전체 다음 SDLC 단계를 알고 싶은 상황일 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- `Approved` `REQ-ID` 목록 -- 선택 입력 - - batch profile 힌트 (`standard` | `batch-lite` | `minor-change`) -- `docs/srs/index.md` -- 대상 REQ 문서들 -- `docs/batches/index.md` -- 배치 템플릿의 논리 경로 - - `stage-pilot/templates/batches/batch-index.md` - - `stage-pilot/templates/batches/planning.md` - - `stage-pilot/templates/batches/design.md` - - `stage-pilot/templates/batches/implementation.md` - - `stage-pilot/templates/batches/verification.md` - - `stage-pilot/templates/batches/index.md` (`docs/batches/index.md` register 템플릿/기준) - -## Template path resolution - -- 배치 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/batches/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/batches/...` - 2. `~/.stage-pilot/templates/batches/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 이 resolution rule은 SKILL.md 본문에 그대로 적어 두는 편이 좋다. 배치 템플릿이 실제로 존재하는 현재 머신의 경로만 적어 두면 다른 PC나 다른 agent install shape에서 portability가 떨어진다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/batches/...` - - target: `docs/batches//...`, `docs/batches/index.md` - -# Core Rules - -- 이 skill은 사람이 선택을 마친 REQ 집합을 batch 문서로 구체화하는 단계다. -- `Approved` 상태가 아닌 REQ는 batch에 포함하지 않는다. -- 새 batch 폴더는 `docs/batches/bat-XXX_YYYYMMDD_scope/` 형식을 사용한다. -- 새 batch의 persisted profile은 `standard` 또는 `batch-lite` 중 하나다. -- `minor-change`는 profile 자체가 아니라 단일 저위험 REQ를 빠르게 `batch-lite`로 판단하는 입력 fast path다. -- 입력이 정확히 1개의 `Approved` REQ이고 범위가 국소적이며 구조/인터페이스/런타임 흐름 변경이 없으면 `minor-change` fast path를 사용할 수 있다. 이 경우 최종 batch profile은 `batch-lite`로 기록한다. -- `standard` batch는 생성 시 아래 파일을 함께 만든다. - - `index.md` ← `stage-pilot/templates/batches/batch-index.md` - - `planning.md` ← `stage-pilot/templates/batches/planning.md` - - `design.md` ← `stage-pilot/templates/batches/design.md` - - `implementation.md` ← `stage-pilot/templates/batches/implementation.md` - - `verification.md` ← `stage-pilot/templates/batches/verification.md` -- `batch-lite`는 생성 시 아래 파일만 먼저 만든다. - - `index.md` ← `stage-pilot/templates/batches/batch-index.md` - - `planning.md` ← `stage-pilot/templates/batches/planning.md` -- `batch-lite`의 `implementation.md`와 `verification.md`는 해당 단계 진입 시 생성한다. -- `batch-lite`라도 구조/인터페이스/흐름 영향이 생기면 `design.md`를 추가하고 사실상 standard depth로 다룬다. -- REQ 묶음 선택이 아직 불안정하거나 대안 비교가 필요하면 batch를 생성하지 말고 먼저 `suggest-batch-reqs`로 되돌린다. -- `docs/batches/index.md`의 register는 새 batch 생성과 함께 반드시 갱신한다. -- register 행에는 최소한 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 채운다. -- source discovery는 포함 REQ에서 역추적해 채운다. source가 하나면 그대로 기록하고, 여러 source discovery가 섞이면 축약 표기와 함께 batch 문서 Notes에 설명한다. - -# Execution Procedure - -1. 입력된 REQ 경로와 상태를 확인하고 모두 `Approved`인지 검증한다. -2. 입력된 REQ 묶음이 이미 사람에 의해 선택된 집합인지 확인한다. 아직 후보 선택 단계면 batch를 만들지 않는다. -3. 필요한 템플릿의 논리 경로를 현재 환경의 물리 경로로 해석하고, 어떤 위치를 사용할지 확정한다. -4. 현재 저장소의 기존 batch 문서(`docs/batches/index.md`, 가능하면 가장 최근 batch의 `index.md`/`planning.md`)를 먼저 읽어 로컬 heading, register 표기, 문체를 확인한다. 템플릿은 scaffold source이고, target 문서는 저장소 로컬 형식을 우선한다. -5. REQ 수, 영향 범위, 구조/인터페이스/런타임 영향 여부를 기준으로 `standard` 또는 `batch-lite` profile을 결정한다. - - 특히 같은 Discovery에서 나온 REQ 묶음이 (a) runtime 또는 interface 구조 변경, (b) 성능/운영 budget 같은 Non-Functional 기준, (c) baseline 문서 정렬을 함께 포함하면 기본값은 `standard`로 본다. 이런 조합은 planning만으로 끝나지 않고 design, implementation, verification 문서를 같은 턴에 scaffold하는 편이 drift와 재작업을 줄인다. -6. 단일 저위험 REQ면 `minor-change` fast path를 검토하되 최종 기록 profile은 `batch-lite`로 남긴다. -7. 배치 제목과 slug를 결정하고 다음 `BAT-ID`를 계산한다. -8. profile에 맞는 템플릿으로 batch 폴더와 문서를 생성하되, placeholder를 그대로 두지 말고 이번 REQ 집합 기준의 구체적 초안 내용을 즉시 채운다. -9. `index.md`에 `Profile`, Included REQ, Excluded REQ, Source Discovery, Scope, Documents, Notes를 채운다. -10. 포함 REQ의 source discovery를 수집해 `index.md`와 register에 반영한다. source가 복수면 Notes에 혼합 배경을 설명한다. -11. `planning.md`에 `Design Gate`를 기록해 design이 즉시 필요한지 여부와 근거를 남긴다. -12. `docs/batches/index.md`에 새 register 행을 추가하고 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 실제 생성 결과와 일치하게 채운다. 이때 register의 헤더/구분선/행 순서는 현재 저장소 로컬 형식을 유지한다. -13. 생성된 문서 집합과 register가 profile 판단 및 입력 REQ 집합과 일치하는지 검증한다. - -# Common Pitfalls - -1. `Approved`가 아닌 REQ를 섞는 실수 - - batch 생성은 delivery 입력이 확정된 REQ만 대상으로 해야 하므로 상태를 먼저 검증한다. - -2. 아직 사람 선택이 끝나지 않았는데 곧바로 batch를 만드는 실수 - - 여러 후보안 중 선택이 남아 있으면 먼저 `suggest-batch-reqs`에서 멈춘다. - -3. `minor-change`를 최종 profile 값처럼 기록하는 실수 - - `minor-change`는 fast path 이름일 뿐이고, persisted profile은 `batch-lite`다. - -4. `stage-pilot/templates/batches/index.md`와 `docs/batches/index.md`를 혼동하는 실수 - - 전자는 register template/source이고 후자는 실제 저장소 target 파일이다. - -5. `batch-lite`인데 구조 영향이 생겼는데도 design 문서를 만들지 않는 실수 - - planning의 `Design Gate`를 다시 보고 필요하면 `design.md`를 추가한다. - -6. 템플릿 경로를 한 설치 형태에만 고정하는 실수 - - `.stage-pilot`, `~/.stage-pilot` 순서의 resolution rule을 따른다. - -7. pack-level 문서를 빼먹는 실수 - - 외부 skill pack의 SKILL.md를 실제 수정해 버전이 바뀌면 `README.md` inventory와 `CHANGELOG.md`도 같은 턴에 함께 갱신한다. - -8. 템플릿 문구를 저장소 로컬 형식보다 우선해 그대로 덮어쓰는 실수 - - 템플릿은 scaffold source일 뿐이다. 실제 생성 시에는 현재 저장소의 기존 batch 문서 형식과 register 배치를 먼저 보고, target 문서는 그 로컬 형식에 맞춰 채운다. - -9. 템플릿 placeholder만 복사하고 batch 초안 내용을 비워 두는 실수 - - `draft-batch` 단계에서는 최소한 포함/제외 REQ, source discovery, scope, Design Gate, 핵심 리스크까지 즉시 채워 넣어 다음 stage가 바로 이어질 수 있게 한다. - -# Verification Checklist - -- [ ] 포함 REQ가 모두 `Approved` 상태다. -- [ ] 새 `BAT-ID`와 folder명이 기존 register와 충돌하지 않는다. -- [ ] 사용한 템플릿 물리 경로가 resolution rule과 일치한다. -- [ ] template source path와 generated target path를 혼동하지 않았다. -- [ ] `standard`면 5개 문서가 모두 생성되었다. -- [ ] `batch-lite`면 최소 `index.md`, `planning.md`가 생성되었고 profile이 `batch-lite`로 기록되었다. -- [ ] `planning.md`의 `Design Gate`가 현재 profile과 구조 영향 판단을 설명한다. -- [ ] `docs/batches/index.md` register 행이 실제 batch folder, profile, included REQ, discovery와 일치한다. -- [ ] source discovery가 단일/복수 여부에 맞게 기록되었다. -- [ ] 아직 사람 선택이 필요한 상태를 batch 생성으로 건너뛰지 않았다. diff --git a/.stage-pilot/skills/draft-release/SKILL.md b/.stage-pilot/skills/draft-release/SKILL.md deleted file mode 100644 index e9350ca..0000000 --- a/.stage-pilot/skills/draft-release/SKILL.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: draft-release -description: "Use when: creating a release document from one or more release-candidate batches, running /draft-release with BAT IDs, generating docs/releases/rel-XXX_YYYYMMDD_.md, or updating docs/releases/index.md before release approval." -version: 0.8.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 bat-001 bat-002" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, release, drafting, documentation, sdlc] - related_skills: [confirm-batch-verification, confirm-release, capture-release-feedback] ---- - -# Purpose - -This skill creates a profile-aware release document from one or more verified batches and registers it in the release index. - -# Inputs - -- `BAT-ID` 목록 -- `docs/batches/index.md` -- 각 batch의 `verification.md` -- `docs/releases/index.md` -- 템플릿의 논리 경로 - - `stage-pilot/templates/releases/release.md` - - `stage-pilot/templates/releases/index.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/releases/rel-XXX_YYYYMMDD_.md`, `docs/releases/index.md` - -# Core Rules - -- `release-candidate`가 아닌 batch는 release에 포함하지 않는다. -- 새 release 경로는 `docs/releases/rel-XXX_YYYYMMDD_.md` 형식을 사용한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. -- release profile은 포함 batch의 운영 부담 중 가장 무거운 것을 따른다. 기본 우선순위는 `app-service` > `tooling` > `docs-only`다. -- `docs-only`는 문서, 가이드, 템플릿, 정적 산출물 변경에 사용한다. -- `tooling`은 bootstrap, 스크립트, CLI, 자동화, 패키지 동작 검증이 필요한 변경에 사용한다. -- `app-service`는 실제 서비스 배포, runtime health, 로그, 운영 확인이 필요한 변경에 사용한다. -- release 문서 생성 시 Included Batch, Profile, Scope, Rollout Plan, Rollback Plan, Verification Checklist를 채운다. - -# Execution Procedure - -1. 입력 batch들의 상태와 verification 결과를 확인한다. -2. 포함 batch의 변경 성격과 검증 evidence를 기준으로 release profile을 결정한다. -3. 다음 `REL-ID`를 계산하고 제목/slug를 정한다. -4. 필요한 release 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤 release 문서를 생성한다. -5. `docs/releases/index.md`가 없으면 `stage-pilot/templates/releases/index.md`에 해당하는 실제 template source를 해석한 뒤 생성하고, 이미 있으면 register 행을 추가한다. - -# Validation - -- 포함 batch가 모두 `release-candidate`인지 확인한다. -- release 문서에 `Profile`이 있고 profile별 검증 항목이 비어 있지 않은지 확인한다. -- release 문서와 index register 행이 함께 생성됐는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-req/SKILL.md b/.stage-pilot/skills/draft-req/SKILL.md deleted file mode 100644 index 14af9b2..0000000 --- a/.stage-pilot/skills/draft-req/SKILL.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -name: draft-req -description: "Use when: turning a confirmed Discovery into one or more requirement documents under `docs/srs/`, running /draft-req with a dcy-id or Discovery path, creating docs/srs//req-XXX_.md files, updating docs/srs/index.md from FR/NFR input, or carrying forward constraints from docs/project-structure.md and docs/runtime-flows.md." -version: 1.0.3 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, requirements, drafting, documentation, sdlc] - related_skills: [confirm-discovery, change-req, confirm-req, suggest-batch-reqs] ---- - -# Purpose - -This skill converts a Discovery document into one or more requirement documents under `docs/srs/`, updates the SRS register, and writes the generated REQ references back to the source Discovery document. - -Reference: template path portability guidance lives in `references/template-path-resolution.md`. - -`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, REQ 작성 시 이 문서들을 공통 baseline 참조 문서로 읽고 필요한 구조 제약만 REQ에 반영한다. - -# When to use - -- `/draft-req dcy-001`처럼 confirmed Discovery를 REQ backlog로 정규화해야 할 때 -- Discovery의 FR/NFR을 구현 가능한 requirement 문서로 분리해야 할 때 -- `docs/srs/index.md`의 register와 Next Requirement ID를 함께 갱신해야 할 때 -- Discovery가 구조 또는 runtime baseline 갱신을 요구할 때 이를 REQ backlog에 반영해야 할 때 - -## Do not use when - -- source Discovery가 아직 `confirmed` 상태가 아니고, 아직 문제 정의나 범위 정리가 끝나지 않았을 때 -- 기존 Discovery 문서를 검토/보강/승인해야 하는 단계일 때 (`review-discovery`, `confirm-discovery`가 더 적절함) -- 새 REQ를 만드는 것이 아니라 기존 REQ의 상태, acceptance criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) -- 이미 승인된 REQ를 구현 batch로 묶는 단계일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) - -# Inputs - -- Discovery 식별자 - - `dcy-001` 같은 prefix - - 전체 Discovery ID - - `docs/discovery/.md` 경로 -- 대상 Discovery 문서 본문 -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) -- 템플릿의 논리 경로 - - `stage-pilot/templates/srs/index.md` - - `stage-pilot/templates/srs/req-template.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/srs/index.md`, `docs/srs//req-XXX_.md` -- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. - -- Repository targets - - `docs/srs/index.md` - - `docs/srs//req-XXX_.md` - -# Core Rules - -## 0. Entry gating and confirmed prerequisite - -- source Discovery는 기본적으로 `confirmed` 상태여야 한다. -- source Discovery가 아직 `draft`이거나 confirmed 여부가 불명확하면, REQ drafting을 계속 진행하지 말고 그 사실을 먼저 보고한다. -- confirmed 전제가 충족되지 않으면 `review-discovery` 또는 `confirm-discovery`가 선행되어야 한다. -- 다만 사용자가 명시적으로 "초안 REQ를 미리 뽑아 달라"고 요청한 예외 상황에서는, 승인 전 초안임을 분명히 표시하고 `Proposed` 초안으로만 제한적으로 작성할 수 있다. - -## 1. Baseline architecture reference rules - -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 REQ의 상세 본문을 대체하지 않지만, 구조 제약과 흐름 제약을 읽기 위한 공통 baseline 참조 문서로 사용한다. -- REQ에는 baseline 문서의 전체 구조나 전체 흐름을 복제하지 않는다. 구현에 필요한 제약과 acceptance 기준만 남긴다. -- source Discovery가 구조 또는 runtime baseline 갱신을 요구하면, REQ 후보에 baseline 문서 갱신 작업을 포함해야 한다. -- baseline 문서가 없고 source Discovery가 그 갭 보완을 직접 범위에 포함한다면, baseline 문서를 다루는 REQ를 누락하면 안 된다. -- baseline 문서가 없는데 source Discovery가 그 갭을 다루지 않는다면, `bootstrap-baseline` 선행 누락 가능성을 보고한다. - -## 2. 입력 해석 - -- 입력이 prefix면 `docs/discovery/` 아래에서 일치하는 문서를 찾는다. -- 여러 후보가 나오면 임의 선택하지 않고 사용자 확인이 필요하다고 보고한다. -- `.stage-pilot/` 경로는 Discovery 문서 탐색 대상에서 제외한다. -- template source는 단일 고정 경로로 가정하지 않는다. -- `stage-pilot/templates/...`는 logical path로 취급하고, 실제 읽기 경로는 현재 workspace의 StagePilot 설치 형태에 맞게 해석한다. -- template source를 읽을 때는 아래 순서로 후보를 확인한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. - -## 3. REQ 분해 원칙 - -- FR은 기본적으로 독립 구현과 독립 검증 가능 단위로 REQ 후보를 만든다. -- 하나의 FR이 과도하게 크면 복수 REQ로 분해한다. -- NFR은 독립 측정 가능성, 독립 backlog 가치, 운영 영향도가 충분할 때만 별도 REQ로 승격한다. -- 특정 FR 또는 batch 검증 기준에 흡수하는 편이 적절한 NFR은 별도 REQ를 만들지 말고 Notes에 연결 근거를 남긴다. -- baseline 문서 갱신 요구는 보통 `Documentation`, `Interface`, `Configuration` 중 가장 적절한 Type으로 분류한다. -- Discovery가 "기능 계약 + 성능/운영 기준 + baseline 문서 정렬"을 함께 요구하는 경우에는 다음 우선 분해 패턴을 먼저 검토한다. - 1. 기능 계약(상태 전이, 인터페이스, 실행 흐름) -> `Interface` 또는 가장 가까운 기능 Type - 2. 상태별 budget, latency, 처리 시간, 계측 의무 -> `Non-Functional` - 3. `docs/runtime-flows.md`/`docs/project-structure.md` 같은 baseline 정렬 -> `Documentation` -- 성능 수치가 구현 전 운영 계측값에 의존하더라도, 측정 경로와 목표 기준이 Discovery에 이미 정리돼 있으면 별도 `Non-Functional` REQ로 승격해 REQ 단계에서 evidence 수집을 요구하는 편을 우선한다. - -## 4. Type 및 경로 규칙 - -- 아래 폴더 중 정확히 하나를 선택한다. - - `Configuration` - - `Data` - - `Deployment` - - `Documentation` - - `Exception` - - `Installation` - - `Integration` - - `Interface` - - `Migration` - - `Non-Functional` - - `Testing` -- 새 REQ 경로는 `docs/srs//req-XXX_.md` 형식을 사용한다. -- `REQ-XXX` 번호는 `docs/srs/index.md`의 `Next Requirement ID`를 source of truth로 사용한다. - -## 5. 문서 작성 규칙 - -- `stage-pilot/templates/srs/req-template.md`에 해당하는 실제 template source를 해석한 뒤 문서를 만든다. -- 생성 대상 경로는 `docs/srs//req-XXX_.md`다. -- `Status` 기본값은 `Proposed`다. -- `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`를 실제 문장으로 채운다. -- baseline 문서와 충돌하거나 baseline 문서를 갱신해야 하는 구조 제약이 있으면 `Impacted Area` 또는 `Notes`에 명시한다. -- 사람 승인 없이는 확정할 수 없는 항목은 비워 두지 말고 `Notes`에 결정 필요 또는 가정으로 명시한다. -- Change Log는 최초 생성 기록을 남긴다. - -## 6. 인덱스 갱신 규칙 - -- REQ를 하나 이상 생성하면 `docs/srs/index.md`의 아래 항목을 함께 갱신한다. - - `Next Requirement ID` - - `Requirement Register` - - `Recent Change Log Summary` -- 기존 `docs/srs/index.md`가 이미 존재하면, 먼저 현재 register/header 형식과 열 순서를 읽고 그 로컬 형식을 우선 보존한다. -- template의 예시 열(`ID, Title, Type, Status, Priority, Owner, Link`)은 기본 기준이지만, 기존 index가 다른 열 이름이나 순서를 사용 중이면 사용자나 저장소가 이미 채택한 형식을 임의로 템플릿 형식으로 갈아엎지 않는다. -- 기존 index에 없는 새 필드가 필요하면 가능한 한 기존 형식에 자연스럽게 확장하고, 대규모 형식 정규화는 별도 변경으로 다룬다. -- Register에는 현재 index 형식이 요구하는 최소 메타데이터를 누락 없이 추가한다. -- 기존 `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. - - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. - - `Next Requirement ID` 표현이 `- REQ-002` 형태인지, `- Current: \`REQ-002\`` 형태인지 등은 현재 파일 형식을 따른다. - - 필요한 값만 in-place로 갱신하고, template source는 새 index bootstrap이 필요할 때만 사용한다. - -## 7. Source Discovery 역참조 갱신 규칙 - -- REQ를 하나 이상 생성하면 source Discovery 문서의 `# 0. 문서 상태`에도 어떤 REQ가 생성됐는지 기록해야 한다. -- Discovery 문서에 `생성된 REQ 참조` 항목이 이미 있으면 생성된 REQ 목록으로 갱신하고, 없으면 `후속 Discovery 참조` 바로 아래에 추가한다. -- `생성된 REQ 참조`는 생성된 REQ 번호 순서대로 유지하고 중복을 허용하지 않는다. -- source Discovery 문서를 갱신했다면 `마지막 갱신 시각(KST)`도 함께 갱신한다. -- 생성된 REQ가 아직 없으면 `생성된 REQ 참조: 없음`을 유지한다. - -# Execution Procedure - -1. 입력에서 Discovery 경로를 확정한다. -2. Discovery 문서에서 FR/NFR, 범위 경계, 리스크, 성공 기준을 읽는다. -3. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고, REQ에 남겨야 할 구조 제약과 흐름 제약을 추린다. -4. FR/NFR별 REQ 후보 목록을 만든다. -5. baseline 문서 갱신 또는 baseline 갭 보완 요구가 있으면 이를 반영한 REQ 후보를 포함한다. -6. 각 후보에 대해 Type, 제목, slug, Acceptance Criteria를 결정한다. -7. `docs/srs/index.md`의 다음 번호부터 순서대로 REQ 파일을 생성한다. -8. `docs/srs/index.md`를 갱신한다. -9. source Discovery 문서의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신한다. -10. 생성 결과와 제외한 NFR이 있으면 그 이유를 함께 보고한다. - -# Output Expectations - -- 생성된 REQ 파일 목록 -- 각 REQ의 Type과 핵심 의도 -- baseline 문서 참조 또는 생성/갱신 요구를 어떤 REQ에 반영했는지 -- `docs/srs/index.md` 갱신 결과 -- source Discovery 문서의 `생성된 REQ 참조` 갱신 결과 -- 별도 REQ로 승격하지 않은 NFR 목록과 처리 이유 - -# Common Pitfalls - -1. source Discovery가 아직 `confirmed`가 아닌데 REQ부터 만드는 실수 - - 이 경우 문제 정의와 범위가 흔들릴 수 있으므로 `review-discovery` 또는 `confirm-discovery`를 먼저 거친다. - -2. NFR을 전부 별도 REQ로 승격하는 실수 - - 독립 측정 가능성, backlog 가치, 운영 영향이 약하면 FR 또는 batch 검증 기준에 흡수하는 편이 낫다. - -3. baseline gap을 읽고도 관련 REQ를 만들지 않는 실수 - - source Discovery 범위에 baseline 보완이 포함되어 있으면, 그 갭을 다루는 REQ를 반드시 backlog에 포함한다. - -4. template source와 generated target path를 혼동하는 실수 - - template source는 `stage-pilot/templates/...` logical path를 해석한 결과이고, 생성 대상은 `docs/srs/...`다. - -5. `docs/srs/index.md`를 갱신하지 않고 REQ 파일만 만드는 실수 - - `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신해야 register가 깨지지 않는다. - -6. source Discovery 역참조를 빼먹는 실수 - - 생성된 REQ 참조와 `마지막 갱신 시각(KST)`를 source Discovery에도 반영해야 추적성이 유지된다. - -# Verification Checklist - -- [ ] source Discovery가 `confirmed` 상태인지 확인했다. -- [ ] Discovery 경로가 유일하게 확정되었고 prefix 충돌이 없다. -- [ ] `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 확인했다. -- [ ] logical template path를 실제 physical path로 올바르게 해석했다. -- [ ] template source와 generated target path를 혼동하지 않았다. -- [ ] 모든 REQ가 `docs/srs//req-XXX_.md` 형식을 따른다. -- [ ] `docs/srs/index.md`의 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신했다. -- [ ] source Discovery의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신했다. -- [ ] baseline 갭 보완이 source Discovery 범위에 포함되면 해당 REQ를 실제로 생성했다. -- [ ] 별도 REQ로 승격하지 않은 NFR은 이유를 함께 보고했다. \ No newline at end of file diff --git a/.stage-pilot/skills/draft-req/references/template-path-resolution.md b/.stage-pilot/skills/draft-req/references/template-path-resolution.md deleted file mode 100644 index 1ea6f88..0000000 --- a/.stage-pilot/skills/draft-req/references/template-path-resolution.md +++ /dev/null @@ -1,61 +0,0 @@ -# Template Path Resolution for `draft-req` - -Use this reference when StagePilot is installed in different physical locations across agents or repositories. - -## Core rule - -Do **not** treat one physical path as the canonical template location for every agent. - -Instead: - -- treat `stage-pilot/templates/...` as the stable logical template path -- resolve that logical path against the active workspace or agent installation -- keep template source paths separate from generated target paths under `docs/srs/` - -## Logical template paths - -For `draft-req`, the logical template sources are: - -- `stage-pilot/templates/srs/index.md` -- `stage-pilot/templates/srs/req-template.md` - -These names are portable across Hermes Agent, GitHub Copilot, Claude, and similar environments. The physical path may differ. - -## Physical path resolution order - -When resolving a logical template path, check candidates in this order: - -1. Workspace-local / subtree install: `.stage-pilot/templates/...` -2. Hermes external install: `~/.stage-pilot/templates/...` - -Prefer the current workspace's installed copy over a user-level fallback. - -## Source vs target distinction - -Keep these separate: - -- Template source: `stage-pilot/templates/...` resolved to one physical path above -- Generated targets: `docs/srs/index.md` and `docs/srs//req-XXX_.md` - -Do not describe a generated `docs/srs/...` file as if it were the template source. - -## Ambiguity rule - -If multiple physical candidates exist: - -- prefer the highest-priority path in the resolution order above -- do not silently mix sources from different installs -- report which physical path was used when ambiguity could matter - -## Durable pitfall - -A common portability mistake is to hardcode only one of these: - -- `.stage-pilot/templates/...` -- `~/.stage-pilot/templates/...` - -That works for one install shape but breaks on another machine or agent. The durable pattern is: - -- logical path in the skill -- resolution rule in the workflow -- generated target path documented separately diff --git a/.stage-pilot/skills/new-discovery/SKILL.md b/.stage-pilot/skills/new-discovery/SKILL.md deleted file mode 100644 index 394120b..0000000 --- a/.stage-pilot/skills/new-discovery/SKILL.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -name: new-discovery -description: "Use when: deciding whether to update an existing Discovery document or start a new one, running /new-discovery with or without parameters, drafting docs/discovery/dcy-__.md for a real product/service change, generating Discovery documents, or updating docs/discovery/index.md after baseline initialization is ready." -version: 1.0.1 -author: Justin Ko -license: private -argument-hint: "예: 사용자 로그인 기능 추가 - OAuth2 소셜 로그인, 세션 관리, 로그아웃 처리" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, documentation, sdlc, planning] - related_skills: [bootstrap-baseline, review-discovery, confirm-discovery, run-sdlc] ---- - -# Purpose - -This skill decides whether an incoming request should update an existing Discovery document or start a new one, then drafts or updates Discovery documentation based on issues, user requests, meeting notes, or repository state. - -입력이 없으면 현재 Discovery/SRS/batch/release 상태와 baseline 문서 상태를 읽어 새 Discovery가 필요한지 판정한다. baseline 초기화가 아직 끝나지 않은 저장소에서는 먼저 `bootstrap-baseline`으로 `docs/project-structure.md`, `docs/runtime-flows.md`, active index를 준비한 뒤 첫 real Discovery를 생성한다. - -새 Discovery를 생성하기로 판정된 경우 generated output must follow this structure: - -- `docs/discovery/dcy-__.md` -- Update `docs/discovery/index.md` - -# When to use - -다음 상황에서 사용한다. - -- 새로운 SDLC 주기를 시작해야 할 때 -- 기존 SDLC의 요구사항, 범위, 성공 기준을 조정해야 하는데 새 주기 생성이 필요한지 먼저 판정해야 할 때 -- `/new-discovery ...` 또는 파라미터 없는 `/new-discovery`로 Discovery 초안을 만들어야 할 때 -- baseline 초기화가 완료된 뒤 첫 real Discovery를 시작해야 할 때 -- 이슈/요청사항을 `docs/discovery/` 아래 표준 Discovery 문서로 전환해야 할 때 -- Discovery 문서를 템플릿 기반으로 작성하되, 추론 가능한 내용을 실제 문장으로 채워야 할 때 -- `docs/discovery/index.md`에 새 Discovery 링크를 추가해야 할 때 - -# Inputs - -이 skill은 아래 입력을 사용한다. - -- 이슈 식별 정보 - - GitHub issue link 또는 issue 번호 -- 이슈 내용 - - 사용자 입력 원문 또는 이슈 본문 전체 - - 이슈 제목 또는 요청 요약 -- SDLC 문맥 - - 기존 `docs/discovery/` 폴더 구조 및 파일 목록 - - 기존 `docs/srs/`, `docs/batches/`, `docs/releases/` 상태 - - 기존 `docs/discovery/index.md` 내용 - - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부 - - 현재 시각 (KST) -- 저장소 문맥 - - 현재 저장소 상태 - - 저장소 현황 탐색 결과 -- 템플릿의 논리 경로 - - `stage-pilot/templates/discovery/discovery.md` - - `stage-pilot/templates/discovery/index.md` - - `stage-pilot/templates/project-structure.md` - - `stage-pilot/templates/runtime-flows.md` - -## Template path resolution - -- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. -- 물리 경로는 다음 순서로 해석한다. - 1. `.stage-pilot/templates/...` - 2. `~/.stage-pilot/templates/...` -- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. -- 템플릿 source path와 생성 target path를 혼동하지 않는다. - - source: `stage-pilot/templates/...` - - target: `docs/discovery/.md`, `docs/discovery/index.md`, 이후 필요 시 참조하는 `docs/project-structure.md`, `docs/runtime-flows.md` - -입력 해석 규칙: -- GitHub issue link 또는 issue 번호가 있으면 해당 이슈의 제목, 본문, 링크를 기준 입력으로 사용한다. -- 사용자 입력 원문과 요청 요약은 이슈 링크가 없거나 보조 설명이 필요할 때 함께 사용한다. -- 파라미터가 없으면 저장소 상태를 먼저 분류한 뒤 추천 Discovery 또는 진행 중인 반복 유지 여부를 판단한다. -- 저장소 현황 탐색 결과는 `# 3. 문제점의 요약` 중 `현재 상태` 작성에 사용한다. - -# Core Rules - -## 0. Baseline 문서 정책 - -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 SDLC의 별도 governance unit가 아니라, 이후 Discovery/REQ/Batch/Release에서 공통으로 참조하는 cross-cutting baseline 문서다. -- baseline 초기화는 첫 Discovery와 분리된 `bootstrap-baseline` 경로를 기본값으로 사용한다. -- baseline 문서와 active index가 모두 없는 저장소에서는 `new-discovery`가 Discovery를 생성하기 전에 `bootstrap-baseline`을 우선 추천하고 중단한다. -- baseline 문서가 이미 있고, Discovery가 구조/런타임 변경을 다룰 때만 해당 Discovery 안에 baseline 문서 갱신 요구를 포함한다. - -## 1. 파라미터 없는 실행 판정 규칙 - -- `/new-discovery`가 파라미터 없이 실행되면 먼저 아래 경로를 점검한다. - - `docs/discovery/` - - `docs/srs/` - - `docs/batches/` - - `docs/releases/` - - `docs/project-structure.md` - - `docs/runtime-flows.md` -- 파라미터 없는 실행은 아래 3가지 저장소 상태 중 하나로 판정한다. - - `no-history`: Discovery와 후속 SDLC 문서가 사실상 없고 첫 반복을 시작해야 하는 상태 - - `active-cycle`: draft/review Discovery, Proposed REQ, 진행 중 batch, 미정리 release처럼 활성 unit가 존재해 파라미터 없는 실행만으로는 다음 반복 추천이 모호한 상태 - - `ready-for-next-iteration`: active unit는 없고 새 반복을 시작할 준비가 된 상태 -- `no-history`이고 baseline 문서 또는 active index가 없으면 `bootstrap-baseline`을 우선 추천하고 새 Discovery를 생성하지 않는다. -- `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery를 생성한다. -- `active-cycle`이면 새 Discovery를 생성하지 않고 중단한다. 대신 현재 반복을 계속 진행하도록 `run-sdlc` 또는 해당 후속 skill을 추천한다. -- `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 차이를 읽고 추천 follow-up Discovery를 생성한다. -- `ready-for-next-iteration`에서 후보 ranking만 먼저 필요하면 `suggest-next-discovery`를 사용하고, 실제 문서 생성이 확정되면 `new-discovery`로 이어 간다. -- baseline 문서가 없고 active unit도 없다면, follow-up Discovery보다 `bootstrap-baseline`을 우선한다. - -## 2. 기존 Discovery 갱신 vs 새 Discovery 생성 판정 규칙 - -- 기존 주기 후보 탐색과 비교는 아래 `확인 절차`를 따른다. -- 후보 문서가 있고 아래 조건을 모두 만족하면 기존 Discovery를 갱신한다. - - 변경이 기존 문제 정의와 핵심 사용자 시나리오를 유지한다. - - FR/NFR 변경이 문구 명확화, 측정 가능성 보강, 범위 경계의 미세 조정, 우선순위 재정렬 수준이다. - - 요구사항 삭제 또는 수정이 기존 REQ, batch planning, batch design, implementation 산출물을 사실상 무효화하지 않는다. - - 대상 Discovery가 아직 초기 정리 단계이거나, 후속 단계 문서가 있어도 변경 영향이 국소적이라 동일 문서 안에서 추적 가능하다. -- 아래 조건 중 하나라도 만족하면 새 Discovery를 생성한다. - - FR/NFR 삭제 또는 수정이 범위, 이해관계자, 핵심 사용자 시나리오, 성공 기준을 실질적으로 바꾼다. - - 기존 하위 단계 문서나 구현 결과를 다시 써야 할 정도로 설계/구현 전제가 깨진다. - - 기존 Discovery가 사실상 Freeze 이후 단계로 진행되어 변경 이력을 같은 문서에 계속 누적하면 책임 경계가 흐려진다. - - 운영 환류, 새로운 이슈, 정책 전환 등으로 후속 반복을 독립 주기로 추적하는 편이 명확하다. -- 기존 Discovery를 더 이상 기준으로 쓰지 않더라도 기존 문서를 삭제하지 않는다. 새 문서를 생성하는 경우 기존 문서는 참조 대상으로 유지하고, 대체 또는 후속 관계를 문서에 남긴다. -- 판정 결과는 반드시 아래 둘 중 하나로 명시한다. - - `기존 Discovery 갱신` - - `새 Discovery 생성` - -### 확인 절차 - -- 입력 원문 또는 추천 Discovery 주제에서 아래 식별 정보를 먼저 추출한다. - - GitHub issue 번호 또는 링크 - - 이슈 제목의 핵심 명사 - - 기능명, 모듈명, 동사, 영향 범위 키워드 -- `docs/discovery/` 아래 기존 문서를 탐색할 때는 `.stage-pilot/` 경로를 제외하고 아래 순서로 후보를 찾는다. - - issue 번호 또는 링크의 정확 일치 - - 이슈 제목과 파일명 slug의 핵심 키워드 일치 - - 기존 Discovery 문서 안의 문제 정의, FR/NFR, 범위 경계 키워드 일치 -- 후보 문서가 하나 이상 발견되면 각 후보마다 아래 항목을 비교한다. - - 같은 문제 정의 또는 같은 기능 묶음을 다루는지 - - 현재 단계가 Discovery, REQ 정제, batch delivery, release 이후 어디까지 진행됐는지 - - 이번 변경이 기존 FR/NFR의 미세 조정인지, 핵심 요구사항 폐기 또는 재정의인지 - - 기존 하위 단계 문서와 구현 산출물을 유지 가능한지, 다시 작성해야 하는지 -- 후보가 없으면 기본 판정은 `새 Discovery 생성`이다. -- 후보가 하나이고 판정 규칙에 명확히 부합하면 해당 후보를 기준으로 자동 판정한다. -- 후보가 여러 개이거나, 하나의 후보라도 `기존 Discovery 갱신`과 `새 Discovery 생성` 사이에서 명확히 갈리지 않으면 임의로 진행하지 않는다. - - 후보 문서 경로 목록과 각 후보의 비교 근거를 사용자에게 보고한다. - - 사용자 확인 전에는 새 폴더 생성이나 기존 문서 갱신을 하지 않는다. -- 최종 판정 결과에는 선택된 대상 문서 경로 또는 새로 만들 Discovery 생성 사유를 반드시 함께 기록한다. - -## 3. Discovery ID 규칙 - -- `docs/discovery/` 아래 기존 문서 중 `dcy-<3자리>_` 패턴의 최대 번호를 찾는다. -- 다음 번호를 3자리 zero-pad로 계산한다. -- 오늘 날짜를 `YYYYMMDD` 형식으로 구한다. -- 사용자 입력 원문, 추천 Discovery 제목, 이슈 본문 전체, GitHub issue link에서 `topic-slug`를 만든다. -- `topic-slug`는 소문자 kebab-case의 형태로 만든다. -- slug 규칙: - - 영문/숫자 외 문자는 `-`로 치환 - - 연속 `-`는 하나로 축약 - - 시작/끝 `-` 제거 - - 결과가 비면 `untitled` 사용 -- 최종 `DISCOVERY_ID`는 `dcy-__` 형식이어야 한다. - -## 4. 문서 생성/갱신 규칙 - -- 판정 결과가 `새 Discovery 생성`이면 새 파일을 `docs/discovery/.md` 경로로 생성한다. -- `새 Discovery 생성`인 경우 `stage-pilot/templates/discovery/discovery.md`에 해당하는 실제 template source를 해석한 뒤 Discovery 문서를 생성한다. -- `docs/discovery/index.md`가 없으면 `stage-pilot/templates/discovery/index.md`에 해당하는 실제 template source를 해석한 뒤 생성한다. -- Discovery가 구조/런타임 baseline 갭을 직접 다루는 경우, `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 이후 산출물의 논리 템플릿 참조로 취급한다. -- `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. -- 동일 경로가 이미 있으면 덮어쓰지 말고 다음 ID를 재계산한다. -- 불필요한 파일은 만들지 않는다. - -## 5. Discovery 초안 작성 원칙 - -- 입력 문장이 짧더라도 기능 목적, 예상 사용자, 산출물, 성공 조건을 합리적으로 추론해 초안을 작성한다. -- 입력만으로 추론 가능한 내용은 플레이스홀더로 남기지 말고 실제 문장으로 채운다. -- 사용자 승인, 외부 데이터, 정책 결정, 확정 범위 경계처럼 추론만으로 확정하면 안 되는 항목만 플레이스홀더로 남긴다. -- 공란은 허용하지 않는다. -- 합리적 기본안을 제시할 수 있으면 초안 문장으로 채운다. -- 사용자 확인이 반드시 필요하면 관련 본문에는 최소 플레이스홀더를 남기고 `# 10. 사용자 결정 필요 항목 요약`에 정리한다. -- `기존 Discovery 갱신`인 경우 FR/NFR 변경 또는 삭제 이유, 영향 받는 하위 단계, 기존 문서와의 차이를 문서 안에 명시해 변경 근거를 남긴다. -- Discovery가 구조/런타임 baseline 변경을 직접 다루는 경우 `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 9. 파일 처리 결과`에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 명시한다. - -## 6. 플레이스홀더 유지 규칙 - -- 사용자 승인 전 확정되면 안 되는 정책/범위/산출물 위치/대상 독자 결정은 본문에도 플레이스홀더를 남긴다. -- `# 10. 사용자 결정 필요 항목 요약`의 항목은 반드시 아래 형식 중 하나를 사용한다. - - `{{DECIDE_*}}` - - `{{CONFIRM_*}}` - - `{{DATA_*}}` -- `TBD` 같은 일반 문자열보다 의미가 드러나는 플레이스홀더를 우선 사용한다. - -# Discovery Sections To Fill - -다음 섹션은 자동으로 채워야 한다. - -## `# 1. 계획 상태 요약` - -- `해석` 2줄은 입력 요청 또는 파라미터 없는 실행의 저장소 상태 해석을 바탕으로 직접 작성한다. - -## `# 0. 문서 상태` - -- `새 Discovery 생성`이면 `대체됨`, `후속 주기 참조` 기본값은 `없음`으로 둔다. -- 기존 Discovery를 대체하거나 이어받는 새 Discovery를 만드는 경우, 이전 Discovery 문서에도 해당 참조 필드를 갱신한다. -- `기존 Discovery 갱신`이면 기존 참조 값을 보존하고, 새 관계가 확정된 경우에만 관련 필드를 갱신한다. - -플레이스홀더 치환 규칙: -- `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` -- `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 -- `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 - -## `# 2. 요구사항 판정 결과` - -- 기본값은 `신규`로 둔다. -- 입력만으로 명백한 중복/유사라고 판단 가능한 경우에만 다르게 쓴다. -- 근거 2줄도 직접 작성한다. - -## `# 3. 문제점의 요약` - -- 현재 상태, 기대 상태, 이해관계자, 주요 사용자 시나리오를 입력 기반으로 채운다. -- 특히 `현재 상태`는 저장소 탐색 결과와 baseline 문서 존재 여부를 반영해야 한다. - -## `# 4. 이번에 정의할 변경` - -- 핵심 변경 항목 1~3개를 직접 작성한다. -- Discovery가 구조 또는 runtime baseline 갭을 다루면 baseline 문서 갱신을 핵심 변경 항목에 포함한다. -- `영향 범위`의 코드/문서/운영 영향도 추론해 채운다. - -## `# 5. 요구사항 목록` - -- FR 2개 이상, NFR 1개 이상을 직접 작성한다. -- Discovery가 구조 또는 runtime baseline 갭을 다루는 경우, `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 FR에 포함한다. -- In Scope/Out of Scope는 초안 수준으로 제안한다. -- 사용자 승인 없이는 확정할 수 없는 범위 경계는 플레이스홀더로 남긴다. - -## `# 6. 리스크/가정 목록` - -- 리스크 2개 이상, 가정 2개 이상을 직접 작성한다. -- 오픈 질문은 정말 결정이 필요한 경우에만 남긴다. - -## `# 7. 초기 성공 기준` - -- 성공 기준 2개 이상을 작성한다. -- Discovery가 구조 또는 runtime baseline 변경을 다루는 경우 baseline 문서와 이후 산출물 참조 가능성을 성공 기준에 연결한다. -- 각 성공 기준에 대해 측정 방식과 데이터 출처 초안을 작성한다. - -## `# 9. 파일 처리 결과` - -- 생성 결과와 최소 참조 문서를 채운다. -- Discovery가 구조 또는 runtime baseline 갭을 다루면 `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 참조 문서에 포함한다. -- 참조 문서가 없으면 `없음`이라고 쓴다. - -## `# 10. 사용자 결정 필요 항목 요약` - -- 자동 추론으로 확정할 수 없는 항목만 `DECIDE`, `CONFIRM`, `DATA`에 넣는다. -- 이 섹션 항목은 설명문이 아니라 플레이스홀더 형식으로 남긴다. - -## `# 11. Discovery Freeze` - -- `Handoff Decision` 기본값은 `보류`다. -- `Handoff Rationale`에는 현재 상태를 짧게 적는다. - - 예: `초기 Discovery 초안 생성 완료, REQ 초안 작성 전 사용자 확인 필요` - -# Repository Exploration - -Discovery 문서의 `# 3. 문제점의 요약` 중 `현재 상태`를 채우기 위해 저장소 현황 탐색을 수행한다. - -1. 이슈 원문 또는 추천 주제에서 핵심 키워드(기능명, 모듈명, 동사)를 추출한다. -2. 키워드를 기반으로 저장소에서 관련 파일, 디렉터리, 함수, 기존 문서를 탐색한다. -3. 탐색 범위는 아래와 같다. - - 소스 파일: `.ts`, `.js`, `.py`, `.go`, `.java` 등 - - 설정 파일 - - 기존 `docs/` 문서 -4. 아래 항목은 제외한다. - - 테스트 파일 - - 의존성 잠금 파일 (`package-lock.json`, `.lock`) -5. 탐색 결과에서 아래 내용을 추출한다. - - 관련 파일 경로 목록 (최대 10개) - - 현재 구현 방식 요약 (함수명, 클래스명, 주요 로직 1~3줄) - - 현재 상태에서 이슈 원문이 지적하는 문제가 실제로 존재하는지 여부 - - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부와 최신성 판단에 필요한 단서 -6. 관련 파일을 찾지 못하면 `관련 파일 없음 — 신규 기능으로 추정`으로 기록한다. -7. 탐색 결과는 `# 3. 문제점의 요약`의 `현재 상태` 기술에만 반영한다. - -# Execution Procedure - -다음 순서대로 작업한다. - -1. 입력 원문이 있으면 이슈명을 한 문장으로 정리하고, 없으면 저장소 상태를 먼저 분류한다. -2. 파라미터 없는 실행이면 discovery/srs/batches/releases/baseline 문서 상태를 읽어 `no-history`, `active-cycle`, `ready-for-next-iteration` 중 하나로 판정한다. -3. 입력 원문이 없고 `active-cycle`이면 새 Discovery 자동 생성을 중단하고 현재 반복을 계속 진행해야 하는 이유와 추천 skill을 보고한다. -4. `no-history`이고 baseline 문서 또는 active index가 비어 있으면 `bootstrap-baseline`을 우선 추천하고 Discovery 생성을 중단한다. -5. `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery 생성 대상으로 확정한다. -6. 입력 원문이 없고 `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 상태를 바탕으로 추천 follow-up Discovery 주제를 정한다. -7. 입력 원문, 이슈 링크, 신규 요구 요약처럼 명시적 신규 입력이 있으면 `active-cycle` 여부와 무관하게 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. -8. 입력 원문이 없고 추천 Discovery 주제가 정해졌으면 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. -9. 후보 비교 결과를 바탕으로 `기존 Discovery 갱신` 또는 `새 Discovery 생성`으로 판정한다. -10. `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 진행하는 경우, 기존 active unit와의 범위 충돌 여부와 병렬 진행 사유를 함께 기록한다. -11. 판정이 애매하면 사용자 확인 전까지 자동 생성 또는 자동 갱신을 중단하고 후보와 근거를 보고한다. -12. 저장소 현황 탐색을 수행한다. -13. 판정 결과가 `새 Discovery 생성`이면 기존 `docs/discovery/` 경로를 다시 스캔해 다음 번호를 계산한다. -14. `새 Discovery 생성`이면 날짜와 slug를 조합해 `DISCOVERY_ID`를 만든다. -15. `새 Discovery 생성`이면 `docs/discovery/.md` 파일을 생성한다. -16. `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. -17. 아래 플레이스홀더를 우선 치환한다. - - `{{DOC_STATUS:draft|review|confirmed}}` -> `draft` - - `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` - - `{{ISSUE_NAME:짧은 한글 또는 영문 이슈명}}` -> 입력 원문 또는 요약 이슈명 - - `{{ISSUE_NAME}}` -> 입력 원문 또는 요약 이슈명 - - `{{CREATED_AT_KST:TBD}}` -> 현재 시각(KST) - - `{{CREATED_AT_KST}}` -> 현재 시각(KST) - - `{{UPDATED_AT_KST:TBD}}` -> 현재 시각(KST) - - `{{UPDATED_AT_KST}}` -> 현재 시각(KST) - - `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 - - `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 - - `{{OUTPUT_PATH}}` -> 생성 파일 경로 - - `{{FILE_RESULT:생성|갱신|미생성}}` -> `생성` -18. Discovery가 구조 또는 runtime baseline 갭을 직접 다루면 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 핵심 변경과 FR에 반영한다. -19. `기존 Discovery 갱신`인 경우 `{{OUTPUT_PATH}}`와 `{{FILE_RESULT}}`는 갱신 대상 파일과 `갱신`으로 해석한다. -20. `새 Discovery 생성`이면서 기존 Discovery를 대체하거나 이어받는 경우, 기존 Discovery 문서에 `대체됨` 또는 `후속 주기 참조` 필드를 새 문서 경로로 갱신한다. -21. Discovery 문서에서는 추론 가능한 플레이스홀더를 적극적으로 실제 내용으로 치환한다. -22. 추론 불가능하거나 사용자 확인이 필요한 항목만 플레이스홀더로 남긴다. -23. `새 Discovery 생성`인 경우에만 `docs/discovery/index.md` 전역 인덱스에 새 행을 추가한다. -24. `기존 Discovery 갱신`인 경우 전역 인덱스에 새 행을 추가하지 않는다. - -# Global Index Rules - -- `docs/discovery/index.md`가 없으면 아래 골격으로 새로 만든다. -- `## Discovery 목록`에는 새 행을 추가한다. -- 형식은 아래와 같다. - -`| | | | [Discovery](./.md) | draft |` - -- 테이블 구조 자체는 바꾸지 않는다. -- `기존 Discovery 갱신`으로 판정된 경우 `docs/discovery/index.md`에 새 행을 추가하지 않는다. -- 같은 `DISCOVERY_ID`가 이미 있으면 중복 추가하지 않는다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 판정 결과 (`기존 Discovery 갱신` 또는 `새 Discovery 생성`) -- 파라미터 없는 실행이면 저장소 상태 분류 (`no-history` | `active-cycle` | `ready-for-next-iteration`) -- `active-cycle` 상태였다면 자동 중단 사유 또는 병렬 Discovery 허용 사유 -- 대상 `DISCOVERY_ID` -- 확인에 사용한 후보 Discovery 목록과 각 후보의 비교 근거 -- 새 Discovery인 경우 생성한 파일 경로 -- 기존 Discovery 갱신인 경우 갱신한 파일 경로 -- 기존 Discovery와의 대체 또는 후속 참조 갱신 결과 -- `docs/discovery/index.md` 갱신 결과 또는 미갱신 사유 -- `docs/project-structure.md`, `docs/runtime-flows.md` baseline 상태와 Discovery 반영 결과 -- 저장소 현황 탐색 결과 -- 자동으로 채운 Discovery 핵심 섹션 요약 -- 사용자 확인이 필요해 플레이스홀더로 남긴 항목 요약 - -# Validation - -완료 전에 아래를 확인한다. - -- `새 Discovery 생성`이면 생성 경로가 `docs/discovery/.md` 형식을 만족하는지 -- `새 Discovery 생성`이면 Discovery 문서가 생성되었는지 -- `새 Discovery 생성`이면 `docs/discovery/index.md`가 신규 생성 또는 올바르게 갱신되었는지 -- Discovery가 구조 또는 runtime baseline 갭을 다루면 `docs/project-structure.md`, `docs/runtime-flows.md` 갱신 요구가 문서 본문에 포함됐는지 -- `기존 Discovery 갱신`이면 대상 Discovery 문서만 갱신되었는지 -- `기존 Discovery 갱신`이면 새 `DISCOVERY_ID`와 불필요한 파일이 생성되지 않았는지 -- 기존 Discovery 후보 탐색 결과와 판정 근거가 보고 가능한 형태로 정리되었는지 -- 파라미터 없는 실행에서 `active-cycle` 상태라면 사용자 확인이나 명시적 신규 입력 없이 새 Discovery를 자동 생성하지 않았는지 -- `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 생성한 경우, 기존 active unit와의 범위 충돌 여부와 판정 근거가 함께 정리됐는지 -- 판정이 애매한 경우 사용자 확인 없이 새 폴더 생성이나 기존 문서 갱신을 진행하지 않았는지 -- `대체됨`, `후속 주기 참조` 필드가 `없음` 또는 유효한 Discovery 경로로 채워졌는지 -- 새 Discovery가 기존 Discovery의 대체 또는 후속 반복이라면 기존 Discovery의 참조 필드가 함께 갱신되었는지 -- 추론 가능한 섹션은 실제 문장으로 채워졌는지 -- 사용자 확인이 필요한 항목만 플레이스홀더로 남았는지 \ No newline at end of file diff --git a/.stage-pilot/skills/review-discovery/SKILL.md b/.stage-pilot/skills/review-discovery/SKILL.md deleted file mode 100644 index 5b6b4e0..0000000 --- a/.stage-pilot/skills/review-discovery/SKILL.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -name: review-discovery -description: "Use when: reviewing an existing Discovery document for missing content, additional OQ candidates, internal consistency, duplication, unnecessary content, placeholder residue, and REQ handoff readiness without changing lifecycle state." -version: 0.8.0 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_topic.md" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, review, quality, sdlc] - related_skills: [new-discovery, confirm-discovery, draft-req] ---- - -# Purpose - -This skill reviews an existing Discovery document as a non-gating quality check before `confirm-discovery` or before human review. It looks for missing content, candidate open questions, internal inconsistencies, duplicated statements, unnecessary text, placeholder residue, and weak handoff readiness. - -이 skill은 별도 lifecycle stage를 만들지 않는다. 기본 목적은 Discovery 품질을 점검하고 수정 우선순위를 정리하는 것이며, 문서 상태 전환이나 index 갱신은 수행하지 않는다. - -# When to use - -- `/review-discovery dcy-xyz` 형태로 Discovery 문서를 먼저 점검하고 싶을 때 -- `confirm-discovery` 전에 누락, 중복, OQ 후보, 표현 불일치를 빠르게 정리하고 싶을 때 -- Discovery 문서가 길어지면서 같은 내용이 여러 섹션에 흩어졌는지 확인해야 할 때 -- 템플릿 잔여 문구, 불필요한 상세 구현 설명, 근거 없는 확정 표현이 남아 있는지 보고 싶을 때 -- 사람이 수정할 항목과 AI가 바로 정리할 수 있는 항목을 분리해서 보고 싶을 때 - -# Inputs - -- Discovery 식별자 - - `dcy-001` 같은 prefix - - `dcy-001_20260424_topic-slug` 같은 전체 ID - - `docs/discovery/.md` 같은 직접 경로 -- 대상 Discovery 문서 본문 -- 필요 시 관련 저장소 문맥 - - 현재 저장소의 관련 파일, 문서, 설정 - - `docs/discovery/index.md` - - `docs/project-structure.md`, `docs/runtime-flows.md` -- 현재 시각 (KST) - -# Core Rules - -## 1. 역할 경계 - -- 이 skill은 review helper이며, active SDLC 단계로 취급하지 않는다. -- 아래 항목은 수정하지 않는다. - - `# 0. 문서 상태`의 `상태` - - `# 11. Discovery Freeze`의 `Handoff Decision`, `Ready for REQ Drafting`, `Confirmed By`, `Confirmed At (KST)` - - `docs/discovery/index.md` -- 이 skill은 REQ 생성, 승인, 상태 승격을 하지 않는다. -- 사실 관계를 바꾸는 추정성 수정은 하지 않는다. 근거 없는 항목은 finding 또는 OQ 후보로 남긴다. -- 사용자가 단순 검토만 요청한 경우 결과는 findings-first로 보고한다. -- 사용자가 `정리`, `수정`, `보강`, `반영`까지 명시한 경우에만 의미 변화가 없는 경미한 편집을 수행할 수 있다. - -## 2. 기본 검토 축 - -반드시 아래 항목을 검토한다. - -### 2-1. 누락 항목 - -- 템플릿 기준으로 필요한 섹션과 핵심 내용이 빠지지 않았는지 확인한다. -- 빈 bullet, `TBD`, `{{...}}`, `필요 없으면 삭제` 잔여 문구를 누락 또는 미정리 항목으로 본다. -- 아래 핵심 정보가 약하거나 비어 있으면 finding으로 기록한다. - - 문제의 현재 상태와 기대 상태 - - 핵심 변경 항목과 영향 범위 - - FR/NFR와 범위 경계 - - 리스크, 가정, 성공 기준 - - REQ handoff 전 확인 기준 -- 단, `# 2. 요구사항 판정 결과`가 `완전 중복`이면 템플릿 가이드에 따라 이후 섹션 축약을 허용한다. 이 경우 `# 9. 파일 처리 결과`까지의 중복 판정 근거와 참조만 충분한지 본다. - -### 2-2. 추가 OQ 후보 - -- 아래 조건이면 새 OQ 후보를 제안한다. - - 범위 경계가 모호해 In Scope/Out of Scope가 흔들리는 경우 - - 성공 기준은 있는데 측정 방식이나 데이터 출처가 없는 경우 - - 리스크 완화 책임자나 판단 주체가 비어 있는 경우 - - 외부 정책, 외부 시스템, 운영 절차, 승인자 결정이 필요한데 본문에 근거가 없는 경우 - - 서로 다른 섹션이 충돌해서 사람 판단 없이는 확정할 수 없는 경우 -- 이미 다른 섹션에서 답이 있는 질문은 OQ로 늘리지 말고, answered cleanup 대상으로 분류한다. -- 사소한 문장 다듬기나 서식 문제는 OQ로 만들지 않는다. - -### 2-3. 문서 일관성 - -- 이슈명, 핵심 변경, FR/NFR, 범위 경계, 성공 기준, Freeze rationale 사이의 용어와 범위가 일치하는지 확인한다. -- `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 본다. -- `Open` OQ가 남아 있는데 Freeze가 사실상 진행 가능처럼 읽히는지 확인한다. -- 문서 안에서 같은 개념을 서로 다른 이름으로 부르는 경우 정규화 필요 finding으로 기록한다. - -### 2-4. 중복 제거 대상 - -- 같은 사실이나 요구를 여러 섹션에서 반복하는지 찾는다. -- 중복은 아래 세 종류로 분류한다. - - 완전 중복: 문장만 다르지만 의미가 같은 경우 - - 부분 중복: 핵심은 같고 세부만 다른 경우 - - 잘못된 재기술: 상위 요약과 하위 상세가 서로 다른 말을 하는 경우 -- 중복이 있으면 어떤 섹션을 source of truth로 둘지 함께 제안한다. - -### 2-5. 불필요한 내용 - -- Discovery 단계에 필요 없는 구현 디테일, 과도한 알고리즘 설계, 배포 절차 상세, 이미 결정된 이력 서술을 찾는다. -- 현재 반복의 문제 정의와 성공 기준에 기여하지 않는 배경 설명은 삭제 또는 압축 후보로 본다. -- 템플릿 가이드 문구, 예시 텍스트, 삭제 예정 placeholder 설명이 본문에 남아 있으면 제거 대상으로 본다. - -## 3. 추가 검토 항목 - -사용자 요청 외에도 아래를 함께 본다. - -### 3-1. 근거와 사실 일치성 - -- 문서가 현재 저장소 상태를 서술하는 경우, 그 주장에 직접 관련된 파일이나 문서와 모순이 없는지 확인한다. -- 저장소 확인은 문서의 핵심 주장과 직접 연결된 범위만 탐색한다. 구현 전체를 감사하지는 않는다. -- 저장소 근거와 충돌하는 서술은 높은 우선순위 finding으로 기록한다. - -### 3-2. 실행 가능성과 측정 가능성 - -- FR/NFR, 성공 기준, 체크 항목이 다음 단계에서 실제 REQ/검증 항목으로 이어질 정도로 구체적인지 본다. -- `개선한다`, `지원한다`, `정리한다`처럼 모호한 동사만 있고 판정 기준이 없으면 구체화 필요 finding으로 기록한다. -- 성공 기준은 가능한 한 측정 방식과 데이터 출처에 연결되어야 한다. - -### 3-3. 플레이스홀더 위생 - -- 사용자 결정이 필요한 placeholder와 단순 미정리 placeholder를 구분한다. -- 같은 의미의 placeholder가 본문과 `# 10. 사용자 결정 필요 항목 요약`에 중복으로 남아 있으면 정리 대상으로 본다. -- 이미 문서 근거로 해소 가능한 placeholder는 unresolved로 방치하지 않는다. - -## 4. 허용되는 편집 범위 - -사용자가 수정까지 요청한 경우에만 아래 경미한 편집을 허용한다. - -- 중복 문장 삭제 또는 압축 -- 템플릿 잔여 문구 제거 -- 번호, 체크리스트, 링크, 용어 표기 일관성 정리 -- 답이 이미 문서 안에 있는 OQ를 본문 반영 후 resolved cleanup으로 정리 -- 의미 변화 없는 섹션 재배치 -- 실제 내용이 바뀌었을 때 `마지막 갱신 시각(KST)` 갱신 - -아래는 이 skill로 수정하지 않는다. - -- 새 요구사항 생성 -- 범위 재정의 -- 정책 확정 -- 승인자 지정 -- 상태 전환과 index 갱신 - -# Review Heuristics By Section - -섹션별로 최소한 아래를 점검한다. - -- `# 0. 문서 상태` - - 문서 ID, 이슈명, 시각 정보, 참조 필드가 자기모순 없는지 확인한다. -- `# 1. 계획 상태 요약` - - 현재 작성 배경과 이번 Discovery의 핵심 판단 포인트가 2줄 요약으로 드러나는지 본다. -- `# 2. 요구사항 판정 결과` - - 신규/유사/중복 판정과 근거가 실제 본문 범위와 맞는지 본다. -- `# 3. 문제점의 요약` - - 현재 상태, 기대 상태, 이해관계자, 시나리오가 왜 이 Discovery가 필요한지를 설명하는지 본다. -- `# 4. 이번에 정의할 변경` - - 변경 항목과 영향 범위가 `# 5` 요구사항과 연결되는지 본다. -- `# 5. 요구사항 목록` - - FR/NFR가 구분되어 있고 범위 경계가 충돌하지 않는지 본다. -- `# 6. 리스크/가정 목록` - - 리스크, 가정, OQ가 각각 역할에 맞게 쓰였는지 본다. -- `# 7. 초기 성공 기준` - - 측정 대상, 방식, 데이터 출처가 요구사항과 연결되는지 본다. -- `# 8. REQ로 넘기기 전 확인 체크` - - 체크 항목이 선언만 있고 판단 기준이 없는지 본다. -- `# 9. 파일 처리 결과` - - 참조 문서가 실제 검토 근거를 반영하는지 본다. -- `# 10. 사용자 결정 필요 항목 요약` - - DECIDE, CONFIRM, DATA가 실제 unresolved 항목과 맞는지 본다. -- `# 11. Discovery Freeze` - - review 결과와 무관하게 상태 전환 없이, 본문과 논리적 충돌만 점검한다. - -# Execution Procedure - -1. 입력에서 Discovery 문서 경로를 확정한다. -2. 대상 Discovery 문서를 읽고, 필요 시 `docs/discovery/index.md`와 직접 관련된 저장소 문맥을 읽는다. -3. 문서를 `중복 판정으로 축약 가능한 문서`인지 `정상 Discovery 검토 대상`인지 먼저 분류한다. -4. 위 `기본 검토 축`과 `추가 검토 항목` 기준으로 findings를 수집한다. -5. findings를 아래 우선순위로 분류한다. - - `blocking`: REQ handoff 또는 사실 관계에 직접 문제를 만드는 항목 - - `major`: 지금 고치는 편이 좋은 구조/일관성/측정 가능성 문제 - - `minor`: 표현, 중복, 서식, 경미한 cleanup 문제 -6. 추가 OQ 후보가 있으면 기존 OQ와 중복 여부를 제거한 뒤 후보 목록으로 정리한다. -7. 사용자가 수정까지 요청한 경우에만 `허용되는 편집 범위` 안에서 경미한 cleanup을 수행한다. -8. 편집을 수행한 경우 `마지막 갱신 시각(KST)`만 갱신하고, 상태 전환 없이 종료한다. -9. 최종적으로 findings, OQ 후보, auto-fix 결과, 다음 추천 행동을 보고한다. - -# Output Expectations - -작업이 끝나면 아래 내용을 보고할 수 있어야 한다. - -- 대상 `DISCOVERY_ID`와 문서 경로 -- 전체 판정 - - `양호, 바로 confirm-discovery 검토 가능` - - `경미한 정리 필요` - - `추가 보강 필요` - - `문서 구조 또는 사실 관계 재정비 필요` -- severity별 findings 목록 -- 새 OQ 후보 목록 -- 중복/불필요 내용 정리 제안 -- 자동 정리한 항목 목록(수정 요청이 있었을 때만) -- 다음 추천 행동 - - `confirm-discovery` 진행 - - Discovery 문서 추가 수정 후 재검토 - - 범위가 바뀌어 `new-discovery` 또는 `change-req` 판단 필요 - -# Validation - -완료 전에 아래를 확인한다. - -- 대상 경로가 실제 Discovery 문서를 가리키는지 확인한다. -- `완전 중복` 예외를 제외하고, 누락 판단이 템플릿 구조와 충돌하지 않는지 확인한다. -- 제안한 OQ가 이미 본문이나 다른 섹션에서 답을 가진 항목이 아닌지 확인한다. -- 같은 finding을 누락/중복/불필요 항목에 중복 보고하지 않았는지 확인한다. -- 수정이 있었다면 상태 전환 값과 `docs/discovery/index.md`가 바뀌지 않았는지 확인한다. -- 저장소 사실 관계와의 충돌을 지적했다면 최소 한 개 이상의 직접 근거가 있는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/run-batch-delivery/SKILL.md b/.stage-pilot/skills/run-batch-delivery/SKILL.md deleted file mode 100644 index db06b0e..0000000 --- a/.stage-pilot/skills/run-batch-delivery/SKILL.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -name: run-batch-delivery -description: "Use when: orchestrating the full delivery chain for an existing batch, running /run-batch-delivery with a BAT ID, resuming an in-progress batch from the first incomplete delivery stage, or chaining planning, design, implementation, verification drafting, verification approval, and REQ implementation sync for one selected batch." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: bat-002 또는 docs/batches/bat-002_20260522_visioncfg-32-contract-migration" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, delivery, orchestration, execution, sdlc] - related_skills: [draft-batch-planning, draft-batch-design, run-batch-implementation, draft-batch-verification, confirm-batch-verification, confirm-req-implemented, draft-release] ---- - -# Purpose - -This skill orchestrates the Delivery phase for one already-created batch by chaining the batch-level skills in order until the batch reaches `release-candidate` and its included REQs are synchronized to `Implemented` where evidence exists. - -이 skill은 새 batch를 만들지 않는다. 이미 존재하는 `docs/batches//`를 기준으로 현재 delivery 진행 상태를 읽고, 아래 6단계를 순서대로 이어 붙이는 orchestration entrypoint다. - -1. `draft-batch-planning` -2. `draft-batch-design` -3. `run-batch-implementation` -4. `draft-batch-verification` -5. `confirm-batch-verification` -6. `confirm-req-implemented` - -# When to use - -- `/run-batch-delivery bat-002`처럼 특정 batch를 delivery phase 끝까지 밀고 싶을 때 -- 이미 `draft-batch`까지 끝난 batch를 planning/design/implementation/verification/REQ 상태 동기화까지 이어서 처리할 때 -- 기존 batch가 어느 delivery 단계에서 멈췄는지 확인하고, 첫 incomplete stage부터 재개하고 싶을 때 -- 사람이 REQ 묶음을 이미 선택했고 batch도 생성했으므로, 이제 batch 내부 delivery chain만 집중 실행해야 할 때 - -## Do not use when - -- 아직 batch가 생성되지 않았고 포함 REQ 집합 선택도 끝나지 않았을 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) -- 단순히 batch의 planning만 채우거나 design만 보강하려는 단일 단계 작업일 때 (해당 개별 skill이 더 적절함) -- verification 승인 이후 release 문서까지 자동으로 이어 가고 싶을 때 (`draft-release`는 별도 단계로 남겨 둔다) -- batch가 아니라 저장소 전체의 다음 SDLC 단계를 판단하려는 상황일 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- `BAT-ID` prefix 또는 batch 경로 -- `docs/batches//index.md` -- `docs/batches//planning.md` -- `docs/batches//design.md` (존재하는 경우) -- `docs/batches//implementation.md` (존재하는 경우) -- `docs/batches//verification.md` (존재하는 경우) -- 포함된 REQ 문서들 -- `docs/batches/index.md` -- `docs/srs/index.md` - -# Core Rules - -## 1. 배치 단위 orchestration - -- 이 skill은 정확히 하나의 batch를 대상으로 한다. -- 입력 batch가 유일하게 확정되지 않으면 진행하지 않는다. -- batch 밖의 다른 REQ나 다른 batch를 임의로 끌어오지 않는다. - -## 2. 6단계 delivery chain 유지 - -- 기본 chain은 아래 순서를 따른다. - 1. `draft-batch-planning` - 2. `draft-batch-design` - 3. `run-batch-implementation` - 4. `draft-batch-verification` - 5. `confirm-batch-verification` - 6. `confirm-req-implemented` -- 앞 단계 결과가 다음 단계 입력 전제를 만족하지 않으면 그 지점에서 멈추고 blocker를 보고한다. -- `confirm-batch-verification`과 `confirm-req-implemented`는 승인 게이트이므로 evidence 부족 상태를 억지로 통과시키지 않는다. - -## 3. design 생략 규칙 - -- batch profile이 `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 실제로 없을 때만 `draft-batch-design`을 생략할 수 있다. -- 그 외에는 design 단계를 delivery chain에 포함한다. -- 생략 여부는 planning 문서와 batch index 근거로 설명되어야 한다. - -## 4. 중복 실행보다 불완전 단계 우선 - -- 이미 충분히 채워진 단계는 다시 덮어쓰는 것이 목적이 아니다. -- 각 단계는 먼저 현재 batch 문서를 읽고, 실제로 비어 있거나 draft 수준인 stage부터 시작한다. -- 다만 앞 단계 산출물이 뒤 단계 전제를 만족하지 못하면 필요한 범위 내에서 보강할 수 있다. - -## 5. 사람 결정과 승인 게이트 존중 - -- verification evidence가 부족하거나 blocker가 있으면 `confirm-batch-verification`에서 멈춘다. -- REQ acceptance criteria와 evidence 연결이 불충분하면 `confirm-req-implemented`에서 멈춘다. -- release 문서 생성과 release 승인까지 자동으로 넘기지 않는다. 이 skill의 종료점은 delivery phase 완료 직전 또는 완료 직후 상태 보고다. - -## 6. child skill 규칙 상속 - -- 이 skill은 하위 skill들을 대체하지 않는다. -- 각 단계에서는 해당 skill의 prerequisite, validation, blocker 규칙을 그대로 따른다. -- 특히 baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 영향 여부는 design/verification 단계 child skill 규칙을 따라 점검한다. - -# Execution Procedure - -1. 입력에서 `BAT-ID` 또는 batch 경로를 유일하게 확정한다. -2. batch `index.md`, `planning.md`, `design.md`, `implementation.md`, `verification.md`, 포함 REQ, `docs/batches/index.md`, `docs/srs/index.md`를 읽어 현재 delivery 상태를 요약한다. -3. 아래 기준으로 첫 incomplete stage를 판정한다. - - `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` - - design이 필요하지만 `design.md`가 비어 있거나 부족하면 `draft-batch-design` - - 구현 로그/변경/검증 기록이 부족하면 `run-batch-implementation` - - verification mapping/evidence 초안이 부족하면 `draft-batch-verification` - - verification evidence는 있으나 batch가 아직 `release-candidate`가 아니면 `confirm-batch-verification` - - batch가 `release-candidate` 또는 `released`인데 포함 REQ 중 `Approved`가 남아 있으면 `confirm-req-implemented` -4. 첫 incomplete stage부터 시작해, blocker가 생길 때까지 다음 stage로 순차 진행한다. -5. `draft-batch-planning`이 실행되면 범위, dependency, milestone, Design Gate가 채워졌는지 확인한다. -6. `draft-batch-design`이 필요하면 architecture summary, changed areas, key decisions, architecture impact가 채워졌는지 확인한다. -7. `run-batch-implementation` 후에는 구현 변경, execution log, validation, remaining risks가 기록됐는지 확인한다. -8. `draft-batch-verification` 후에는 REQ acceptance criteria와 evidence mapping, blocking issues, baseline 동기화 evidence가 필요한 경우 그 항목이 채워졌는지 확인한다. -9. `confirm-batch-verification` 성공 시 batch가 `release-candidate`로 전환됐는지 확인한다. 실패 시 blocker를 보고하고 멈춘다. -10. `confirm-req-implemented`를 실행해 포함 REQ 중 evidence가 충분한 항목만 `Implemented`로 동기화한다. 부족한 항목은 blocker와 함께 남긴다. -11. delivery chain 종료 후 결과를 아래 중 하나로 보고한다. - - `Stopped at ` + blocker - - `Delivery chain complete through REQ sync` -12. delivery chain이 성공적으로 끝났으면 다음 단계로 `draft-release`를 추천하되, 자동 실행하지는 않는다. - -# Output Expectations - -- 대상 batch 경로 -- 시작 시점 batch 상태 요약 -- 실제로 실행한 stage 목록 -- 생략한 stage와 생략 근거 (`batch-lite` design 생략 등) -- 각 stage 결과 요약 -- 중단 지점과 blocker 또는 완료 결과 -- batch 최종 상태 (`draft` | `in-delivery` | `release-candidate` 등) -- 포함 REQ 중 `Implemented` 전환 성공 목록 / 보류 목록 -- 다음 권장 단계 (`draft-release` 또는 blocker 해소) - -# Common Pitfalls - -1. `draft-batch` 전 batch를 바로 delivery chain에 넣는 실수 - - 이 skill은 이미 생성된 batch가 전제다. REQ 묶음 선택이나 batch 생성은 먼저 끝나 있어야 한다. - -2. `draft-batch-verification`을 건너뛰고 바로 `confirm-batch-verification`으로 가는 실수 - - verification 초안과 evidence mapping이 먼저 있어야 approval gate를 제대로 통과시킬 수 있다. - -3. `batch-lite`라는 이유만으로 design을 자동 생략하는 실수 - - planning의 `Design Gate`가 `no`여야 하고 실제 구조 영향도 없어야 한다. - -4. verification blocker가 있는데도 REQ를 `Implemented`로 올리는 실수 - - `confirm-req-implemented`는 batch verification evidence를 근거로 해야 하므로 blocker가 남아 있으면 보류해야 한다. - -5. child skill이 이미 만든 문서를 읽지 않고 매번 처음부터 다시 쓰는 실수 - - 현재 batch 문서를 먼저 읽고, 첫 incomplete stage부터 이어 가야 한다. - -6. release까지 자동으로 계속 진행하는 실수 - - 이 skill의 역할은 delivery chain orchestration이다. release 문서 생성/승인은 별도 stage다. - -# Verification Checklist - -- [ ] 대상 batch가 유일하게 확정되었다. -- [ ] batch 생성 단계가 이미 완료된 상태다. -- [ ] planning -> design -> implementation -> verification drafting -> verification approval -> REQ sync 순서가 유지되었다. -- [ ] `batch-lite` design 생략은 `Design Gate`와 실제 영향 근거로 설명된다. -- [ ] `confirm-batch-verification`은 blocker 없는 evidence 상태에서만 통과했다. -- [ ] `confirm-req-implemented`는 evidence가 연결된 REQ만 전환했다. -- [ ] stage별 실행 결과와 중단/완료 상태가 보고되었다. -- [ ] release 단계는 자동으로 진행하지 않고 별도 추천으로 남겼다. diff --git a/.stage-pilot/skills/run-batch-implementation/SKILL.md b/.stage-pilot/skills/run-batch-implementation/SKILL.md deleted file mode 100644 index fd76db0..0000000 --- a/.stage-pilot/skills/run-batch-implementation/SKILL.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: run-batch-implementation -description: "Use when: implementing a confirmed batch design, running /run-batch-implementation with a BAT ID, applying code changes for docs/batches/, or updating implementation logs and validation evidence for a batch." -version: 0.7.1 -author: Justin Ko -license: private -argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, implementation, execution, sdlc] - related_skills: [draft-batch-planning, draft-batch-design, draft-batch-verification, confirm-req-implemented] ---- - -# Purpose - -This skill executes the implementation work for a batch, updates code, and records the changed files, execution log, validation, and remaining risks in the batch implementation document. - -# Inputs - -- `BAT-ID` 또는 batch 경로 -- `docs/batches//planning.md` -- `docs/batches//design.md` -- `docs/batches//implementation.md` -- 관련 REQ 문서와 실제 코드 경로 - -# Core Rules - -- planning과 design이 없는 batch는 구현하지 않는다. -- 코드 변경 전 `implementation.md`의 Plan Summary와 Changed Files 초안을 먼저 맞춘다. -- 구현 직후 가장 좁은 테스트, lint, typecheck, 또는 동작 검증을 수행한다. -- stateful runtime을 host/PYDEBUG binding으로 검증할 때는 동일 입력 반복 호출이 내부 scan/cache/state를 누적할 수 있음을 먼저 점검한다. deterministic evidence가 필요하면 테스트 전에 reset hook을 추가하거나 초기 상태를 명시적으로 복원한 뒤 비교한다. -- blocker가 생기면 문서에 남기고 범위를 임의 확장하지 않는다. -- 구현을 시작한 batch는 `implementation.md`만 갱신하고 끝내지 말고, batch index와 batch index 문서의 Status도 최소 `in-delivery`로 맞춰 stage 상태가 어긋나지 않게 한다. -- 배치 문서 경로가 아직 없거나 추적 문서가 비어 있으면, 구현 단계에서 `index.md`, `implementation.md`, `verification.md` 기본 뼈대를 함께 정리해 이후 stage가 바로 이어질 수 있게 한다. - -# Execution Procedure - -1. batch 경로, plan, design, 관련 REQ를 읽는다. -2. 구현 범위와 변경 파일 후보를 요약한다. -3. 실제 코드 변경을 수행한다. -4. `implementation.md`의 Changed Files, Execution Log, Validation, Remaining Risks를 갱신한다. -5. batch index 상태를 필요하면 `in-delivery`로 유지 또는 보정한다. -6. persistence schema, flash migration, 또는 저장 포맷 호환성 이슈를 디버깅할 때는 호스트/PYDEBUG 빌드의 `sizeof(...)` 추정으로 결론내리지 말고, 타깃 펌웨어 쪽에 구조체 크기(`sizeof(struct prop_header)`, 관련 persisted struct), header probe 결과, 첫/둘째 `property_read(...)` 반환값을 직접 로그로 남겨 원인을 분리한다. - -# Validation - -- `implementation.md`에 실제 변경 파일과 검증 기록이 반영됐는지 확인한다. -- 구현 직후 수행한 가장 좁은 검증 명령 또는 결과가 문서에 남았는지 확인한다. -- persistence/migration 디버깅 변경이라면, 타깃 로그로 ABI 크기와 read rc를 캡처하는 계측 또는 동등한 증거가 남았는지 확인한다. -- batch 문서 `index.md`와 `docs/batches/index.md`의 Status가 구현 진행 상태(`in-delivery` 등)와 일치하는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/run-sdlc/SKILL.md b/.stage-pilot/skills/run-sdlc/SKILL.md deleted file mode 100644 index d5dd2d7..0000000 --- a/.stage-pilot/skills/run-sdlc/SKILL.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -name: run-sdlc -description: "Use when: orchestrating the next SDLC action from the current 3-phase state, running /run-sdlc with a Discovery, REQ, Batch, or Release ID, resuming from the first incomplete unit in docs/discovery, docs/srs, docs/batches, or docs/releases, or routing a no-history repository to bootstrap-baseline before the first real Discovery." -version: 0.7.0 -author: Justin Ko -license: private -argument-hint: "예: dcy-001, bat-001, rel-001, req-001 req-002" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, orchestration, workflow, execution, sdlc] - related_skills: [bootstrap-baseline, new-discovery, draft-req, draft-batch, run-batch-delivery, draft-release] ---- - -# Purpose - -This skill inspects the current state of Discovery, REQ, Batch, and Release documents and routes work to the next skill-only step in the 3-phase SDLC model. - -`docs/project-structure.md`와 `docs/runtime-flows.md`는 별도 governance unit는 아니지만, 이 skill은 다음 단계를 추천할 때 해당 baseline 문서의 존재 여부와 갱신 필요 여부를 함께 확인한다. - -# Inputs - -- `DISCOVERY_ID` -- `REQ-ID` 목록 -- `BAT-ID` -- `REL-ID` -- 관련 문서 경로 -- `docs/project-structure.md` (존재하는 경우) -- `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- 이 skill은 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`만 active 경로로 사용한다. -- `docs/project-structure.md`와 `docs/runtime-flows.md`는 active routing 대상은 아니지만, 모든 단계에서 참고할 cross-cutting baseline 문서로 취급한다. -- prompt 기반 stage command는 사용하지 않는다. -- `review-*` 단계는 별도 명령으로 두지 않고 대응 `confirm-*` 절차에 흡수된 상태를 전제로 한다. -- 사람 선택이 필요한 경계에서는 자동으로 넘기지 않고 중단한다. 대표적으로 `suggest-batch-reqs` 결과에서 어떤 후보를 채택할지 결정하는 단계가 그렇다. -- 단, 정확히 1개의 `Approved` REQ가 국소적이고 저위험 변경으로 판단되면 `minor-change` fast path로 `draft-batch`를 바로 제안할 수 있다. 이 경로의 batch profile은 `batch-lite`다. -- active unit가 아직 없고 baseline 문서 또는 active index가 비어 있으면, 첫 real Discovery 전에 `bootstrap-baseline`을 우선 추천한다. - -# Execution Procedure - -## 1. 입력 해석 - -- 입력이 `dcy-`로 시작하면 Discovery를 기준으로 현재 상태를 확인한다. -- 입력이 `req-` 목록이면 REQ 상태를 읽고 batch 생성 또는 추천 단계로 보낸다. -- 입력이 `bat-`로 시작하면 batch delivery 상태를 확인한다. -- 입력이 `rel-`로 시작하면 release 상태를 확인한다. -- 입력이 없으면 현재 저장소에서 첫 incomplete step을 판정한다. -- 어떤 입력이든 가능하면 `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 함께 확인한다. - -## 1.5 bootstrap baseline routing - -- active unit가 없고 `docs/project-structure.md`, `docs/runtime-flows.md`, active index 중 일부라도 비어 있으면 `bootstrap-baseline`을 다음 skill로 제안한다. -- 이 경우 Discovery를 자동 생성하지 않는다. -- baseline 초기화가 끝난 뒤 `new-discovery`를 다음 real Discovery entrypoint로 안내한다. - -## 2. Discovery 기준 routing - -- Discovery가 아직 `confirmed`가 아니면 `confirm-discovery`를 우선 실행한다. -- Discovery는 confirmed지만 연결된 REQ가 없으면 `draft-req`를 다음 단계로 사용한다. -- `Approved` REQ는 있으나 batch가 없으면 `suggest-batch-reqs`를 실행해 후보를 제시하고, 사용자가 후보를 선택한 뒤 `draft-batch`로 이어 가도록 멈춘다. -- batch가 이미 생성되었고 delivery chain을 한 번에 이어서 진행하려면 `run-batch-delivery`를 직접 entrypoint로 사용할 수 있다. -- baseline 문서 갭이 있으면 상태 요약에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 필요 여부를 함께 기록한다. - -## 3. REQ 기준 routing - -- `Proposed` REQ면 `confirm-req`를 다음 단계로 사용한다. -- 기존 REQ의 `Requirement`, `Acceptance Criteria`, 구현 전제가 바뀌어 Change Log 기반 변경 관리가 필요하면 `change-req`를 다음 단계로 사용한다. -- `Approved` REQ가 `release-candidate` 또는 `released` batch의 verification evidence와 연결돼 있으면 `confirm-req-implemented`를 다음 단계로 사용한다. -- 단일 `Approved` REQ이고 구조/인터페이스/런타임 영향이 없는 소규모 변경이면 `draft-batch`를 `minor-change -> batch-lite` 경로로 바로 제안한다. -- Approved REQ 묶음이 2개 이상이거나 사람 선택이 필요한 경우 `suggest-batch-reqs` 또는 `draft-batch`로 이어진다. -- baseline 문서 갱신이 REQ에 포함돼 있으면 상태 요약에 이를 반영한다. - -## 4. Batch 기준 routing - -- `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` -- batch profile이 `standard`이거나 `batch-lite`라도 planning의 `Design Gate`가 `yes`면 `draft-batch-design` -- batch profile이 `batch-lite`이고 `Design Gate`가 `no`면 design 없이 `run-batch-implementation` -- 구현 로그와 코드 변경이 아직 없으면 `run-batch-implementation` -- verification 문서가 없거나 불완전하면 `draft-batch-verification` -- verification evidence가 있지만 release-candidate가 아니면 `confirm-batch-verification` -- batch가 `release-candidate` 또는 `released`이고 포함 REQ 중 `Approved` 상태가 남아 있으면 `confirm-req-implemented` -- batch가 `release-candidate`이고 포함 REQ 구현 상태 동기화까지 끝났으면 `draft-release` -- batch design 또는 verification에 structure/flow 영향이 보이면 baseline 문서 갱신 필요 여부를 상태 요약에 포함한다. - -## 5. Release 기준 routing - -- release 문서가 draft면 profile(`docs-only` | `tooling` | `app-service`)에 맞는 근거가 채워졌는지 확인한 뒤 `confirm-release` -- `feedback-captured` release에 follow-up 입력이 있으면 `suggest-next-discovery` 또는 `change-req`를 다음 단계 후보로 제안한다. -- release가 confirmed 이후 운영 관찰이 남아 있지 않으면 `capture-release-feedback` - -# Output Expectations - -- 입력으로 해석한 단위 종류 -- 현재 상태 요약 -- baseline 문서 상태 (`present` | `missing` | `update-likely-required` | `bootstrap-required`) -- 바로 실행할 다음 skill 1개 또는 추천 후보 목록 -- 자동으로 진행하지 않고 멈춘 이유가 있으면 그 사유 - -# Validation - -- 다음 단계가 현재 문서 상태와 모순되지 않는지 확인한다. -- 제안한 다음 skill이 실제 `.stage-pilot/skills/` 아래 존재하는지 확인한다. -- baseline 문서가 필요한 상황에서 상태 요약 또는 추천 사유에 그 정보가 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/skills/suggest-batch-reqs/SKILL.md b/.stage-pilot/skills/suggest-batch-reqs/SKILL.md deleted file mode 100644 index d513969..0000000 --- a/.stage-pilot/skills/suggest-batch-reqs/SKILL.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -name: suggest-batch-reqs -description: "Use when: recommending delivery batch groupings from approved requirements, running /suggest-batch-reqs with a Discovery ID or REQ list, assessing cohesion and delivery risk, or preparing input for /draft-batch without changing repository state." -version: 0.8.2 -author: Justin Ko -license: private -argument-hint: "예: dcy-001 또는 req-001 req-002 req-003" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, batch, requirements, planning, sdlc] - related_skills: [draft-req, draft-batch, run-sdlc] ---- - -# Purpose - -This skill recommends one or more candidate batch groupings from approved requirements and reports rationale, exclusions, risk, and confidence without modifying any files. 입력이 없으면 현재 저장소에서 아직 delivery 입력으로 소모되지 않은 Approved REQ를 찾아 이번 batch 후보를 추천한다. - -# When to use - -- Approved REQ가 여러 개 쌓여 어떤 묶음으로 delivery를 시작할지 추천이 필요할 때 -- `/suggest-batch-reqs dcy-001` 또는 `/suggest-batch-reqs req-001 req-002`처럼 batch 후보안을 받아야 할 때 -- `/suggest-batch-reqs`처럼 인자 없이 현재 저장소의 Approved REQ 중 아직 구현 완료 또는 기존 batch 편성이 끝나지 않은 후보만 추려 이번 batch 권장안을 보고 싶을 때 -- `draft-batch` 실행 전에 포함/제외 REQ 근거를 미리 보고 싶을 때 - -## Do not use when - -- 아직 `Approved`되지 않은 REQ를 먼저 승인해야 할 때 (`confirm-req`가 더 적절함) -- 포함할 REQ 집합이 이미 확정되어 바로 batch 문서를 생성하면 될 때 (`draft-batch`가 더 적절함) -- 단일 저위험 `Approved` REQ라 minor-change 또는 `batch-lite` 경로가 명확할 때 (`draft-batch`를 바로 고려할 수 있음) -- batch 추천이 아니라 현재 저장소의 전체 다음 SDLC 단계를 알고 싶을 때 (`run-sdlc`가 더 적절함) - -# Inputs - -- 선택 입력 - - `DISCOVERY_ID` 또는 Approved `REQ-ID` 목록 - - 입력이 없으면 `docs/srs/**/req-*.md`와 `docs/srs/index.md`를 읽어 Approved REQ를 찾는다. - - 입력이 없으면 `docs/batches/index.md`도 함께 읽어 이미 batch에 편성된 REQ를 제외한다. -- 선택 입력 - - batch 최대 크기 - - Type, Priority, Owner 필터 - - 제외할 REQ 목록 - - 강제로 함께 고려할 REQ 목록 - -# Core Rules - -- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. -- `Approved`가 아닌 REQ는 후보에서 제외한다. -- `Deprecated` REQ는 후보에서 제외한다. -- 입력이 없는 경우 `Implemented` 상태 REQ는 후보에서 제외한다. -- 입력이 없는 경우 `docs/batches/index.md` 기준으로 `draft`, `in-delivery`, `release-candidate`, `released` batch에 이미 포함된 REQ는 중복 추천하지 않는다. -- 상태가 REQ 본문과 index 사이에서 불일치하면, 추천 전에 그 불일치를 보고하고 기본적으로 후보에서 제외하거나 보류한다. -- 아래 조건을 많이 만족할수록 같은 batch 후보로 묶는다. - - 같은 사용자 가치 또는 기능 흐름 - - 같은 모듈, 인터페이스, 런타임 경로 - - 같은 설계 전제와 환경 가정 - - 같은 iteration 안에서 구현과 검증 가능 - - acceptance criteria를 함께 검증하는 것이 효율적임 - - release 시 함께 배포해도 위험이 과도하지 않음 -- 아래 조건이 있으면 같은 batch로 묶지 않는다. - - 핵심 설계 전제 충돌 - - 완전히 다른 verification 흐름 필요 - - 독립 배포가 필요한 고위험 변경 - - 미해결 정책 결정이나 외부 의존성으로 planning이 불안정함 - -## 사람 선택 규칙 - -- 추천 결과 중 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정한다. -- 이 skill은 후보를 자동 채택하거나 `draft-batch`를 자동 실행하지 않는다. -- 후보 간 우열이 애매하면 `기본안` 1개와 `보수적 대안` 1개 이상을 함께 제시하고 trade-off를 설명한다. -- 사람이 선택해야 하는 기준은 보통 다음 순서로 설명한다. - 1. 이번 iteration 안에 끝낼 수 있는가 - 2. 함께 검증할 때 verification cost가 실제로 줄어드는가 - 3. release risk가 한 번에 묶어도 감당 가능한가 - 4. 나중에 분리하면 오히려 재작업이 커지는가 - -## 후보별 batch profile 힌트 - -- `batch-lite` - - 단일 REQ 또는 아주 작은 저위험 묶음 - - 구조/인터페이스/런타임 영향이 좁음 - - planning/design/verification 서술이 짧고 명확함 -- `standard` - - 다수 REQ 또는 상호작용이 있는 묶음 - - 구조/인터페이스/런타임 영향이 중간 이상임 - - planning/design/verification 근거를 분리해서 적는 편이 안전함 -- 각 후보 출력에는 profile 이름만 적지 말고, 왜 그 profile이 맞는지 REQ 수, 변경 폭, verification 복잡도 기준으로 한 줄 이상 근거를 붙인다. - -# Execution Procedure - -1. 입력이 Discovery면 source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 읽고, 입력이 REQ 목록이면 각 REQ 문서를 읽는다. -2. Discovery 입력인데 연결 REQ가 없거나 연결 REQ가 모두 `Approved`가 아니면 추천을 만들지 않고 그 사실을 보고한다. -3. 입력이 없으면 `docs/srs/**/req-*.md`, `docs/srs/index.md`, `docs/batches/index.md`를 읽어 Approved REQ 전체를 찾고, `Implemented` 상태 또는 기존 batch에 이미 포함된 REQ를 제외해 현재 후보군을 만든다. -4. 각 REQ의 상태, Type, Impacted Area, Acceptance Criteria를 요약한다. -5. 후보군에서 Approved REQ만 추린 뒤 응집도와 delivery risk 기준으로 후보안을 1개 이상 만든다. -6. 입력이 없는 스캔 결과 후보군이 비어 있으면 억지로 batch 후보를 만들지 않는다. - - `Approved` REQ가 전혀 없거나 - - Approved REQ가 모두 기존 batch에 이미 포함돼 있거나 - - 남은 REQ가 전부 `Proposed`/`Implemented`/`Deprecated`라면 - `추천 가능한 신규 batch 없음`으로 보고한다. - - 이 경우 스캔한 REQ와 제외 사유를 함께 정리하고, 필요하면 먼저 `confirm-req` 또는 기존 batch 진행 상태 점검을 권장한다. - - 후보가 없는데도 형식상 `BAT-CANDIDATE-1`을 억지로 만들거나, 실제로 구성할 수 없는 `/draft-batch ...` 예시를 만들지 않는다. -7. 가능하면 최소 2개 후보를 제시한다. - - 응집도 중심 기본안 - - 더 작은 묶음 또는 보수적 대안 -8. 적절한 묶음이 없지만 단일 Approved REQ는 존재하면 `단일 REQ batch 권장`을 명시한다. -9. 각 후보에 대해 포함 REQ, 제외 REQ와 제외 이유, 추천 근거, 위험도, 신뢰도, 예상 batch profile(`standard` | `batch-lite`)을 정리한다. -10. 각 후보에 대해 profile 판단 근거(REQ 수, 구조/인터페이스/런타임 영향, verification 흐름 복잡도)를 함께 적는다. -11. 바로 사용할 수 있는 `/draft-batch ...` 입력 예시를 제시하되, 이는 추천 초안이며 최종 채택은 사람 선택임을 명시한다. 단, 추천 가능한 후보가 없으면 `/draft-batch` 예시는 생략하고 그 이유를 쓴다. - -# Output Expectations - -- 후보 `BAT-CANDIDATE-N` 목록 -- 입력이 없는 경우 스캔한 Approved REQ 목록 -- 후보별 포함 REQ -- 후보별 제외 REQ와 제외 이유 -- 후보별 추천 근거 -- 후보별 위험도 (`Low|Medium|High`) -- 후보별 신뢰도 (`High|Medium|Low`) -- 후보별 예상 batch profile (`standard` | `batch-lite`)과 판단 근거 -- `draft-batch` 추천 명령 예시 -- 최종 채택은 사람 선택이라는 안내 -- 단, 추천 가능한 신규 batch가 없으면 아래 형식으로 대체 보고할 수 있다. - - 스캔한 REQ 목록 - - 승인됨/제외됨/기존 batch 포함 상태 요약 - - `추천 가능한 신규 batch 없음` 결론 - - 먼저 필요한 선행 단계(`confirm-req`, 기존 batch 진행, release 정리 등) - -# Common Pitfalls - -1. `Approved`가 아닌 REQ를 후보에 섞는 실수 - - batch 추천은 delivery input이 확정된 REQ만 대상으로 해야 하므로 `Proposed`, `Implemented`, `Deprecated`를 구분해 제외한다. - -2. 이미 다른 batch에 포함된 REQ를 다시 추천하는 실수 - - `docs/batches/index.md`를 읽고 중복 편성 여부를 먼저 확인한다. - -3. 서로 다른 verification 흐름이 필요한 REQ를 단순 개수 맞추기로 한 batch에 묶는 실수 - - 응집도보다 검증 흐름 충돌이 크면 더 작은 보수적 대안을 함께 제시한다. - -4. Discovery 입력을 받았지만 실제 연결 REQ 근거 없이 임의로 묶는 실수 - - source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 근거로 삼는다. - -5. 추천 후보를 정답처럼 확정해 버리는 실수 - - 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정하며, 이 skill은 추천만 수행한다. - -6. 단일 저위험 REQ인데도 불필요하게 큰 batch 후보만 제시하는 실수 - - 필요하면 `단일 REQ batch 권장` 또는 `batch-lite` 대안을 함께 제시한다. - -# Verification Checklist - -- [ ] 후보에 `Approved`가 아닌 REQ가 포함되지 않았다. -- [ ] 입력이 없는 경우 `Implemented` 상태 REQ와 기존 batch 포함 REQ가 제외되었다. -- [ ] Discovery 입력이면 연결 REQ 근거가 명확하다. -- [ ] 상태 불일치 REQ는 보고되었고 무리하게 후보에 포함되지 않았다. -- [ ] 후보별 포함/제외 이유가 설명되어 있다. -- [ ] 가능하면 최소 1개의 보수적 대안 또는 `단일 REQ batch 권장`이 포함되어 있다. -- [ ] 후보별 예상 batch profile(`standard` | `batch-lite`)과 그 근거가 제시되었다. -- [ ] `/draft-batch` 예시 입력이 실제 후보 구성과 일치한다. -- [ ] 저장소 상태를 변경하지 않았다. -- [ ] 최종 채택이 사람 선택이라는 점이 명시되어 있다. \ No newline at end of file diff --git a/.stage-pilot/skills/suggest-next-discovery/SKILL.md b/.stage-pilot/skills/suggest-next-discovery/SKILL.md deleted file mode 100644 index 8c5b7a4..0000000 --- a/.stage-pilot/skills/suggest-next-discovery/SKILL.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -name: suggest-next-discovery -description: "Use when: recommending the next Discovery candidate from release feedback, pending requirements, baseline gaps, or recent doctor reports, running /suggest-next-discovery with or without a REL ID, or deciding whether feedback should become a new Discovery or a change-req instead." -version: 0.6.0 -author: Justin Ko -license: private -argument-hint: "예: rel-001 또는 인자 없이 /suggest-next-discovery" -user-invocable: true -metadata: - hermes: - tags: [stage-pilot, discovery, prioritization, planning, sdlc] - related_skills: [capture-release-feedback, new-discovery, run-sdlc] ---- - -# Purpose - -This skill analyzes release feedback, pending REQs, baseline gaps, and recent delivery signals to recommend the next improvement loop. It reports ranked candidates and tells the user whether to start a new Discovery or route work into `change-req` instead. - -# When to use - -- `/suggest-next-discovery`처럼 다음 반복 주제를 추천받고 싶을 때 -- `feedback-captured` release 이후 어떤 후속 Discovery를 열지 정해야 할 때 -- release feedback에 기존 REQ 수정과 신규 Discovery 후보가 섞여 있어 triage가 필요할 때 -- `stagepilot-doctor --report ...` 결과와 release feedback를 함께 보고 다음 개선 루프를 정하고 싶을 때 - -# Inputs - -- 선택 입력 - - `REL-ID` prefix 또는 release 경로 - - 입력이 없으면 `docs/releases/*.md`, `docs/srs/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`를 읽어 후보를 찾는다. -- 선택 입력 - - `stagepilot-doctor` Markdown report 경로 -- 관련 release 문서의 `Feedback Handoff` -- `docs/srs/index.md`와 미구현/변경 대기 REQ 상태 -- `docs/project-structure.md`, `docs/runtime-flows.md` (존재하는 경우) - -# Core Rules - -- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. -- release feedback의 후보는 아래 셋 중 하나로 분류한다. - - `new-discovery` - - `change-req` - - `no-action` -- 기존 REQ의 수정으로 닫히는 항목은 새 Discovery보다 `change-req`를 우선 추천한다. -- 서로 다른 가치 흐름이나 이해관계자를 가지는 후보는 별도 Discovery로 분리한다. -- 추천에는 가치, 위험, 노력의 상대 평가를 포함한다. - -# Execution Procedure - -1. 입력 release가 있으면 해당 문서를, 없으면 최근 release 문서들을 읽는다. -2. `Feedback Handoff`에서 Discovery Input, REQ Input, Change Request Input을 추린다. -3. `docs/srs/index.md`와 관련 REQ 상태를 읽어 기존 REQ 수정으로 해결 가능한지 판정한다. -4. baseline 문서 gap, 미구현 REQ, doctor report의 경고/오류가 후속 반복 후보인지 함께 판단한다. -5. 후보를 `new-discovery` 또는 `change-req`로 분류하고 가치/위험/노력 점수를 매긴다. -6. 우선순위 순으로 추천 목록과 바로 실행할 명령 예시를 제시한다. - -# Output Expectations - -- 후보 목록 (`CANDIDATE-1`, `CANDIDATE-2`, ...) -- 각 후보의 분류 (`new-discovery` | `change-req` | `no-action`) -- 근거 release 또는 REQ -- 가치/위험/노력 (`High|Medium|Low`) -- 추천 이유 -- 다음 명령 예시 (`/new-discovery ...` 또는 `/change-req ...`) - -# Validation - -- 동일 후보를 `new-discovery`와 `change-req`에 중복 배치하지 않았는지 확인한다. -- release feedback 근거가 없는 임의 후보를 만들지 않았는지 확인한다. -- 기존 REQ 수정으로 닫히는 항목을 새 Discovery로 과도하게 승격하지 않았는지 확인한다. \ No newline at end of file diff --git a/.stage-pilot/source/.claude/commands/bootstrap-baseline.md b/.stage-pilot/source/.claude/commands/bootstrap-baseline.md deleted file mode 100644 index 03afeef..0000000 --- a/.stage-pilot/source/.claude/commands/bootstrap-baseline.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/bootstrap-baseline/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/capture-release-feedback.md b/.stage-pilot/source/.claude/commands/capture-release-feedback.md deleted file mode 100644 index 03266f9..0000000 --- a/.stage-pilot/source/.claude/commands/capture-release-feedback.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/capture-release-feedback/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/change-req.md b/.stage-pilot/source/.claude/commands/change-req.md deleted file mode 100644 index 20fa644..0000000 --- a/.stage-pilot/source/.claude/commands/change-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/change-req/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/confirm-batch-verification.md b/.stage-pilot/source/.claude/commands/confirm-batch-verification.md deleted file mode 100644 index 6e72a0b..0000000 --- a/.stage-pilot/source/.claude/commands/confirm-batch-verification.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-batch-verification/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/confirm-discovery.md b/.stage-pilot/source/.claude/commands/confirm-discovery.md deleted file mode 100644 index 4b36987..0000000 --- a/.stage-pilot/source/.claude/commands/confirm-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-discovery/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/confirm-release.md b/.stage-pilot/source/.claude/commands/confirm-release.md deleted file mode 100644 index fea9962..0000000 --- a/.stage-pilot/source/.claude/commands/confirm-release.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-release/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/confirm-req-implemented.md b/.stage-pilot/source/.claude/commands/confirm-req-implemented.md deleted file mode 100644 index d9e90b5..0000000 --- a/.stage-pilot/source/.claude/commands/confirm-req-implemented.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-req-implemented/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/confirm-req.md b/.stage-pilot/source/.claude/commands/confirm-req.md deleted file mode 100644 index 558f868..0000000 --- a/.stage-pilot/source/.claude/commands/confirm-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/confirm-req/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-batch-design.md b/.stage-pilot/source/.claude/commands/draft-batch-design.md deleted file mode 100644 index 2674265..0000000 --- a/.stage-pilot/source/.claude/commands/draft-batch-design.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-design/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-batch-planning.md b/.stage-pilot/source/.claude/commands/draft-batch-planning.md deleted file mode 100644 index cd5261b..0000000 --- a/.stage-pilot/source/.claude/commands/draft-batch-planning.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-planning/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-batch-verification.md b/.stage-pilot/source/.claude/commands/draft-batch-verification.md deleted file mode 100644 index 7758a67..0000000 --- a/.stage-pilot/source/.claude/commands/draft-batch-verification.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch-verification/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-batch.md b/.stage-pilot/source/.claude/commands/draft-batch.md deleted file mode 100644 index 66d1a06..0000000 --- a/.stage-pilot/source/.claude/commands/draft-batch.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-batch/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-release.md b/.stage-pilot/source/.claude/commands/draft-release.md deleted file mode 100644 index 40c6826..0000000 --- a/.stage-pilot/source/.claude/commands/draft-release.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-release/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/draft-req.md b/.stage-pilot/source/.claude/commands/draft-req.md deleted file mode 100644 index 29b91bb..0000000 --- a/.stage-pilot/source/.claude/commands/draft-req.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/draft-req/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/new-discovery.md b/.stage-pilot/source/.claude/commands/new-discovery.md deleted file mode 100644 index 970e28a..0000000 --- a/.stage-pilot/source/.claude/commands/new-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/new-discovery/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/review-discovery.md b/.stage-pilot/source/.claude/commands/review-discovery.md deleted file mode 100644 index 255c040..0000000 --- a/.stage-pilot/source/.claude/commands/review-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/review-discovery/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/run-batch-delivery.md b/.stage-pilot/source/.claude/commands/run-batch-delivery.md deleted file mode 100644 index 4c1c9ce..0000000 --- a/.stage-pilot/source/.claude/commands/run-batch-delivery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-batch-delivery/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/run-batch-implementation.md b/.stage-pilot/source/.claude/commands/run-batch-implementation.md deleted file mode 100644 index 9014da3..0000000 --- a/.stage-pilot/source/.claude/commands/run-batch-implementation.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-batch-implementation/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/run-sdlc.md b/.stage-pilot/source/.claude/commands/run-sdlc.md deleted file mode 100644 index e8cdb83..0000000 --- a/.stage-pilot/source/.claude/commands/run-sdlc.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/run-sdlc/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/suggest-batch-reqs.md b/.stage-pilot/source/.claude/commands/suggest-batch-reqs.md deleted file mode 100644 index b4a7b1d..0000000 --- a/.stage-pilot/source/.claude/commands/suggest-batch-reqs.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/suggest-batch-reqs/SKILL.md` exactly. diff --git a/.stage-pilot/source/.claude/commands/suggest-next-discovery.md b/.stage-pilot/source/.claude/commands/suggest-next-discovery.md deleted file mode 100644 index 16198ca..0000000 --- a/.stage-pilot/source/.claude/commands/suggest-next-discovery.md +++ /dev/null @@ -1 +0,0 @@ -Follow the instructions in `.stage-pilot/skills/suggest-next-discovery/SKILL.md` exactly. diff --git a/.stage-pilot/source/.github/instructions/placeholder-guide.md b/.stage-pilot/source/.github/instructions/placeholder-guide.md deleted file mode 100644 index 30b97f5..0000000 --- a/.stage-pilot/source/.github/instructions/placeholder-guide.md +++ /dev/null @@ -1,239 +0,0 @@ -# 플레이스홀더 가이드 - -이 파일은 active workspace 기준으로 `.github/templates/`와 `.github/skills/`에서 사용하는 플레이스홀더의 문법과 처리 원칙을 정의한다. - -기본 전제는 아래와 같다. - -- active SDLC 진입점은 skill-only 체계다. -- baseline 문서와 active index 초기화는 `bootstrap-baseline`이 담당하며, 이 단계는 Discovery unit를 만들지 않는다. -- `.github/prompts/`는 active 기준 문서가 아니다. -- active 산출물 경로는 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`다. - -## 1. 문법 규칙 - -| 형식 | 의미 | 예시 | -| --- | --- | --- | -| `{{KEY}}` | 단순 치환 대상 | `{{DISCOVERY_ID}}` | -| `{{KEY:hint}}` | 힌트 포함 치환 대상 | `{{CONFIRMED_AT_KST:TBD}}` | -| `{{KEY:opt1\|opt2}}` | 열거형 선택 값 | `{{DOC_STATUS:draft\|confirmed}}` | - -규칙: - -- 최종 문서에는 `{{...}}` 문법이 남지 않아야 한다. -- 힌트 텍스트는 치환 시 함께 제거한다. -- 사람 승인 없이는 확정할 수 없는 값은 본문에 무리하게 확정하지 말고 Notes 또는 결정 필요 항목으로 남긴다. - -## 2. 처리 주체 - -플레이스홀더는 아래 세 가지 처리 주체 중 하나에 속한다. - -### A. 자동 치환 - -- ID, 생성 시각, 갱신 시각, 상태 기본값처럼 실행 시점에 자동으로 정해지는 값 -- 예: - - `{{DISCOVERY_ID}}` - - `{{CONFIRMED_AT_KST}}` - - `{{BAT_ID}}` - - `{{REL_ID}}` - -### B. AI 근거 기반 치환 - -- 현재 저장소 상태, 상위 문서, 이미 확정된 값으로 채울 수 있는 항목 -- 예: - - Discovery의 현재 상태 요약 - - REQ의 Impacted Area 초안 - - Batch planning의 dependency 정리 - - Release rollout checklist 초안 - -### C. 사람 결정 필요 - -- 정책 선택, 승인자 지정, 우선순위 확정, 외부 환경 정보처럼 사람 판단이 필요한 값 -- 이런 항목은 무리하게 확정하지 않는다. - -## 3. 경로별 플레이스홀더 원칙 - -### 3.0 Bootstrap baseline 템플릿 - -대상 경로: - -- `.github/templates/bootstrap/baseline-seed.yaml` -- `.github/templates/project-structure.md` -- `.github/templates/runtime-flows.md` -- `.github/templates/discovery/index.md` -- `.github/templates/srs/index.md` -- `.github/templates/batches/index.md` -- `.github/templates/releases/index.md` - -처리 원칙: - -- `bootstrap-baseline`은 먼저 `.stage-pilot/bootstrap/baseline.yaml` seed를 만들거나 보강한다. -- greenfield 저장소처럼 관찰 가능한 정보가 부족하면 최소 질문 세트로 seed를 채운다. -- `bootstrap-baseline`은 baseline 문서와 index를 초기화하지만 Discovery 문서를 만들지 않는다. -- baseline 문서는 seed를 source of truth로 렌더링하고, 필요하면 저장소 관찰값으로 보강한다. -- index 문서는 빈 register 골격만 준비하고, 실제 unit 행은 이후 skill이 추가한다. - -주요 키: - -- `{{BASELINE_MODE}}` -- `{{BASELINE_SEED_PATH}}` -- `{{PROJECT_SUMMARY}}` -- `{{PRIMARY_DOMAIN}}` -- `{{TECH_STACK_BULLETS}}` -- `{{PRIMARY_RUNTIME}}` -- `{{PRIMARY_ENTRYPOINT_BULLETS}}` -- `interfaces.contracts[*]` -- `data_model.entities[*]` - -### 3.0.1 선택 cross-cutting 템플릿 - -대상 경로: - -- `.github/templates/interface-contract.md` -- `.github/templates/data-model.md` - -처리 원칙: - -- 이 문서들은 optional cross-cutting reference다. -- active unit의 source of truth를 대체하지 않고, 여러 REQ와 batch에 공통으로 반복되는 계약 정보를 모아 둔다. -- `bootstrap-baseline`은 seed의 `interfaces`와 `data_model` 섹션을 우선 읽고, 값이 없으면 `runtime`, `structure`, `notes`를 바탕으로 기본 초안을 구체적으로 유도한다. -- baseline 수준의 변경이 있을 때만 갱신하고, REQ별 acceptance criteria를 그대로 복사하지 않는다. - -주요 키: - -- `{{BASELINE_MODE}}` -- `{{BASELINE_SEED_PATH}}` -- `{{PROJECT_SUMMARY}}` -- `{{PRIMARY_DOMAIN}}` -- `{{PRIMARY_RUNTIME}}` -- `{{INTERFACE_NAME_1}}` -- `{{INTERFACE_PURPOSE_1}}` -- `{{INTERFACE_SUMMARY_BULLETS}}` -- `{{INTERFACE_INPUTS_1}}` -- `{{INTERFACE_OUTPUTS_1}}` -- `{{INTERFACE_ERRORS_1}}` -- `{{INTERFACE_COMPATIBILITY_1}}` -- `{{INTERFACE_SHARED_CONSTRAINTS_BULLETS}}` -- `{{INTERFACE_CURRENT_GAPS_BULLETS}}` -- `{{PERSISTENCE_BACKEND}}` -- `{{ENTITY_NAME_1}}` -- `{{ENTITY_PURPOSE_1}}` -- `{{ENTITY_SOURCE_1}}` -- `{{ENTITY_LIFECYCLE_1}}` -- `{{MODEL_SUMMARY_BULLETS}}` -- `{{ENTITY_FIELDS_1}}` -- `{{ENTITY_RELATIONSHIPS_1}}` -- `{{CONSISTENCY_RULE_BULLETS}}` -- `{{PERSISTENCE_NOTES_BULLETS}}` -- `{{MODEL_CURRENT_GAPS_BULLETS}}` - -### 3.1 Discovery 템플릿 - -대상 경로: - -- `.github/templates/discovery/discovery.md` -- `docs/discovery/*.md` - -주요 키: - -- `{{DISCOVERY_ID}}` -- `{{ISSUE_NAME}}` -- `{{CREATED_AT_KST}}` -- `{{UPDATED_AT_KST}}` -- `{{FREEZE_HANDOFF_DECISION}}` -- `{{READY_FOR_REQ_DRAFTING}}` -- `{{CONFIRMED_BY}}` -- `{{CONFIRMED_AT_KST}}` - -처리 원칙: - -- Discovery는 REQ 초안 작성으로 넘길 수 있을 때만 freeze handoff를 완료한다. -- Discovery 문서의 용어는 `Planning` 직결이 아니라 `REQ Drafting` 기준으로 유지한다. - -### 3.2 SRS 템플릿 - -대상 경로: - -- `docs/srs/req-template.md` -- `docs/srs/index.md` - -주요 키: - -- `REQ-XXX` -- `Status` -- `Type` -- `Priority` -- `Owner` - -처리 원칙: - -- REQ는 `Proposed -> Approved -> Implemented -> Deprecated` 상태를 따른다. -- `draft-req`는 초안을 만들고, `confirm-req`는 `Proposed -> Approved`, `confirm-req-implemented`는 `Approved -> Implemented` 전환을 담당한다. - -### 3.3 Batch 템플릿 - -대상 경로: - -- `.github/templates/batches/*` -- `docs/batches/*` - -주요 키: - -- `{{BAT_ID}}` -- `Profile` -- Included REQ 목록 -- Source Discovery - -처리 원칙: - -- batch는 Approved REQ만 포함한다. -- batch는 `standard` 또는 `batch-lite` profile을 가진다. -- `batch-lite`는 `minor-change` fast path의 기본 delivery profile이며, 처음에는 `index.md`와 `planning.md`만 만들 수 있다. -- planning, design, implementation, verification 문서는 같은 batch 폴더 아래에서 관리한다. 단, `batch-lite`는 design/implementation/verification을 단계 진입 시점에 생성할 수 있다. -- `confirm-batch-verification` 성공 전에는 `release-candidate`로 올리지 않는다. - -### 3.4 Release 템플릿 - -대상 경로: - -- `.github/templates/releases/*` -- `docs/releases/*` - -주요 키: - -- `{{REL_ID}}` -- `Profile` -- `Change Request Input` -- Included Batch -- Rollout Plan -- Rollback Plan - -처리 원칙: - -- release는 `release-candidate` batch만 포함한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. -- feedback handoff에는 `Discovery Input`, `REQ Input`, `Change Request Input`을 구분해 기록한다. -- `confirm-release`는 release 승인, `capture-release-feedback`는 운영 환류 기록을 담당한다. - -## 4. Active Skill Summary - -| 목적 | Skill | 주요 입력 | 주요 출력 | -| --- | --- | --- | --- | -| Bootstrap baseline 초기화 | `.github/skills/bootstrap-baseline/SKILL.md` | 저장소 루트, baseline/index 존재 여부 | baseline 문서와 active index | -| Discovery 초안 생성 | `.github/skills/new-discovery/SKILL.md` | 이슈/요청 요약 | `docs/discovery/*.md` | -| Discovery 확정 | `.github/skills/confirm-discovery/SKILL.md` | `DISCOVERY_ID` | confirmed Discovery | -| REQ 초안 생성 | `.github/skills/draft-req/SKILL.md` | `DISCOVERY_ID` | `docs/srs//req-*.md` | -| REQ 확정 | `.github/skills/confirm-req/SKILL.md` | `REQ-ID` | Approved REQ | -| REQ 변경 관리 | `.github/skills/change-req/SKILL.md` | `REQ-ID` | Change Log 기반 REQ 갱신 | -| REQ 구현 완료 전환 | `.github/skills/confirm-req-implemented/SKILL.md` | `REQ-ID` 또는 `BAT-ID` | Implemented REQ | -| Batch 추천 | `.github/skills/suggest-batch-reqs/SKILL.md` | `DISCOVERY_ID` 또는 REQ 목록 | batch 후보안 | -| Batch 생성 | `.github/skills/draft-batch/SKILL.md` | Approved REQ 목록 | `docs/batches//` | -| Delivery 진행 | `.github/skills/draft-batch-planning/SKILL.md`, `.github/skills/draft-batch-design/SKILL.md`, `.github/skills/run-batch-implementation/SKILL.md`, `.github/skills/draft-batch-verification/SKILL.md`, `.github/skills/confirm-batch-verification/SKILL.md` | `BAT-ID` | batch 문서와 상태 | -| Release 진행 | `.github/skills/draft-release/SKILL.md`, `.github/skills/confirm-release/SKILL.md`, `.github/skills/capture-release-feedback/SKILL.md` | `BAT-ID` 또는 `REL-ID` | release 문서와 운영 환류 | -| 다음 Discovery 추천 | `.github/skills/suggest-next-discovery/SKILL.md` | `REL-ID` 또는 없음 | follow-up 후보안 | -| 상위 orchestration | `.github/skills/run-sdlc/SKILL.md` | Discovery/REQ/Batch/Release 식별자 | 다음 skill 라우팅 | - -## 5. 금지 사항 - -- active 설명 문서에서 `.github/prompts/`를 기준 경로로 안내하지 않는다. -- `review-*`를 별도 active 단계로 서술하지 않는다. -- `docs/sdlc/`를 신규 active 산출물 경로로 사용하지 않는다. \ No newline at end of file diff --git a/.stage-pilot/source/instruction.md b/.stage-pilot/source/instruction.md deleted file mode 100644 index 623cf73..0000000 --- a/.stage-pilot/source/instruction.md +++ /dev/null @@ -1,80 +0,0 @@ -# Copilot Instructions - -이 파일은 호스트 프로젝트 고유 규칙과 StagePilot 규칙을 함께 담는다. - -## Host Project Rules - -호스트 프로젝트 전용 규칙은 이 섹션에 작성한다. - -## StagePilot - -# SDLC Governance Instructions - -이 워크스페이스의 AI 에이전트는 Agile 중심 반복형 SDLC를 따른다. -모든 작업은 3-phase 구조와 그 안의 단위별 게이트를 기준으로 수행하며, 임의 단계 스킵은 허용되지 않는다(긴급 핫픽스 예외는 별도 규칙 적용). - -## Lifecycle Phases - -1. Requirements -2. Delivery -3. Release & Feedback - -## Governing Units - -1. Discovery: 문제 정의 단위 -2. REQ: 승인 가능한 요구사항 단위 -3. Batch: 함께 계획, 설계, 구현, 검증하는 delivery 단위 -4. Release: 함께 배포하고 운영 환류를 기록하는 단위 - -## Bootstrap Baseline Path - -- 프로젝트 시작 시 baseline 문서와 active index는 `bootstrap-baseline`으로 먼저 초기화한다. -- `bootstrap-baseline`은 3-phase lifecycle 밖의 bootstrap 단계이며 별도 governance unit가 아니다. -- 첫 real Discovery는 baseline 초기화 이후 실제 제품/서비스 변경 주제로 시작한다. - -## Default Flow - -1. Bootstrap: `bootstrap-baseline` (baseline 문서와 active index가 비어 있을 때만) -2. Requirements: `new-discovery` -> `confirm-discovery` -> `draft-req` -> `confirm-req` -> `suggest-batch-reqs` -> `draft-batch` -3. Delivery: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` -4. Release & Feedback: `draft-release` -> `confirm-release` -> `capture-release-feedback` - -## Lightweight Change Path - -- 단일 저위험 `Approved` REQ이고 구조/인터페이스/런타임 흐름 영향이 없으면 `minor-change` fast path를 사용할 수 있다. -- 이 경로는 `suggest-batch-reqs`를 생략하고 `draft-batch`로 바로 `batch-lite` batch를 만든다. -- `batch-lite`는 planning부터 시작하고, design은 필요할 때만 생성한다. -- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 선택해 검증 강도를 조정한다. - -## Change Management Extension - -- 기존 REQ 본문을 수정할 때는 `change-req`를 사용해 Change Log 근거를 먼저 남긴다. -- release feedback의 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 분리해 기록한다. -- 다음 반복 후보만 먼저 정리하려면 `suggest-next-discovery`를 사용하고, 실제 문서 생성은 `new-discovery`로 이어 간다. - -## Stage Mapping - -- Discovery는 Requirements phase 안에서 유지한다. -- Planning, Design, Implementation, Verification은 Batch 내부 stage로 다룬다. -- Release와 Operations는 Release & Feedback phase 내부에서 다룬다. -- `review-*` stage command는 더 이상 active flow에 포함하지 않고 대응 `confirm-*` 절차에 흡수한다. -- prompt 기반 stage command는 active 경로에서 사용하지 않고 `.stage-pilot/skills/` 아래 skill entrypoint만 사용한다. - -## Global Rules - -1. 증거 기반: 요구사항, 가정, 리스크, 결정 근거를 문서에 남긴다. -2. 게이트 준수: 각 phase와 단위의 DoR(진입 조건)과 DoD(완료 조건)을 충족해야 다음 단계로 이동한다. -3. 테스트 우선 검증: 구현 결과는 테스트 또는 검증 근거 없이 완료로 간주하지 않는다. -4. 변경 영향도 기록: 코드/문서/운영 영향 범위를 명시한다. -5. 불확실성 우선 해소: 차단 이슈가 있으면 임의 추정 대신 질문 또는 추가 탐색을 수행한다. -6. 운영 환류: 운영 단계 인사이트는 다음 Discovery의 입력으로 반드시 반영한다. - -## Hotfix Exception Path - -긴급 핫픽스는 Requirements/Delivery 설계를 경량화할 수 있다. 단, 아래는 필수다. - -1. 최소 요구사항 정의(증상, 영향, 성공 조건) -2. 최소 검증(재현 테스트 또는 핵심 시나리오 확인) -3. 배포 전 롤백 계획 -4. 배포 후 Postmortem 작성 및 다음 스프린트 환류 - diff --git a/.stage-pilot/templates/batches/batch-index.md b/.stage-pilot/templates/batches/batch-index.md deleted file mode 100644 index 8aca9a2..0000000 --- a/.stage-pilot/templates/batches/batch-index.md +++ /dev/null @@ -1,25 +0,0 @@ -# {{BAT_ID}}: <배치 제목> - -- Status: draft | in-delivery | release-candidate | released | archived -- Profile: standard | batch-lite -- Source Discovery: -- Included REQ: -- Excluded REQ: - -## Summary -- <이 batch를 왜 묶었는지> - -## Scope -- In Scope: - - <포함 범위> -- Out of Scope: - - <제외 범위> - -## Documents -- Planning: [planning.md](./planning.md) -- Design: <`[design.md](./design.md)` 또는 `none (batch-lite with no architecture impact)`> -- Implementation: <`[implementation.md](./implementation.md)` 또는 `create at implementation stage`> -- Verification: <`[verification.md](./verification.md)` 또는 `create at verification stage`> - -## Notes -- <공통 리스크, 공통 결정 사항> diff --git a/.stage-pilot/templates/batches/design.md b/.stage-pilot/templates/batches/design.md deleted file mode 100644 index 87d718b..0000000 --- a/.stage-pilot/templates/batches/design.md +++ /dev/null @@ -1,26 +0,0 @@ -# {{BAT_ID}} Design - -- Status: draft | confirmed -- Batch: {{BAT_ID}} - -## Architecture Summary -- <배치 수준 설계 요약> - -## Changed Areas -- Docs: <문서 경로> -- Modules: <모듈/컴포넌트> -- Interfaces: <인터페이스> - -## Key Decisions -1. <결정 1> -2. <결정 2> - -## Edge Cases -- <예외 처리 및 설계 경계> - -## Architecture Impact -- - -## Reference Doc Update Plan -- project-structure.md: <갱신 계획 또는 none> -- runtime-flows.md: <갱신 계획 또는 none> diff --git a/.stage-pilot/templates/batches/implementation.md b/.stage-pilot/templates/batches/implementation.md deleted file mode 100644 index d69112e..0000000 --- a/.stage-pilot/templates/batches/implementation.md +++ /dev/null @@ -1,20 +0,0 @@ -# {{BAT_ID}} Implementation - -- Status: draft | in-progress | completed -- Batch: {{BAT_ID}} - -## Plan Summary -- <이번 구현에서 실제 변경할 범위> - -## Changed Files -- <파일 목록> - -## Execution Log -1. <실행 항목> -2. <실행 항목> - -## Validation -- <실행한 테스트, lint, typecheck, 수동 점검> - -## Remaining Risks -- <잔여 이슈> diff --git a/.stage-pilot/templates/batches/index.md b/.stage-pilot/templates/batches/index.md deleted file mode 100644 index 158750c..0000000 --- a/.stage-pilot/templates/batches/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Batch Index - -## 목적 -- 이 문서는 batch delivery 단위의 전역 인덱스다. -- 각 batch는 `docs/batches//` 아래에서 profile(`standard` | `batch-lite`)에 맞는 산출물을 관리한다. - -## Batch Register - -| BAT ID | Profile | Status | Included REQ | Discovery | Folder | -| --- | --- | --- | --- | --- | --- | diff --git a/.stage-pilot/templates/batches/planning.md b/.stage-pilot/templates/batches/planning.md deleted file mode 100644 index 324adbd..0000000 --- a/.stage-pilot/templates/batches/planning.md +++ /dev/null @@ -1,28 +0,0 @@ -# {{BAT_ID}} Planning - -- Status: draft | confirmed -- Batch: {{BAT_ID}} -- Profile: standard | batch-lite - -## Included REQ -- - -## Delivery Plan -- <구현 범위, 순서, 선행 조건> - -## Dependencies -- Internal: - - <내부 의존성> -- External: - - <외부 의존성> - -## Design Gate -- Design Required Before Implementation: yes | no -- Reason: <왜 즉시 design이 필요한지 또는 왜 생략 가능한지> - -## Milestones -1. <마일스톤> -2. <마일스톤> - -## Risks -- <리스크와 대응> diff --git a/.stage-pilot/templates/batches/verification.md b/.stage-pilot/templates/batches/verification.md deleted file mode 100644 index 0a7971e..0000000 --- a/.stage-pilot/templates/batches/verification.md +++ /dev/null @@ -1,21 +0,0 @@ -# {{BAT_ID}} Verification - -- Status: draft | confirmed -- Batch: {{BAT_ID}} - -## Acceptance Mapping -- : - - - -## Evidence -- Test: - - <테스트 결과> -- Manual: - - <수동 검증 결과> -- Logs: - - <로그 또는 스크린샷 경로> - -## Result -- Ready for Release: false -- Blocking Issues: - - <차단 이슈 또는 없음> diff --git a/.stage-pilot/templates/bootstrap/baseline-seed.yaml b/.stage-pilot/templates/bootstrap/baseline-seed.yaml deleted file mode 100644 index 41c3cbf..0000000 --- a/.stage-pilot/templates/bootstrap/baseline-seed.yaml +++ /dev/null @@ -1,95 +0,0 @@ -schema_version: 1 -capture_mode: declared # declared | observed | mixed - -project: - repository_name: {{REPOSITORY_NAME:repo-folder-name}} - display_name: {{PROJECT_NAME:display name or repo name}} - summary: {{PROJECT_SUMMARY:one-sentence project summary}} - primary_domain: {{PRIMARY_DOMAIN:short domain phrase or undecided}} - -stack: - tech_stack: - - {{TECH_STACK_ITEM_1:primary language or framework}} - -runtime: - primary_runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} - primary_entrypoints: - - name: {{ENTRYPOINT_NAME_1:command or interface name}} - purpose: {{ENTRYPOINT_PURPOSE_1:brief purpose}} - -interfaces: - contracts: - - name: {{INTERFACE_NAME_1:primary interface name}} - type: {{INTERFACE_TYPE_1:http-api|cli|event|file|internal-service|other}} - actors: {{INTERFACE_ACTORS_1:who calls or receives it}} - purpose: {{INTERFACE_PURPOSE_1:what this interface exists to do}} - stability: {{INTERFACE_STABILITY_1:experimental|internal|stable}} - inputs: - - {{INTERFACE_INPUT_1_1:input contract or invocation note}} - outputs: - - {{INTERFACE_OUTPUT_1_1:output contract or artifact}} - errors: - - {{INTERFACE_ERROR_1_1:error code or failure behavior}} - compatibility_rules: - - {{INTERFACE_COMPATIBILITY_1_1:compatibility or versioning rule}} - - name: {{INTERFACE_NAME_2:secondary interface name}} - type: {{INTERFACE_TYPE_2:http-api|cli|event|file|internal-service|other}} - actors: {{INTERFACE_ACTORS_2:who calls or receives it}} - purpose: {{INTERFACE_PURPOSE_2:what this interface exists to do}} - stability: {{INTERFACE_STABILITY_2:experimental|internal|stable}} - inputs: - - {{INTERFACE_INPUT_2_1:input contract or invocation note}} - outputs: - - {{INTERFACE_OUTPUT_2_1:output contract or artifact}} - errors: - - {{INTERFACE_ERROR_2_1:error code or failure behavior}} - compatibility_rules: - - {{INTERFACE_COMPATIBILITY_2_1:compatibility or versioning rule}} - shared_constraints: - - {{INTERFACE_SHARED_CONSTRAINT_1:cross-cutting interface constraint}} - current_gaps: - - {{INTERFACE_GAP_1:known contract gap or planned change}} - -structure: - planned_top_level_areas: - - path: {{TOP_LEVEL_PATH_1:path such as src/ or app/}} - responsibility: {{TOP_LEVEL_RESPONSIBILITY_1:brief responsibility}} - -data_model: - persistence_backend: {{PERSISTENCE_BACKEND:primary persistence backend or storage system}} - entities: - - name: {{ENTITY_NAME_1:primary entity}} - purpose: {{ENTITY_PURPOSE_1:what this entity represents}} - source_of_truth: {{ENTITY_SOURCE_1:table, collection, file, or service}} - lifecycle: {{ENTITY_LIFECYCLE_1:created -> updated -> archived}} - key_fields: - - {{ENTITY_FIELD_1_1:field :: type :: meaning}} - relationships: - - {{ENTITY_RELATIONSHIP_1_1:related entity :: relationship meaning}} - state_rules: - - {{ENTITY_STATE_RULE_1_1:state or invariant rule}} - - name: {{ENTITY_NAME_2:secondary entity}} - purpose: {{ENTITY_PURPOSE_2:what this entity represents}} - source_of_truth: {{ENTITY_SOURCE_2:table, collection, file, or service}} - lifecycle: {{ENTITY_LIFECYCLE_2:created -> updated -> archived}} - key_fields: - - {{ENTITY_FIELD_2_1:field :: type :: meaning}} - relationships: - - {{ENTITY_RELATIONSHIP_2_1:related entity :: relationship meaning}} - state_rules: - - {{ENTITY_STATE_RULE_2_1:state or invariant rule}} - consistency_rules: - - {{CONSISTENCY_RULE_1:consistency or referential rule}} - persistence_notes: - - {{PERSISTENCE_NOTE_1:mapping, storage, or migration note}} - current_gaps: - - {{MODEL_GAP_1:known model gap or planned schema change}} - -notes: - known_unknowns: - - {{KNOWN_UNKNOWN_1:optional unresolved bootstrap item or none}} - -metadata: - captured_at_kst: {{CAPTURED_AT_KST:YYYY-MM-DD HH:MM}} - source: bootstrap-baseline-questionnaire - updated_from: {{UPDATED_FROM:user-answer|repo-observation|mixed}} \ No newline at end of file diff --git a/.stage-pilot/templates/data-model.md b/.stage-pilot/templates/data-model.md deleted file mode 100644 index 2709090..0000000 --- a/.stage-pilot/templates/data-model.md +++ /dev/null @@ -1,88 +0,0 @@ -# {{PROJECT_NAME}} Data Model - -- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} -- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} -- Status: draft | confirmed -- Owner: {{OWNER_OR_TEAM}} -- Last Updated (KST): {{UPDATED_AT_KST}} -- Source Discovery / Batch: {{SOURCE_REF}} - -## Project Summary -- {{PROJECT_SUMMARY:one-sentence summary of the project}} - -## Primary Domain -- {{PRIMARY_DOMAIN:short domain phrase or undecided}} - -## Primary Persistence Backend -- {{PERSISTENCE_BACKEND:primary persistence backend or storage system}} - -## Purpose -- This document captures the approved cross-cutting data model baseline for the project. -- It is a shared reference for REQ drafting, batch design, implementation, verification, migration planning, and handoff. - -## Scope -- Covers core entities, relationships, states, persistence mappings, and shared consistency rules. -- Does not duplicate full functional acceptance criteria already owned by REQ documents. -- Does not replace ORM definitions, migration files, or generated schema artifacts. - -## Model Summary - -{{MODEL_SUMMARY_BULLETS:- - :: }} - -## Entity: `{{ENTITY_NAME_1:primary entity}}` - -- Purpose: {{ENTITY_PURPOSE_1:what this entity represents}} -- Source of Truth: {{ENTITY_SOURCE_1:table, collection, file, or service}} -- Lifecycle: {{ENTITY_LIFECYCLE_1:created -> updated -> archived}} - -### Key Fields - -{{ENTITY_FIELDS_1:- :: :: }} - -### Relationships - -{{ENTITY_RELATIONSHIPS_1:- :: }} - -### State Rules - -{{ENTITY_STATE_RULES_1:- }} - -## Entity: `{{ENTITY_NAME_2:secondary entity}}` - -- Purpose: {{ENTITY_PURPOSE_2:what this entity represents}} -- Source of Truth: {{ENTITY_SOURCE_2:table, collection, file, or service}} -- Lifecycle: {{ENTITY_LIFECYCLE_2:created -> updated -> archived}} - -### Key Fields - -{{ENTITY_FIELDS_2:- :: :: }} - -### Relationships - -{{ENTITY_RELATIONSHIPS_2:- :: }} - -### State Rules - -{{ENTITY_STATE_RULES_2:- }} - -## Shared Consistency Rules - -{{CONSISTENCY_RULE_BULLETS:- - }} - -## Persistence / Integration Notes - -{{PERSISTENCE_NOTES_BULLETS:- - }} - -## Current Gaps / Planned Changes - -{{MODEL_CURRENT_GAPS_BULLETS:- - }} - -## Update Triggers - -- Update this document when a core entity, key field, or relationship changes. -- Update this document when lifecycle or persistence behavior changes. -- Update this document when a batch introduces a migration, backfill, or breaking data assumption. - -## Change Log - -{{CHANGE_LOG_BULLETS:- - }} \ No newline at end of file diff --git a/.stage-pilot/templates/discovery/discovery.md b/.stage-pilot/templates/discovery/discovery.md deleted file mode 100644 index dc3c13d..0000000 --- a/.stage-pilot/templates/discovery/discovery.md +++ /dev/null @@ -1,224 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: {{DOC_STATUS:draft|review|confirmed}} -- 문서 ID: {{DISCOVERY_ID:dcy-<3자리>__}} -- 이슈명: {{ISSUE_NAME:짧은 한글 또는 영문 이슈명}} -- 작성 시각(KST): {{CREATED_AT_KST:TBD}} -- 마지막 갱신 시각(KST): {{UPDATED_AT_KST:TBD}} -- 대체됨: {{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}} -- 후속 Discovery 참조: {{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}} -- 생성된 REQ 참조: 없음 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: {{RELATED_SCORE_FILE:미생성 또는 경로}} - - 관련 TODO/메모: {{TODO_FILE:경로 또는 없음}} -- 해석: - - {{SUMMARY_LINE_1:현재 검토/작성 배경 요약}} - - {{SUMMARY_LINE_2:이번 Discovery에서 판단해야 할 핵심 요약}} - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: {{REQUIREMENT_CLASSIFICATION:완전 중복|유사|신규}} -- 근거 - - {{CLASSIFICATION_REASON_1}} - - {{CLASSIFICATION_REASON_2}} - -> **판정이 `완전 중복`인 경우**: 이하 섹션 작성을 중단하고 `# 9. 파일 처리 결과`에 중복 판정 사유와 참조 문서 경로를 기록한 뒤 문서를 닫는다. REQ 초안 작성으로 넘기지 않는다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - {{CURRENT_STATE_1}} - - {{CURRENT_STATE_2}} -- 기대 상태 - - {{TARGET_STATE_1}} - - {{TARGET_STATE_2}} -- 이해관계자 - - {{STAKEHOLDER_1:예. 문서 작성자(AI agent)}} - - {{STAKEHOLDER_2:예. 리뷰어/승인자}} - - {{STAKEHOLDER_3:예. 운영 담당자}} -- 제약사항 - - {{CONSTRAINT_1}} - - {{CONSTRAINT_2}} - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- {{USER_SCENARIO_1}} -- {{USER_SCENARIO_2}} -- {{USER_SCENARIO_3}} - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- {{CHANGE_ITEM_1}} -- {{CHANGE_ITEM_2:필요 없으면 삭제}} -- {{CHANGE_ITEM_3:필요 없으면 삭제}} - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - {{CODE_IMPACT_1:예. docs/discovery/index.md 갱신 로직}} - - {{CODE_IMPACT_2:필요 없으면 삭제}} -- 문서 영향: - - {{DOC_IMPACT_1}} - - {{DOC_IMPACT_2:필요 없으면 삭제}} -- 운영 영향: - - {{OPS_IMPACT_1:예. 배포/운영 절차 변경 없음}} - - {{OPS_IMPACT_2:필요 없으면 삭제}} - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: {{FR_1}} -- FR-2: {{FR_2}} -- FR-3: {{FR_3:필요 없으면 삭제}} -- FR-4: {{FR_4:필요 없으면 삭제}} - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: {{NFR_1}} -- NFR-2: {{NFR_2}} -- NFR-3: {{NFR_3:필요 없으면 삭제}} - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - {{IN_SCOPE_1}} - - {{IN_SCOPE_2}} -- Out of Scope - - {{OUT_OF_SCOPE_1}} - - {{OUT_OF_SCOPE_2}} - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 ({{RISK_LEVEL_1:High|Medium|Low}}): {{RISK_1}} - - 완화 방안: {{MITIGATION_1}} -- R-2 ({{RISK_LEVEL_2:High|Medium|Low}}): {{RISK_2}} - - 완화 방안: {{MITIGATION_2}} -- R-3 ({{RISK_LEVEL_3:High|Medium|Low}}): {{RISK_3:필요 없으면 삭제}} - - 완화 방안: {{MITIGATION_3:필요 없으면 삭제}} - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: {{ASSUMPTION_1}} -- A-2: {{ASSUMPTION_2}} -- A-3: {{ASSUMPTION_3:필요 없으면 삭제}} - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: {{OPEN_QUESTION_1}} - - 상태: {{OPEN_QUESTION_STATUS_1:Open|Answered|Deferred|Dropped}} - - 처리 방안: {{OPEN_QUESTION_ACTION_1:누가 어떤 방식으로 결정/확인할지}} - - 종료 조건: {{OPEN_QUESTION_EXIT_1:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} -- OQ-2: {{OPEN_QUESTION_2}} - - 상태: {{OPEN_QUESTION_STATUS_2:Open|Answered|Deferred|Dropped}} - - 처리 방안: {{OPEN_QUESTION_ACTION_2:누가 어떤 방식으로 결정/확인할지}} - - 종료 조건: {{OPEN_QUESTION_EXIT_2:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} -- OQ-3: {{OPEN_QUESTION_3:필요 없으면 삭제}} - - 상태: {{OPEN_QUESTION_STATUS_3:Open|Answered|Deferred|Dropped}} - - 처리 방안: {{OPEN_QUESTION_ACTION_3:누가 어떤 방식으로 결정/확인할지}} - - 종료 조건: {{OPEN_QUESTION_EXIT_3:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: {{SUCCESS_METRIC_1}} -- S-2: {{SUCCESS_METRIC_2}} -- S-3: {{SUCCESS_METRIC_3:필요 없으면 삭제}} - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: {{MEASURE_METHOD_1}} / 데이터 출처: {{MEASURE_SOURCE_1}} -- S-2 측정 방식: {{MEASURE_METHOD_2}} / 데이터 출처: {{MEASURE_SOURCE_2}} -- S-3 측정 방식: {{MEASURE_METHOD_3:필요 없으면 삭제}} / 데이터 출처: {{MEASURE_SOURCE_3:필요 없으면 삭제}} - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [ ] 범위(In Scope/Out of Scope) 확정 - - 기준: {{CHECK_SCOPE_CRITERIA}} -- [ ] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: {{CHECK_METRICS_CRITERIA}} -- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: {{CHECK_RISK_OWNER_CRITERIA}} -- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: {{CHECK_CONFIRMEDBY_CRITERIA}} - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: {{FILE_RESULT:생성|갱신|미생성}} -- 생성 경로: {{OUTPUT_PATH}} -- 참조 문서: - - {{REFERENCE_1}} - - {{REFERENCE_2}} - - {{REFERENCE_3:필요 없으면 삭제}} - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- {{DECIDE_ITEM_1}} -- {{DECIDE_ITEM_2:필요 없으면 삭제}} - -## CONFIRM -- {{CONFIRM_ITEM_1}} -- {{CONFIRM_ITEM_2:필요 없으면 삭제}} - -## DATA -- {{DATA_ITEM_1}} -- {{DATA_ITEM_2:필요 없으면 삭제}} - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: {{FREEZE_HANDOFF_DECISION:REQ Drafting 진행 가능|보류}} -- Handoff Rationale: {{FREEZE_HANDOFF_RATIONALE:진행 가능 또는 보류 판단 근거 1-2줄}} -- Ready for REQ Drafting: {{READY_FOR_REQ_DRAFTING:false}} -- Confirmed By: {{CONFIRMED_BY:TBD}} -- Confirmed At (KST): {{CONFIRMED_AT_KST:TBD}} - -# 12. 작성 가이드 -템플릿 사용 시의 기본 규칙을 적는다. 실제 문서를 만들 때는 이 가이드를 기준으로 플레이스홀더를 치환한다. - -- 이 파일은 템플릿이다. 실제 문서 작성 시 `{{...}}` 플레이스홀더를 구체 값으로 교체한다. -- 필요 없는 항목은 삭제하고, 부족한 항목은 같은 형식으로 추가한다. -- Open Questions는 가정으로 대체하지 말고 그대로 유지한다. -- `대체됨`은 현재 Discovery가 더 이상 기준 문서가 아닐 때 대체 Discovery 경로를 기록하고, 해당 사항이 없으면 `없음`으로 둔다. -- `후속 Discovery 참조`는 범위를 이어받는 별도 Discovery가 생성된 경우 그 Discovery 경로를 기록하고, 없으면 `없음`으로 둔다. -- Discovery Freeze는 문서 하단에 반드시 유지한다. \ No newline at end of file diff --git a/.stage-pilot/templates/discovery/index.md b/.stage-pilot/templates/discovery/index.md deleted file mode 100644 index 8c9f028..0000000 --- a/.stage-pilot/templates/discovery/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Discovery 전체 인덱스 - - - -## Discovery 목록 - -| ID | 날짜 | 이슈명 | 문서 | 상태 | -|---|---|---|---|---| \ No newline at end of file diff --git a/.stage-pilot/templates/interface-contract.md b/.stage-pilot/templates/interface-contract.md deleted file mode 100644 index 48bdbb8..0000000 --- a/.stage-pilot/templates/interface-contract.md +++ /dev/null @@ -1,92 +0,0 @@ -# {{PROJECT_NAME}} Interface Contract - -- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} -- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} -- Status: draft | confirmed -- Owner: {{OWNER_OR_TEAM}} -- Last Updated (KST): {{UPDATED_AT_KST}} -- Source Discovery / Batch: {{SOURCE_REF}} -- Primary Runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} - -## Project Summary -- {{PROJECT_SUMMARY:one-sentence summary of the project}} - -## Primary Domain -- {{PRIMARY_DOMAIN:short domain phrase or undecided}} - -## Purpose -- This document captures the approved external and cross-boundary interface contracts for the project. -- It is a cross-cutting reference for REQ drafting, batch design, implementation, verification, and handoff. - -## Scope -- Covers externally visible APIs, CLI surfaces, events, files, or batch I/O contracts. -- Does not duplicate full business requirements already owned by REQ documents. -- Does not replace low-level code references or generated API artifacts. - -## Interface Summary - -{{INTERFACE_SUMMARY_BULLETS:- - :: }} - -## Interface: `{{INTERFACE_NAME_1:primary interface name}}` - -- Type: {{INTERFACE_TYPE_1:http-api|cli|event|file|internal-service|other}} -- Consumers / Producers: {{INTERFACE_ACTORS_1:who calls or receives it}} -- Purpose: {{INTERFACE_PURPOSE_1:what this interface exists to do}} -- Stability: {{INTERFACE_STABILITY_1:experimental|internal|stable}} - -### Inputs - -{{INTERFACE_INPUTS_1:- :: }} - -### Outputs - -{{INTERFACE_OUTPUTS_1:- :: }} - -### Error Contract - -{{INTERFACE_ERRORS_1:- :: }} - -### Compatibility Rules - -{{INTERFACE_COMPATIBILITY_1:- }} - -## Interface: `{{INTERFACE_NAME_2:secondary interface name}}` - -- Type: {{INTERFACE_TYPE_2:http-api|cli|event|file|internal-service|other}} -- Consumers / Producers: {{INTERFACE_ACTORS_2:who calls or receives it}} -- Purpose: {{INTERFACE_PURPOSE_2:what this interface exists to do}} -- Stability: {{INTERFACE_STABILITY_2:experimental|internal|stable}} - -### Inputs - -{{INTERFACE_INPUTS_2:- :: }} - -### Outputs - -{{INTERFACE_OUTPUTS_2:- :: }} - -### Error Contract - -{{INTERFACE_ERRORS_2:- :: }} - -### Compatibility Rules - -{{INTERFACE_COMPATIBILITY_2:- }} - -## Shared Constraints - -{{INTERFACE_SHARED_CONSTRAINTS_BULLETS:- - }} - -## Current Gaps / Planned Changes - -{{INTERFACE_CURRENT_GAPS_BULLETS:- - }} - -## Update Triggers - -- Update this document when an externally visible API, event, CLI, or file contract changes. -- Update this document when a batch changes compatibility, validation, or error behavior. -- Update this document when a new external consumer or producer is introduced. - -## Change Log - -{{CHANGE_LOG_BULLETS:- - }} \ No newline at end of file diff --git a/.stage-pilot/templates/project-structure.md b/.stage-pilot/templates/project-structure.md deleted file mode 100644 index 844d3ae..0000000 --- a/.stage-pilot/templates/project-structure.md +++ /dev/null @@ -1,64 +0,0 @@ -# {{PROJECT_NAME}} Project Structure - -- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} -- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} -- Status: draft | confirmed -- Owner: {{OWNER_OR_TEAM}} -- Last Updated (KST): {{UPDATED_AT_KST}} -- Source Discovery / Batch: {{SOURCE_REF}} - -## Project Summary -- {{PROJECT_SUMMARY:one-sentence summary of the project}} - -## Primary Domain -- {{PRIMARY_DOMAIN:short domain phrase or undecided}} - -## Tech Stack -{{TECH_STACK_BULLETS:- -- }} - -## Primary Runtime -- Type: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} -- Planned Entry Points: -{{PRIMARY_ENTRYPOINT_BULLETS:- :: }} - -## Purpose -- This document is the current approved repository and package structure baseline. -- It is a cross-cutting reference document for Discovery, REQ, Batch Design, Batch Verification, and implementation updates. - -## Scope -- Covers the top-level repository layout and package/module boundaries. -- Does not duplicate detailed business rules, acceptance criteria, or implementation-only function signatures. - -## Top-Level Structure - -```text -{{TOP_LEVEL_STRUCTURE_TREE:}} -``` - -## Package / Module Responsibilities - -{{MODULE_RESPONSIBILITY_BULLETS:- `` - - }} - -## Dependency Rules - -{{DEPENDENCY_RULE_BULLETS:- }} - -## Shared Boundaries - -{{SHARED_BOUNDARY_BULLETS:- }} - -## Current Gaps / Planned Changes - -{{CURRENT_GAPS_BULLETS:- }} - -## Update Triggers - -- Update this document when package boundaries change. -- Update this document when new top-level runtime or domain modules are added. -- Update this document when a batch changes the approved structure baseline. - -## Change Log - -{{CHANGE_LOG_BULLETS:- - }} diff --git a/.stage-pilot/templates/releases/index.md b/.stage-pilot/templates/releases/index.md deleted file mode 100644 index 1b66789..0000000 --- a/.stage-pilot/templates/releases/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Release Index - -## 목적 -- 이 문서는 release 단위의 전역 인덱스다. -- 각 release는 하나 이상의 release-candidate batch를 묶고, profile(`docs-only` | `tooling` | `app-service`)에 맞는 배포와 후속 환류를 추적한다. - -## Release Register - -| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | -| --- | --- | --- | --- | --- | --- | diff --git a/.stage-pilot/templates/releases/release.md b/.stage-pilot/templates/releases/release.md deleted file mode 100644 index ce1e43c..0000000 --- a/.stage-pilot/templates/releases/release.md +++ /dev/null @@ -1,92 +0,0 @@ -# {{REL_ID}}: <릴리즈 제목> - -- Status: draft | confirmed | released | feedback-captured -- Profile: docs-only | tooling | app-service -- Included Batch: - -## Scope -- Included: - - <포함 batch 또는 REQ> -- Excluded: - - <제외 범위> - -## Rollout Plan -1. <배포 순서> -2. <배포 순서> - -## Rollback Plan -- <롤백 조건과 절차> - -## Verification Checklist -- <배포 전 확인> -- <배포 후 확인> - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - <배포 시작 시각> -- Completed At: - - <배포 완료 시각> -- Environment: - - <배포 대상 환경> -- Operator: - - <배포 수행자> -- Revision or Artifact: - - <커밋 SHA 또는 배포 artifact 식별자> -- Commands or Procedure: - - <실행 명령 또는 배포 절차 요약> - -### Profile-Specific Checks -- docs-only: - - Published or Packaged Targets: - - <문서 경로, 배포 산출물 또는 none> - - Link or Rendering Check: - - <링크 점검, 렌더링 확인 또는 none> - - Navigation or Consumer Update: - - <목차/가이드/릴리즈 노트 반영 결과 또는 none> -- tooling: - - Install or Upgrade Smoke: - - <설치/업데이트/부트스트랩 결과 또는 none> - - CLI or Script Execution: - - <명령 실행 결과 또는 none> - - Compatibility Note: - - <하위 호환성/운영자 영향 또는 none> -- app-service: - - Health Endpoint: - - <호출 결과와 확인 시각> - - Startup Log Fields: - - - - Request Log Fields: - - - - Smoke or Manual Verification: - - <배포 후 재실행한 검증과 결과> - -### Rollback Outcome -- Rollback Required: - - -- Rollback Trigger: - - <발생 시 원인, 없으면 none> -- Rollback Action: - - <수행한 조치, 없으면 none> - -### Release Decision Evidence -- Released Decision: - - -- Rationale: - - -- Recorded By: - - <기록자> -- Recorded At: - - <기록 시각> - -## Feedback Handoff -- Observation Summary: - - <운영 관찰 사실 요약 또는 없음> -- Discovery Input: - - <후속 discovery 후보> -- REQ Input: - - <후속 req 후보> -- Change Request Input: - - <기존 req 변경 후보> \ No newline at end of file diff --git a/.stage-pilot/templates/runtime-flows.md b/.stage-pilot/templates/runtime-flows.md deleted file mode 100644 index 0720694..0000000 --- a/.stage-pilot/templates/runtime-flows.md +++ /dev/null @@ -1,58 +0,0 @@ -# {{PROJECT_NAME}} Runtime Flows - -- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} -- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} -- Status: draft | confirmed -- Owner: {{OWNER_OR_TEAM}} -- Last Updated (KST): {{UPDATED_AT_KST}} -- Source Discovery / Batch: {{SOURCE_REF}} -- Primary Runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} - -## Project Summary -- {{PROJECT_SUMMARY:one-sentence summary of the project}} - -## Purpose -- This document is the current approved execution-flow baseline for the project. -- It is a cross-cutting reference document for Discovery, REQ, Batch Design, Batch Verification, and implementation updates. - -## Scope -- Covers representative command and runtime flows. -- Does not duplicate full acceptance criteria or low-level implementation details. - -## Covered Entry Points - -{{PRIMARY_ENTRYPOINT_BULLETS:- :: }} - -## Shared Components - -{{SHARED_COMPONENT_BULLETS:- }} - -## Flow: `{{FLOW_NAME_1:bootstrap or first runtime flow}}` - -{{FLOW_STEPS_1:1. -2. -3. }} - -## Flow: `{{FLOW_NAME_2:secondary runtime flow or operation}}` - -{{FLOW_STEPS_2:1. -2. -3. }} - -## Flow Constraints - -{{FLOW_CONSTRAINT_BULLETS:- }} - -## Current Gaps / Planned Changes - -{{CURRENT_GAPS_BULLETS:- }} - -## Update Triggers - -- Update this document when a command entry point changes. -- Update this document when a runtime orchestration path changes. -- Update this document when a batch changes the approved flow baseline. - -## Change Log - -{{CHANGE_LOG_BULLETS:- - }} diff --git a/.stage-pilot/templates/srs/index.md b/.stage-pilot/templates/srs/index.md deleted file mode 100644 index 67c789c..0000000 --- a/.stage-pilot/templates/srs/index.md +++ /dev/null @@ -1,87 +0,0 @@ -# Software Requirement Specification Index - -## 목적 -- 이 문서는 프로젝트의 SRS 운영 기준과 requirement 목록을 관리하는 entry point이다. -- 개별 requirement의 상세 내용은 타입별 폴더 하위 `req-XXX_*.md` 파일에서 관리한다. - -## 사용 방법 -1. 새로운 requirement가 필요하면 `Next Requirement ID`를 확인하고 `docs/srs/req-template.md`를 복사해 `docs/srs//req-XXX_.md` 파일을 생성한다. -2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. -3. 작성 또는 수정 후 누락과 모호성을 점검한다. -4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다. -5. `Proposed` requirement를 승인하려면 승인 준비 상태와 blocker를 점검한다. -6. `Approved` requirement는 구현 이슈나 작업 항목으로 연결한다. -7. 구현과 검증이 끝난 requirement는 `confirm-req-implemented` skill로 완료 전환 가능 여부를 점검한다. -8. requirement 변경 시 `change-req` skill을 기준으로 해당 REQ 파일의 `Change Log`를 먼저 갱신한 뒤 본문을 수정한다. -9. 구현 전에는 관련 REQ 파일과 최근 `Change Log`를 기준으로 변경 범위를 판단한다. - -## 상태 정의 -- Proposed: 초안 상태이며 구현 기준으로 확정되지 않음 -- Approved: 구현 기준으로 승인됨 -- Implemented: 구현과 검증이 완료됨 -- Deprecated: 더 이상 유지하지 않음 - -## Status Transition Evidence - -### Proposed -> Approved -- `Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`가 모두 존재해야 한다. -- `Type`은 아래 분류 도표에서 정확하게 하나로 지정되어야 한다. -- `Acceptance Criteria`는 구현 판단에 사용할 수 있을 정도로 구체적이어야 한다. -- 미해결 assumption 또는 open question이 있으면 `Notes`에 명시하고, 승인 blocker 여부를 판단해야 한다. -- 승인 전환 시 해당 REQ 파일의 `Change Log`에 status 변경 사유를 기록해야 한다. - -### Approved -> Implemented -- 각 `Acceptance Criteria`에 대해 test, manual verification, 또는 구현 결과 중 최소 1개의 evidence가 있어야 한다. -- evidence는 requirement 문서 자체 또는 연결된 구현/검증 결과에서 추적 가능해야 한다. -- 미충족 `Acceptance Criteria`가 있으면 `Implemented`로 전환하지 않는다. -- 구현 완료 전환 시 잔여 리스크와 미검증 영역이 있으면 `Change Log`에 기록해야 한다. -- 상태 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. - -### Requirement Change Handling -- 기존 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`)을 수정하기 전에는 새 `Change Log` 항목을 만든다. -- `Change Log`에는 최소한 `Acceptance Criteria Delta`, `Impacted Area Delta`, `Delivery Trace Delta`, `Revalidation Impact`, `Validation Plan`이 포함되어야 한다. -- 변경이 기존 구현 또는 verification evidence를 무효화하면 `Implemented` requirement는 `Approved`로 되돌린다. -- 변경이 승인 전제 자체를 다시 검토하게 만들면 `Approved` 또는 `Implemented` requirement를 `Proposed`로 되돌릴 수 있다. -- 상태 되돌림이 발생하면 `Requirement Register`와 `Recent Change Log Summary`도 함께 갱신한다. - -## Requirement 타입 분류 도표 - -| 타입 | 정의 | 예시 | -|------|------|------| -| **Configuration** | 설정 옵션, 커스터마이징 포인트 | 설정 파라미터, 환경 변수, 옵션 항목 | -| **Data** | 데이터 구조, 저장/로드, 파일 형식, 스키마 | 설정 파일 형식, DB 스키마, API 구조 | -| **Deployment** | 배포 방식, 운영 절차, 배포 환경 | 컨테이너 배포, 스택 관리, 운영 가이드 | -| **Documentation** | 문서 작성, 가이드, 매뉴얼, 안내서 | 사용자 가이드, API 문서, 튜토리얼 | -| **Exception** | 오류 처리, 예외 복구, 재시도 전략 | 오류 복구, 검증 실패 처리, 폴백 전략 | -| **Installation** | 시스템 설치, 환경 구성, 초기화 절차 | Docker 이미지 빌드, DB 마이그레이션, 사전 조건 설정 | -| **Integration** | 외부 시스템 연동, 플러그인, 확장 | 3rd party 연동, 플러그인 인터페이스 | -| **Interface** | 사용자 상호작용 방식 (UI, API, CLI, 제어방식) | CLI 명령어, API 엔드포인트, 웹 UI 기능 | -| **Migration** | 데이터 이전, 버전 업그레이드 | 데이터 마이그레이션, 버전 호환성 | -| **Non-Functional** | 성능, 보안, 확장성, 호환성, 신뢰성 | 성능 목표, 보안 정책, 확장성 | -| **Testing** | 테스트 전략, QA 요구사항, 검증 방식 | 테스트 커버리지, 회귀 테스트, 수락 기준 | - -## Requirement 작성 규칙 -- 각 requirement는 고유 ID를 가진다. 예: `REQ-001` -- 다음 신규 requirement 번호의 source of truth는 이 문서의 `Next Requirement ID` 섹션이다. -- 새 requirement를 추가하거나 ID 충돌이 발생할 수 있는 변경을 했으면 `Next Requirement ID`를 함께 갱신한다. -- 각 requirement는 `Type`을 명시해야 한다. 위 분류 도표 중 정확하게 하나로 선택하며, `docs/srs//` 폴더명도 같은 문자열을 사용한다. -- 각 requirement는 `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`를 반드시 포함한다. -- 모호한 표현("개선한다", "적절히 처리한다") 대신 관찰 가능하고 test 가능한 문장을 사용한다. -- requirement 변경 사항은 해당 REQ 파일의 `Change Log`에 기록한다. -- requirement 변경 시 영향 받는 batch/release와 재검증 필요 여부를 `Change Log`에 남긴다. -- `Recent Change Log Summary`는 최신 5건만 유지한다. -- status 전환은 `Status Transition Evidence` 기준을 따른다. - -## Next Requirement ID - -- Current: `REQ-001` -- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - -## Requirement Register - -| ID | Title | Type | Status | Priority | Owner | Link | -| --- | --- | --- | --- | --- | --- | --- | - -## Recent Change Log Summary - -- 없음 \ No newline at end of file diff --git a/.stage-pilot/templates/srs/req-template.md b/.stage-pilot/templates/srs/req-template.md deleted file mode 100644 index 2fd39c7..0000000 --- a/.stage-pilot/templates/srs/req-template.md +++ /dev/null @@ -1,67 +0,0 @@ -# req-XXX: <요구사항 제목> - -- Status: Proposed | Approved | Implemented | Deprecated -- Type: Configuration | Data | Deployment | Documentation | Exception | Installation | Integration | Interface | Migration | Non-Functional | Testing -- Priority: High | Medium | Low -- Owner: <담당자 또는 팀> - -## Intent -- <이 요구사항이 필요한 이유와 달성하려는 목표를 1~3문장으로 작성> - -## Requirement -- <시스템이 반드시 수행해야 하는 동작을 단정형 문장으로 작성> - -## Acceptance Criteria -1. Given <선행 조건>, When <행동>, Then <기대 결과> -2. Given <선행 조건>, When <행동>, Then <기대 결과> - -## Impacted Area -- Docs: <영향 받는 문서 경로> -- Modules: <영향 받는 모듈/컴포넌트> -- Tests: <추가/수정해야 하는 테스트> - -## Notes -- <가정, 제약, 미해결 이슈> - -## Change Log - -### CHG-YYYYMMDD-01 -- Date: YYYY-MM-DD -- Author: <작성자> - -#### Change Summary -- <무엇이 변경되었는지> - -#### Intent -- <왜 변경했는지> - -#### Acceptance Criteria Delta -- Added: - - <추가된 기준> -- Updated: - - <변경된 기준> -- Removed: - - <삭제된 기준> - -#### Impacted Area Delta -- Docs: - - <문서 영향> -- Modules: - - <코드 영향> -- Tests: - - <테스트 영향> - -#### Delivery Trace Delta -- Batches: - - <영향 받는 batch 또는 none> -- Releases: - - <영향 받는 release 또는 none> - -#### Revalidation Impact -- Reverification Needed: yes | no -- Existing Implementation Invalidated: yes | no -- Status Recommendation: keep-current | revert-to-approved | revert-to-proposed -- Follow-up Action: <필요한 재검증, 재배치, 후속 작업> - -#### Validation Plan -- <검증 방법과 완료 조건> \ No newline at end of file diff --git a/.stage-pilot/tools/bootstrap.sh b/.stage-pilot/tools/bootstrap.sh deleted file mode 100644 index c90b3e8..0000000 --- a/.stage-pilot/tools/bootstrap.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -log() { - printf '[bootstrap] %s\n' "$*" -} - -fail() { - printf '[bootstrap] ERROR: %s\n' "$*" >&2 - exit 1 -} - -current_os() { - uname -s -} - -HOST_ROOT="${1:-$(pwd)}" - -ensure_host_structure() { - # active SDLC 문서 루트만 생성한다. - # .github/.claude 하위 파일은 install.sh가 source/에서 복사하며 필요한 디렉터리를 함께 만든다. - local dirs=( - "docs/discovery" - "docs/srs" - "docs/batches" - "docs/releases" - ) - - for dir in "${dirs[@]}"; do - mkdir -p "${HOST_ROOT}/${dir}" - done -} - -report_github_structure() { - if [ ! -d "${HOST_ROOT}/.github" ]; then - return 0 - fi - log "Host .github structure:" - if command -v find >/dev/null 2>&1; then - find "${HOST_ROOT}/.github" -maxdepth 2 -mindepth 1 | sed "s#${HOST_ROOT}/##" | sort - else - log "find command not available; skipping structure scan." - fi -} - -main() { - os_name="$(current_os)" - [ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" - - log "Host root: ${HOST_ROOT}" - ensure_host_structure - - # Intentionally do not install dependencies in bootstrap. - # This script only prepares the host directory layout. - log "${os_name} detected. Skipping Python/Node installation by design." - - report_github_structure - - if command -v python3 >/dev/null 2>&1; then - log "python3: $(python3 --version 2>&1)" - else - log "python3: not found" - fi - - if command -v node >/dev/null 2>&1; then - log "node: $(node --version 2>&1)" - else - log "node: not found" - fi - - log "Bootstrap completed successfully." -} - -main "$@" diff --git a/.stage-pilot/tools/install.sh b/.stage-pilot/tools/install.sh deleted file mode 100644 index f480c80..0000000 --- a/.stage-pilot/tools/install.sh +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: install.sh [--dry-run] [HOST_ROOT] [POLICY] - -source/ 아래의 모든 파일을 호스트 저장소 루트로 복사한다. - source/.github/... -> HOST_ROOT/.github/... - source/.claude/... -> HOST_ROOT/.claude/... - -단, source/instruction.md는 도구 중립 단일 원본으로 다음 두 복사본으로 fan-out 한다. - -> HOST_ROOT/.github/copilot-instructions.md (Copilot용, verbatim 복사) - -> HOST_ROOT/CLAUDE.md 의 StagePilot 마커 블록 (Claude용) -(instruction.md 자체는 루트로 그대로 복사하지 않는다.) - -또한 .stage-pilot/skills를 .github/skills로 동기화한다. - .stage-pilot/skills/... -> HOST_ROOT/.github/skills/... - -Options: - --dry-run 변경 없이 복사/생성 계획만 출력한다. - -h, --help 도움말을 출력한다. - -Arguments: - HOST_ROOT 대상 호스트 저장소 루트 (default: 현재 디렉터리) - POLICY 기존 파일 충돌 정책: replace|preserve|fail - (default: STAGEPILOT_CONFLICT_POLICY 또는 preserve) -EOF -} - -log() { - printf '[install] %s\n' "$*" -} - -fail() { - printf '[install] ERROR: %s\n' "$*" >&2 - exit 1 -} - -# pwd -P로 물리 경로를 잡아 심링크(.stage-pilot/* 등)를 실제 위치로 해석한다. -# 그렇지 않으면 SOURCE_DIR가 심링크가 되어 `find`가 내려가지 못한다. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -PACKAGE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" -SOURCE_DIR="${PACKAGE_ROOT}/source" -SKILLS_SOURCE_DIR="${PACKAGE_ROOT}/skills" - -DRY_RUN=0 -ARGS=() -while [ "$#" -gt 0 ]; do - case "$1" in - --dry-run) - DRY_RUN=1 - ;; - -h|--help) - usage - exit 0 - ;; - --*) - fail "Unknown option: $1" - ;; - *) - ARGS+=("$1") - ;; - esac - shift -done - -HOST_ROOT="${ARGS[0]:-$(pwd)}" -POLICY="${ARGS[1]:-${STAGEPILOT_CONFLICT_POLICY:-preserve}}" -TARGET_CLAUDE="${HOST_ROOT}/CLAUDE.md" -INSTRUCTION_SOURCE="${SOURCE_DIR}/instruction.md" -STAGEPILOT_BEGIN="" -STAGEPILOT_END="" - -case "${POLICY}" in - replace|preserve|fail) - ;; - *) - fail "Invalid conflict policy '${POLICY}'. Allowed: replace|preserve|fail" - ;; -esac - -[ -d "${SOURCE_DIR}" ] || fail "Source directory not found: ${SOURCE_DIR}" -[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" - -copied=0 -skipped=0 -conflicted=0 -failed=0 - -# source/ 파일 하나를 HOST_ROOT/로 정책에 맞춰 복사한다. -copy_one() { - local src_file="$1" - local rel_path="$2" - local dst_file="${HOST_ROOT}/${rel_path}" - local dst_dir - dst_dir="$(dirname "${dst_file}")" - - if [ ! -e "${dst_file}" ]; then - if [ "${DRY_RUN}" -eq 1 ]; then - log "[dry-run] copy: ${rel_path}" - else - mkdir -p "${dst_dir}" - cp -a "${src_file}" "${dst_file}" - fi - copied=$((copied + 1)) - return - fi - - if cmp -s "${src_file}" "${dst_file}"; then - skipped=$((skipped + 1)) - return - fi - - conflicted=$((conflicted + 1)) - case "${POLICY}" in - replace) - if [ "${DRY_RUN}" -eq 1 ]; then - log "[dry-run] replace: ${rel_path}" - else - mkdir -p "${dst_dir}" - cp -a "${src_file}" "${dst_file}" - fi - copied=$((copied + 1)) - ;; - preserve) - log "preserve existing: ${rel_path}" - skipped=$((skipped + 1)) - ;; - fail) - printf '[install] CONFLICT: %s\n' "${rel_path}" >&2 - failed=$((failed + 1)) - ;; - esac -} - -# 1) source/ 전체를 호스트 루트로 복사 (.github, .claude 등 구조 보존) -# 단, instruction.md는 단일 원본이므로 루트로 직접 복사하지 않고 아래에서 fan-out 한다. -while IFS= read -r -d '' src_file; do - rel_path="${src_file#${SOURCE_DIR}/}" - if [ "${rel_path}" = "instruction.md" ]; then - continue - fi - copy_one "${src_file}" "${rel_path}" -done < <(find "${SOURCE_DIR}" -type f -print0 | sort -z) - -# 1-b) instruction.md -> .github/copilot-instructions.md (Copilot 매직 파일명)로 fan-out -if [ -f "${INSTRUCTION_SOURCE}" ]; then - copy_one "${INSTRUCTION_SOURCE}" ".github/copilot-instructions.md" -fi - -# 1-c) .stage-pilot/skills -> .github/skills 동기화 (심볼릭 링크 미사용) -if [ -d "${SKILLS_SOURCE_DIR}" ]; then - while IFS= read -r -d '' src_file; do - rel_path="${src_file#${SKILLS_SOURCE_DIR}/}" - copy_one "${src_file}" ".github/skills/${rel_path}" - done < <(find "${SKILLS_SOURCE_DIR}" -type f -print0 | sort -z) -else - log "skills source not found: ${SKILLS_SOURCE_DIR}" -fi - -# 2) CLAUDE.md: instruction.md를 단일 원본으로 StagePilot 블록 생성/갱신. -# 호스트가 직접 추가한 CLAUDE.md 내용은 마커 밖에 그대로 보존한다. -generate_claude_md() { - [ -f "${INSTRUCTION_SOURCE}" ] || { log "instruction.md 없음; CLAUDE.md 생략"; return 0; } - - local payload output - payload="$(mktemp)" - output="$(mktemp)" - - # 원본에 이미 마커가 있으면 그 블록만, 없으면 파일 전체를 payload로 사용한다. - if grep -Fq "${STAGEPILOT_BEGIN}" "${INSTRUCTION_SOURCE}" && grep -Fq "${STAGEPILOT_END}" "${INSTRUCTION_SOURCE}"; then - awk -v b="${STAGEPILOT_BEGIN}" -v e="${STAGEPILOT_END}" ' - $0 == b { f=1; next } - $0 == e { f=0; exit } - f { print } - ' "${INSTRUCTION_SOURCE}" > "${payload}" - else - cat "${INSTRUCTION_SOURCE}" > "${payload}" - fi - - if [ ! -e "${TARGET_CLAUDE}" ]; then - { - printf '%s\n' "${STAGEPILOT_BEGIN}" - cat "${payload}" - printf '%s\n' "${STAGEPILOT_END}" - } > "${output}" - elif grep -Fq "${STAGEPILOT_BEGIN}" "${TARGET_CLAUDE}" && grep -Fq "${STAGEPILOT_END}" "${TARGET_CLAUDE}"; then - awk -v b="${STAGEPILOT_BEGIN}" -v e="${STAGEPILOT_END}" -v p="${payload}" ' - $0 == b { print; while ((getline line < p) > 0) print line; f=1; next } - $0 == e { f=0; print; next } - !f { print } - ' "${TARGET_CLAUDE}" > "${output}" - else - cat "${TARGET_CLAUDE}" > "${output}" - { - printf '\n## StagePilot\n' - printf '%s\n' "${STAGEPILOT_BEGIN}" - cat "${payload}" - printf '%s\n' "${STAGEPILOT_END}" - } >> "${output}" - fi - - if [ "${DRY_RUN}" -eq 1 ]; then - if command -v diff >/dev/null 2>&1 && [ -e "${TARGET_CLAUDE}" ]; then - log "[dry-run] CLAUDE.md diff:" - diff -u "${TARGET_CLAUDE}" "${output}" || true - else - log "[dry-run] create/update: CLAUDE.md" - fi - else - cp -a "${output}" "${TARGET_CLAUDE}" - log "CLAUDE.md StagePilot 블록 갱신" - fi - - rm -f "${payload}" "${output}" -} - -generate_claude_md - -log "host_root=${HOST_ROOT}" -log "source=${SOURCE_DIR}" -log "policy=${POLICY}" -log "copied=${copied} skipped=${skipped} conflicted=${conflicted}" - -if [ "${failed}" -ne 0 ]; then - if [ "${DRY_RUN}" -eq 1 ]; then - fail "[dry-run] policy=fail에서 충돌이 발생합니다." - fi - fail "policy=fail 충돌로 설치를 중단합니다." -fi - -if [ "${DRY_RUN}" -eq 1 ]; then - log "[dry-run] 디렉터리 골격 생성 단계 생략. 변경된 파일 없음." - exit 0 -fi - -# 3) 디렉터리 골격 생성 (docs/discovery|srs|batches|releases 등) -BOOTSTRAP_SCRIPT="${PACKAGE_ROOT}/tools/bootstrap.sh" -[ -f "${BOOTSTRAP_SCRIPT}" ] || fail "Bootstrap script not found: ${BOOTSTRAP_SCRIPT}" - -/bin/bash "${BOOTSTRAP_SCRIPT}" "${HOST_ROOT}" -log "Install completed successfully." diff --git a/.stage-pilot/tools/stagepilot-bootstrap-seed.py b/.stage-pilot/tools/stagepilot-bootstrap-seed.py deleted file mode 100644 index 10bc293..0000000 --- a/.stage-pilot/tools/stagepilot-bootstrap-seed.py +++ /dev/null @@ -1,913 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import argparse -from dataclasses import dataclass -from datetime import datetime, timedelta, timezone -from pathlib import Path -import re -import sys - -try: - from zoneinfo import ZoneInfo -except ImportError: # pragma: no cover - ZoneInfo = None - - -RUNTIME_CHOICES = ( - "cli", - "api-service", - "web-app", - "worker", - "library", - "mixed", - "other", -) - -INTERFACE_TYPE_CHOICES = ( - "http-api", - "cli", - "event", - "file", - "internal-service", - "other", -) - -UPPERCASE_TOKENS = { - "api": "API", - "http": "HTTP", - "cli": "CLI", - "id": "ID", - "io": "I/O", - "json": "JSON", - "sql": "SQL", - "ui": "UI", - "ux": "UX", - "url": "URL", -} - -STORAGE_BACKEND_HINTS = ( - (("postgresql", "postgres", "psql"), "PostgreSQL"), - (("mysql", "mariadb"), "MySQL"), - (("sqlite",), "SQLite"), - (("mongodb", "mongo"), "MongoDB"), - (("dynamodb",), "DynamoDB"), - (("redis",), "Redis"), - (("s3", "blob storage", "object storage"), "Object Storage"), -) - - -@dataclass(frozen=True) -class Entrypoint: - name: str - purpose: str - - -@dataclass(frozen=True) -class TopLevelArea: - path: str - responsibility: str - - -@dataclass(frozen=True) -class InterfaceContractHint: - name: str - interface_type: str - actors: str - purpose: str - - -@dataclass(frozen=True) -class EntityHint: - name: str - purpose: str - - -@dataclass(frozen=True) -class CompatibilityRuleHint: - interface_name: str - rule: str - - -@dataclass(frozen=True) -class InterfaceDetailHint: - interface_name: str - value: str - - -@dataclass(frozen=True) -class SeedAnswers: - repository_name: str - display_name: str - summary: str - primary_domain: str - tech_stack: list[str] - primary_runtime: str - primary_entrypoints: list[Entrypoint] - planned_top_level_areas: list[TopLevelArea] - interface_contracts: list[InterfaceContractHint] - interface_inputs: list[InterfaceDetailHint] - interface_outputs: list[InterfaceDetailHint] - interface_errors: list[InterfaceDetailHint] - core_entities: list[EntityHint] - persistence_backend: str - compatibility_rules: list[CompatibilityRuleHint] - known_unknowns: list[str] - capture_mode: str - - -def fail(message: str) -> int: - print(f"[bootstrap-seed] ERROR: {message}", file=sys.stderr) - return 1 - - -def log(message: str) -> None: - print(f"[bootstrap-seed] {message}") - - -def default_display_name(repository_name: str) -> str: - words = re.sub(r"[-_]+", " ", repository_name.strip()).split() - if not words: - return "Project" - return " ".join(word.capitalize() for word in words) - - -def kst_timestamp() -> str: - if ZoneInfo is not None: - current = datetime.now(ZoneInfo("Asia/Seoul")) - else: # pragma: no cover - current = datetime.now(timezone(timedelta(hours=9))) - return current.strftime("%Y-%m-%d %H:%M") - - -def yaml_scalar(value: str) -> str: - escaped = value.replace("'", "''") - return f"'{escaped}'" - - -def parse_csv_list(raw_value: str) -> list[str]: - return [item.strip() for item in raw_value.split(",") if item.strip()] - - -def parse_pair(raw_value: str, label: str) -> tuple[str, str]: - if "::" not in raw_value: - raise ValueError(f"{label} entries must use 'name :: purpose' format: {raw_value}") - - left, right = raw_value.split("::", 1) - left = left.strip() - right = right.strip() - if not left or not right: - raise ValueError(f"{label} entries must include values on both sides of '::': {raw_value}") - return left, right - - -def parse_parts(raw_value: str, label: str, expected_parts: int) -> tuple[str, ...]: - parts = tuple(part.strip() for part in raw_value.split("::")) - if len(parts) != expected_parts or any(not part for part in parts): - raise ValueError( - f"{label} entries must use exactly {expected_parts} parts separated by '::': {raw_value}" - ) - return parts - - -def prompt_value(question: str, *, default: str | None = None, choices: tuple[str, ...] | None = None) -> str: - while True: - choice_hint = f" ({', '.join(choices)})" if choices else "" - default_hint = f" [{default}]" if default else "" - raw_value = input(f"{question}{choice_hint}{default_hint}: ").strip() - - if not raw_value and default is not None: - return default - if not raw_value: - print("Value is required.") - continue - if choices and raw_value not in choices: - print(f"Choose one of: {', '.join(choices)}") - continue - return raw_value - - -def prompt_lines(question: str, *, example: str, required: bool = False, max_items: int | None = None) -> list[str]: - print(question) - print(f"Format: {example}") - print("Press Enter on an empty line to finish.") - - values: list[str] = [] - while True: - raw_value = input("> ").strip() - if not raw_value: - if required and not values: - print("At least one line is required.") - continue - break - - values.append(raw_value) - if max_items is not None and len(values) >= max_items: - break - - return values - - -def title_case_phrase(text: str) -> str: - tokens = [token for token in re.split(r"[\s/_-]+", text.strip()) if token] - formatted: list[str] = [] - for token in tokens: - lowered = token.lower() - if lowered in UPPERCASE_TOKENS: - formatted.append(UPPERCASE_TOKENS[lowered]) - else: - formatted.append(token.capitalize()) - return " ".join(formatted) - - -def compact_label(text: str, *, max_words: int = 5) -> str: - cleaned = re.sub(r"[^A-Za-z0-9\s/_-]+", " ", text).strip() - label = title_case_phrase(cleaned) - words = label.split() - if len(words) > max_words: - return " ".join(words[:max_words]) - return label - - -def infer_interface_type(primary_runtime: str, entrypoint_name: str, index: int) -> str: - lowered = entrypoint_name.lower() - if primary_runtime in {"api-service", "web-app"} and index == 0: - return "http-api" - if primary_runtime == "library": - return "internal-service" - if primary_runtime in {"cli", "worker"}: - return "cli" - if any(token in lowered for token in ("uvicorn", "gunicorn", ":app")): - return "http-api" - if lowered.startswith(("python ", "python3 ", "node ")) or " -m " in lowered: - return "cli" - return "other" - - -def default_interface_actors(interface_type: str) -> str: - actor_map = { - "http-api": "clients and downstream services", - "cli": "operators and automation", - "event": "publishers and subscribers", - "file": "file producers and consumers", - "internal-service": "internal services", - "other": "project operators and integrators", - } - return actor_map[interface_type] - - -def default_interface_stability(interface_type: str, capture_mode: str) -> str: - if interface_type == "cli": - return "internal" - if capture_mode == "declared": - return "experimental" - return "internal" - - -def derive_interface_name(entrypoint: Entrypoint, interface_type: str, index: int) -> str: - purpose_label = compact_label(entrypoint.purpose) - if interface_type == "http-api": - return "Primary HTTP API" if index == 0 else f"{purpose_label} API" - if interface_type == "cli": - return f"{purpose_label} Command" - if interface_type == "event": - return f"{purpose_label} Event" - if interface_type == "file": - return f"{purpose_label} File Contract" - if interface_type == "internal-service": - return f"{purpose_label} Service" - return f"{purpose_label} Interface" - - -def build_interface_hints(answers: SeedAnswers) -> list[InterfaceContractHint]: - hints = list(answers.interface_contracts[:2]) - for index, entrypoint in enumerate(answers.primary_entrypoints): - if len(hints) >= 2: - break - interface_type = infer_interface_type(answers.primary_runtime, entrypoint.name, index) - hints.append( - InterfaceContractHint( - name=derive_interface_name(entrypoint, interface_type, index), - interface_type=interface_type, - actors=default_interface_actors(interface_type), - purpose=entrypoint.purpose, - ) - ) - - while len(hints) < 2: - index = len(hints) - fallback_type = "http-api" if index == 0 and answers.primary_runtime in {"api-service", "web-app"} else "internal-service" - hints.append( - InterfaceContractHint( - name="Primary HTTP API" if fallback_type == "http-api" else "Internal Service Interface", - interface_type=fallback_type, - actors=default_interface_actors(fallback_type), - purpose=f"support the primary {answers.primary_domain} workflow", - ) - ) - return hints[:2] - - -def default_entity_name(primary_domain: str, index: int) -> str: - domain_label = compact_label(primary_domain, max_words=4) or "Domain" - if index == 0: - return f"{domain_label} Record" - return f"{domain_label} Snapshot" - - -def default_entity_purpose(primary_domain: str, index: int) -> str: - if index == 0: - return f"represent a core business record in the {primary_domain} domain" - return f"represent a derived or supporting artifact in the {primary_domain} domain" - - -def build_entity_hints(answers: SeedAnswers) -> list[EntityHint]: - hints = list(answers.core_entities[:2]) - while len(hints) < 2: - index = len(hints) - hints.append( - EntityHint( - name=default_entity_name(answers.primary_domain, index), - purpose=default_entity_purpose(answers.primary_domain, index), - ) - ) - return hints[:2] - - -def detect_storage_backend(tech_stack: list[str]) -> str: - lowered_stack = [item.lower() for item in tech_stack] - for keywords, label in STORAGE_BACKEND_HINTS: - if any(keyword in item for item in lowered_stack for keyword in keywords): - return label - return "application data store" - - -def normalize_storage_backend(raw_value: str) -> str: - lowered_value = raw_value.strip().lower() - if not lowered_value: - return "application data store" - for keywords, label in STORAGE_BACKEND_HINTS: - if any(keyword in lowered_value for keyword in keywords): - return label - return raw_value.strip() - - -def derive_interface_inputs(contract: InterfaceContractHint) -> list[str]: - if contract.interface_type == "http-api": - return [f"service request handled by {contract.name} :: {contract.purpose}"] - if contract.interface_type == "cli": - return [f"command invocation :: {contract.purpose}"] - if contract.interface_type == "event": - return [f"event publication or subscription :: {contract.purpose}"] - if contract.interface_type == "file": - return [f"file input or output trigger :: {contract.purpose}"] - return [f"interface invocation :: {contract.purpose}"] - - -def derive_interface_outputs(contract: InterfaceContractHint) -> list[str]: - if contract.interface_type == "http-api": - return [f"structured service response :: result of {contract.purpose}"] - if contract.interface_type == "cli": - return [f"console output or generated artifact :: result of {contract.purpose}"] - if contract.interface_type == "event": - return [f"published event payload :: result of {contract.purpose}"] - if contract.interface_type == "file": - return [f"generated or updated file artifact :: result of {contract.purpose}"] - return [f"observable interface result :: outcome of {contract.purpose}"] - - -def derive_interface_errors(contract: InterfaceContractHint) -> list[str]: - if contract.interface_type == "http-api": - return ["4xx/5xx response :: the request could not be completed successfully"] - if contract.interface_type == "cli": - return ["non-zero exit code :: the command failed for operators or automation"] - return ["reported interface failure :: the contract could not be completed successfully"] - - -def derive_interface_compatibility(contract: InterfaceContractHint) -> list[str]: - if contract.interface_type == "cli": - return ["arguments and exit-code semantics should remain stable for automation callers"] - return ["payload shape and externally visible behavior should remain backward compatible for stable consumers"] - - -def resolve_interface_compatibility_rules( - answers: SeedAnswers, - contract: InterfaceContractHint, -) -> list[str]: - shared_rules = [ - rule.rule for rule in answers.compatibility_rules if rule.interface_name.strip() == "*" - ] - named_rules = [ - rule.rule - for rule in answers.compatibility_rules - if rule.interface_name.strip().casefold() == contract.name.casefold() - ] - if shared_rules or named_rules: - return shared_rules + named_rules - return derive_interface_compatibility(contract) - - -def resolve_interface_detail_hints( - detail_hints: list[InterfaceDetailHint], - contract: InterfaceContractHint, -) -> list[str]: - return [ - hint.value - for hint in detail_hints - if hint.interface_name.strip().casefold() == contract.name.casefold() - ] - - -def derive_interface_shared_constraints(answers: SeedAnswers, contracts: list[InterfaceContractHint]) -> list[str]: - constraints: list[str] = [] - if any(contract.interface_type == "http-api" for contract in contracts): - constraints.append("request validation should happen before persistence or response rendering") - if any(contract.interface_type == "cli" for contract in contracts): - constraints.append("automation-facing commands should communicate failure through stable exit semantics") - if not constraints: - constraints.append("externally visible interfaces should document compatibility expectations before release") - return constraints[:2] - - -def derive_interface_current_gaps(answers: SeedAnswers, contracts: list[InterfaceContractHint]) -> list[str]: - if answers.known_unknowns: - return answers.known_unknowns[:2] - gaps: list[str] = [] - if any(contract.interface_type == "http-api" for contract in contracts): - gaps.append("authentication and API versioning strategy are not defined yet") - if any(contract.interface_type == "cli" for contract in contracts): - gaps.append("automation caller expectations are not fully documented yet") - return gaps[:2] or ["consumer-specific compatibility expectations are not defined yet"] - - -def derive_entity_source(storage_backend: str, index: int) -> str: - if storage_backend == "PostgreSQL": - return "PostgreSQL primary tables" if index == 0 else "PostgreSQL derived tables" - if storage_backend == "MySQL": - return "MySQL primary tables" if index == 0 else "MySQL derived tables" - if storage_backend == "SQLite": - return "SQLite primary tables" if index == 0 else "SQLite derived tables" - if storage_backend == "MongoDB": - return "MongoDB primary collections" if index == 0 else "MongoDB derived collections" - if storage_backend == "Object Storage": - return "object storage manifests" if index == 0 else "object storage derived artifacts" - return "primary application records" if index == 0 else "derived application records" - - -def derive_entity_lifecycle(index: int) -> str: - if index == 0: - return "created -> updated -> archived" - return "derived -> published -> superseded" - - -def derive_entity_fields(index: int, entity: EntityHint, source_entity: EntityHint | None = None) -> list[str]: - if index == 0: - return [ - "id :: string or uuid :: stable identifier for the entity", - "status :: enum :: current lifecycle state for the entity", - "created_at :: timestamp :: when the entity was first recorded", - ] - source_name = source_entity.name if source_entity is not None else "the primary entity" - return [ - "id :: string or uuid :: stable identifier for the derived entity", - f"source_id :: string or uuid :: reference to {source_name}", - "updated_at :: timestamp :: when the derived state was last recomputed or refreshed", - ] - - -def derive_entity_relationships(index: int, entity: EntityHint, other_entity: EntityHint) -> list[str]: - if index == 0: - return [f"{other_entity.name} :: derived from or associated with {entity.name}"] - return [f"{other_entity.name} :: source record or upstream entity for {entity.name}"] - - -def derive_entity_state_rules(index: int, entity: EntityHint, other_entity: EntityHint) -> list[str]: - if index == 0: - return [ - "only one current active record should be treated as authoritative per business key", - "archived records should remain available for audit or replay needs when retained", - ] - return [ - f"{entity.name} should not exist without a corresponding {other_entity.name}", - f"{entity.name} should be refreshed whenever the authoritative {other_entity.name} changes", - ] - - -def derive_consistency_rules(entities: list[EntityHint]) -> list[str]: - return [ - "stable identifiers must remain consistent across related entities", - f"derived records must not outlive their source relationship to {entities[0].name}", - ] - - -def derive_persistence_notes(storage_backend: str) -> list[str]: - return [ - f"{storage_backend} is expected to store the current cross-cutting records and derived artifacts", - "migration, indexing, and retention strategy should be updated when schema assumptions change", - ] - - -def derive_model_current_gaps(answers: SeedAnswers) -> list[str]: - if answers.known_unknowns: - return answers.known_unknowns[:2] - return [ - "exact schema and indexing strategy are not defined yet", - "retention and archival policy are not defined yet", - ] - - -def collect_required_args(args: argparse.Namespace) -> tuple[str, str, list[str], str, list[str]]: - missing: list[str] = [] - if not args.project_summary: - missing.append("--project-summary") - if not args.primary_domain: - missing.append("--primary-domain") - if not args.tech_stack: - missing.append("--tech-stack") - if not args.primary_runtime: - missing.append("--primary-runtime") - if not args.primary_entrypoint: - missing.append("--primary-entrypoint") - if missing: - raise ValueError("Missing required arguments for --non-interactive: " + ", ".join(missing)) - - tech_stack = parse_csv_list(args.tech_stack) - if not tech_stack: - raise ValueError("--tech-stack must include at least one comma-separated item") - - return ( - args.project_summary, - args.primary_domain, - tech_stack, - args.primary_runtime, - args.primary_entrypoint, - ) - - -def collect_answers(args: argparse.Namespace, host_root: Path) -> SeedAnswers: - repository_name = args.repository_name or host_root.resolve().name or "project" - display_name = args.display_name or default_display_name(repository_name) - - if args.non_interactive: - summary, primary_domain, tech_stack, primary_runtime, primary_entrypoints = collect_required_args(args) - top_level_area_values = args.top_level_area - interface_contract_values = args.interface_contract - interface_input_values = args.interface_input - interface_output_values = args.interface_output - interface_error_values = args.interface_error - core_entity_values = args.core_entity - persistence_backend = normalize_storage_backend(args.persistence_backend) if args.persistence_backend else detect_storage_backend(tech_stack) - compatibility_rule_values = args.compatibility_rule - known_unknowns = args.known_unknown - else: - print("[bootstrap-seed] Answer the minimum bootstrap questions for baseline.yaml.") - print(f"[bootstrap-seed] Host root: {host_root}") - display_name = prompt_value("Display name", default=display_name) - summary = args.project_summary or prompt_value("project-summary | 이 프로젝트를 한 문장으로 설명하면 무엇인가?") - primary_domain = args.primary_domain or prompt_value("primary-domain | 이 프로젝트의 주요 도메인은 무엇인가?") - tech_stack = parse_csv_list(args.tech_stack) if args.tech_stack else parse_csv_list( - prompt_value("tech-stack | 계획 중인 주 언어, 프레임워크, 핵심 인프라는 무엇인가? (comma-separated)") - ) - primary_runtime = args.primary_runtime or prompt_value( - "primary-runtime | 계획 중인 주 실행 형태는 무엇인가?", - choices=RUNTIME_CHOICES, - ) - primary_entrypoints = args.primary_entrypoint or prompt_lines( - "primary-entrypoints | 대표 진입점 1~3개를 적어 달라.", - example="name :: purpose", - required=True, - max_items=3, - ) - top_level_area_values = args.top_level_area or prompt_lines( - "planned-top-level-areas | 이미 알고 있는 top-level 경로가 있으면 적어 달라.", - example="path :: responsibility", - ) - interface_contract_values = args.interface_contract or prompt_lines( - "interface-contracts | 대표 인터페이스 1~2개가 있으면 적어 달라.", - example="name :: type :: actors :: purpose", - max_items=2, - ) - interface_input_values = args.interface_input or prompt_lines( - "interface-inputs | 각 인터페이스의 대표 input을 적어 달라.", - example="interface-name :: input contract or invocation note", - ) - interface_output_values = args.interface_output or prompt_lines( - "interface-outputs | 각 인터페이스의 대표 output을 적어 달라.", - example="interface-name :: output contract or artifact", - ) - interface_error_values = args.interface_error or prompt_lines( - "interface-errors | 각 인터페이스의 대표 error를 적어 달라.", - example="interface-name :: error code or failure behavior", - ) - core_entity_values = args.core_entity or prompt_lines( - "core-entities | 핵심 엔티티 1~2개가 있으면 적어 달라.", - example="name :: purpose", - max_items=2, - ) - persistence_backend = normalize_storage_backend(args.persistence_backend) if args.persistence_backend else prompt_value( - "persistence-backend | 주 persistence backend가 있으면 적어 달라.", - default=detect_storage_backend(tech_stack), - ) - compatibility_rule_values = args.compatibility_rule or prompt_lines( - "compatibility-rules | 인터페이스 호환성 규칙이 있으면 적어 달라.", - example="interface-name :: rule 또는 * :: rule", - ) - known_unknowns = args.known_unknown or prompt_lines( - "known-unknowns | 아직 미정인 핵심 항목이 있으면 적어 달라.", - example="one unresolved item per line", - ) - - if primary_runtime not in RUNTIME_CHOICES: - raise ValueError(f"primary runtime must be one of: {', '.join(RUNTIME_CHOICES)}") - if not tech_stack: - raise ValueError("tech-stack must include at least one item") - if not primary_entrypoints: - raise ValueError("at least one primary-entrypoint is required") - if len(primary_entrypoints) > 3: - raise ValueError("primary-entrypoint supports at most 3 values") - if len(interface_contract_values) > 2: - raise ValueError("interface-contract supports at most 2 values") - if len(core_entity_values) > 2: - raise ValueError("core-entity supports at most 2 values") - - entrypoints = [Entrypoint(*parse_pair(value, "primary-entrypoint")) for value in primary_entrypoints] - top_level_areas = [TopLevelArea(*parse_pair(value, "top-level-area")) for value in top_level_area_values] - interface_contracts = [ - InterfaceContractHint(*parse_parts(value, "interface-contract", 4)) for value in interface_contract_values - ] - for contract in interface_contracts: - if contract.interface_type not in INTERFACE_TYPE_CHOICES: - raise ValueError( - "interface-contract type must be one of: " + ", ".join(INTERFACE_TYPE_CHOICES) - ) - interface_inputs = [InterfaceDetailHint(*parse_pair(value, "interface-input")) for value in interface_input_values] - interface_outputs = [InterfaceDetailHint(*parse_pair(value, "interface-output")) for value in interface_output_values] - interface_errors = [InterfaceDetailHint(*parse_pair(value, "interface-error")) for value in interface_error_values] - core_entities = [EntityHint(*parse_parts(value, "core-entity", 2)) for value in core_entity_values] - compatibility_rules = [ - CompatibilityRuleHint(*parse_pair(value, "compatibility-rule")) for value in compatibility_rule_values - ] - - return SeedAnswers( - repository_name=repository_name, - display_name=display_name, - summary=summary, - primary_domain=primary_domain, - tech_stack=tech_stack, - primary_runtime=primary_runtime, - primary_entrypoints=entrypoints, - planned_top_level_areas=top_level_areas, - interface_contracts=interface_contracts, - interface_inputs=interface_inputs, - interface_outputs=interface_outputs, - interface_errors=interface_errors, - core_entities=core_entities, - persistence_backend=persistence_backend, - compatibility_rules=compatibility_rules, - known_unknowns=known_unknowns, - capture_mode=args.capture_mode or "declared", - ) - - -def render_seed(answers: SeedAnswers) -> str: - updated_from = { - "declared": "user-answer", - "observed": "repo-observation", - "mixed": "mixed", - }[answers.capture_mode] - interface_contracts = build_interface_hints(answers) - entities = build_entity_hints(answers) - storage_backend = answers.persistence_backend - interface_shared_constraints = derive_interface_shared_constraints(answers, interface_contracts) - interface_current_gaps = derive_interface_current_gaps(answers, interface_contracts) - consistency_rules = derive_consistency_rules(entities) - persistence_notes = derive_persistence_notes(storage_backend) - model_current_gaps = derive_model_current_gaps(answers) - - lines = [ - "schema_version: 1", - f"capture_mode: {answers.capture_mode}", - "", - "project:", - f" repository_name: {yaml_scalar(answers.repository_name)}", - f" display_name: {yaml_scalar(answers.display_name)}", - f" summary: {yaml_scalar(answers.summary)}", - f" primary_domain: {yaml_scalar(answers.primary_domain)}", - "", - "stack:", - " tech_stack:", - ] - - for item in answers.tech_stack: - lines.append(f" - {yaml_scalar(item)}") - - lines.extend( - [ - "", - "runtime:", - f" primary_runtime: {answers.primary_runtime}", - " primary_entrypoints:", - ] - ) - for entrypoint in answers.primary_entrypoints: - lines.append(f" - name: {yaml_scalar(entrypoint.name)}") - lines.append(f" purpose: {yaml_scalar(entrypoint.purpose)}") - - lines.extend(["", "interfaces:", " contracts:"]) - for contract in interface_contracts: - resolved_inputs = resolve_interface_detail_hints(answers.interface_inputs, contract) - resolved_outputs = resolve_interface_detail_hints(answers.interface_outputs, contract) - resolved_errors = resolve_interface_detail_hints(answers.interface_errors, contract) - lines.append(f" - name: {yaml_scalar(contract.name)}") - lines.append(f" type: {yaml_scalar(contract.interface_type)}") - lines.append(f" actors: {yaml_scalar(contract.actors)}") - lines.append(f" purpose: {yaml_scalar(contract.purpose)}") - lines.append( - f" stability: {yaml_scalar(default_interface_stability(contract.interface_type, answers.capture_mode))}" - ) - lines.append(" inputs:") - for item in resolved_inputs or derive_interface_inputs(contract): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" outputs:") - for item in resolved_outputs or derive_interface_outputs(contract): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" errors:") - for item in resolved_errors or derive_interface_errors(contract): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" compatibility_rules:") - for item in resolve_interface_compatibility_rules(answers, contract): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" shared_constraints:") - for item in interface_shared_constraints: - lines.append(f" - {yaml_scalar(item)}") - lines.append(" current_gaps:") - for item in interface_current_gaps: - lines.append(f" - {yaml_scalar(item)}") - - lines.extend(["", "structure:"]) - if answers.planned_top_level_areas: - lines.append(" planned_top_level_areas:") - for area in answers.planned_top_level_areas: - lines.append(f" - path: {yaml_scalar(area.path)}") - lines.append(f" responsibility: {yaml_scalar(area.responsibility)}") - else: - lines.append(" planned_top_level_areas: []") - - lines.extend(["", "data_model:", f" persistence_backend: {yaml_scalar(storage_backend)}", " entities:"]) - for index, entity in enumerate(entities): - other_entity = entities[1 - index] - source_entity = entities[0] if index == 1 else None - lines.append(f" - name: {yaml_scalar(entity.name)}") - lines.append(f" purpose: {yaml_scalar(entity.purpose)}") - lines.append(f" source_of_truth: {yaml_scalar(derive_entity_source(storage_backend, index))}") - lines.append(f" lifecycle: {yaml_scalar(derive_entity_lifecycle(index))}") - lines.append(" key_fields:") - for item in derive_entity_fields(index, entity, source_entity): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" relationships:") - for item in derive_entity_relationships(index, entity, other_entity): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" state_rules:") - for item in derive_entity_state_rules(index, entity, other_entity): - lines.append(f" - {yaml_scalar(item)}") - lines.append(" consistency_rules:") - for item in consistency_rules: - lines.append(f" - {yaml_scalar(item)}") - lines.append(" persistence_notes:") - for item in persistence_notes: - lines.append(f" - {yaml_scalar(item)}") - lines.append(" current_gaps:") - for item in model_current_gaps: - lines.append(f" - {yaml_scalar(item)}") - - lines.extend(["", "notes:"]) - if answers.known_unknowns: - lines.append(" known_unknowns:") - for item in answers.known_unknowns: - lines.append(f" - {yaml_scalar(item)}") - else: - lines.append(" known_unknowns: []") - - lines.extend( - [ - "", - "metadata:", - f" captured_at_kst: {yaml_scalar(kst_timestamp())}", - " source: stagepilot-bootstrap-seed.py", - f" updated_from: {updated_from}", - "", - ] - ) - return "\n".join(lines) - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - description="Create .stage-pilot/bootstrap/baseline.yaml from StagePilot bootstrap answers.", - ) - parser.add_argument("host_root", nargs="?", default=".", help="Host repository root. Defaults to the current directory.") - parser.add_argument("--output", help="Explicit output path. Defaults to HOST_ROOT/.stage-pilot/bootstrap/baseline.yaml.") - parser.add_argument("--force", action="store_true", help="Overwrite an existing seed file.") - parser.add_argument("--dry-run", action="store_true", help="Print the generated seed to stdout without writing a file.") - parser.add_argument("--non-interactive", action="store_true", help="Require explicit CLI answers instead of interactive prompts.") - parser.add_argument("--capture-mode", choices=("declared", "observed", "mixed"), help="Seed capture mode.") - parser.add_argument("--repository-name", help="Repository name stored in the seed. Defaults to the host root folder name.") - parser.add_argument("--display-name", help="Display name stored in the seed. Defaults to a title-cased repository name.") - parser.add_argument("--project-summary", help="One-sentence project summary.") - parser.add_argument("--primary-domain", help="Primary domain noun phrase.") - parser.add_argument("--tech-stack", help="Comma-separated technology list.") - parser.add_argument("--primary-runtime", choices=RUNTIME_CHOICES, help="Primary runtime type.") - parser.add_argument( - "--primary-entrypoint", - action="append", - default=[], - help="Primary entrypoint line in 'name :: purpose' format. Repeat up to 3 times.", - ) - parser.add_argument( - "--top-level-area", - action="append", - default=[], - help="Optional top-level area in 'path :: responsibility' format.", - ) - parser.add_argument( - "--interface-contract", - action="append", - default=[], - help="Optional interface contract in 'name :: type :: actors :: purpose' format. Repeat up to 2 times.", - ) - parser.add_argument( - "--interface-input", - action="append", - default=[], - help="Optional interface input in 'interface-name :: input contract or invocation note' format.", - ) - parser.add_argument( - "--interface-output", - action="append", - default=[], - help="Optional interface output in 'interface-name :: output contract or artifact' format.", - ) - parser.add_argument( - "--interface-error", - action="append", - default=[], - help="Optional interface error in 'interface-name :: error code or failure behavior' format.", - ) - parser.add_argument( - "--core-entity", - action="append", - default=[], - help="Optional core entity in 'name :: purpose' format. Repeat up to 2 times.", - ) - parser.add_argument("--persistence-backend", help="Optional primary persistence backend or storage system.") - parser.add_argument( - "--compatibility-rule", - action="append", - default=[], - help="Optional compatibility rule in 'interface-name :: rule' format. Use '* :: rule' for a shared rule.", - ) - parser.add_argument( - "--known-unknown", - action="append", - default=[], - help="Optional unresolved bootstrap note. Repeat for multiple items.", - ) - return parser - - -def main() -> int: - parser = build_parser() - args = parser.parse_args() - - host_root = Path(args.host_root).resolve() - if not host_root.exists() or not host_root.is_dir(): - return fail(f"host root not found: {host_root}") - - output_path = Path(args.output).resolve() if args.output else host_root / ".stage-pilot/bootstrap/baseline.yaml" - - try: - answers = collect_answers(args, host_root) - content = render_seed(answers) - except ValueError as error: - return fail(str(error)) - except EOFError: - return fail("input ended before the questionnaire was completed") - - if args.dry_run: - print(content, end="") - return 0 - - if output_path.exists() and not args.force: - return fail(f"seed file already exists: {output_path}. Use --force to overwrite.") - - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(content, encoding="utf-8") - - log(f"wrote {output_path}") - log("Next: run /bootstrap-baseline in Copilot Chat to render baseline docs from the seed.") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/.stage-pilot/tools/stagepilot-doctor.py b/.stage-pilot/tools/stagepilot-doctor.py deleted file mode 100644 index 4729d34..0000000 --- a/.stage-pilot/tools/stagepilot-doctor.py +++ /dev/null @@ -1,1271 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import argparse -from datetime import datetime -import re -import sys -from dataclasses import dataclass -from pathlib import Path -from typing import Iterable - - -PLACEHOLDER_PATTERN = re.compile(r"\{\{[^{}]+\}\}") -LINK_PATTERN = re.compile(r"\[[^\]]+\]\(([^)]+)\)") -REQ_ID_PATTERN = re.compile(r"req-(\d+)", re.IGNORECASE) -BATCH_ID_PATTERN = re.compile(r"bat-(\d+)", re.IGNORECASE) -DISCOVERY_ID_PATTERN = re.compile(r"dcy-(\d+)", re.IGNORECASE) -RELEASE_ID_PATTERN = re.compile(r"rel-(\d+)", re.IGNORECASE) -STATUS_PATTERN = re.compile(r"^-\s*(?:Status|상태):\s*(.+?)\s*$", re.MULTILINE) -BATCH_PROFILE_PATTERN = re.compile(r"^-\s*Profile:\s*(.+?)\s*$", re.MULTILINE) -RELEASE_PROFILE_PATTERN = re.compile(r"^-\s*Profile:\s*(.+?)\s*$", re.MULTILINE) -REQ_TYPE_LINE_PATTERN = re.compile(r"^-\s*Type:\s*(.+)$", re.MULTILINE) -INLINE_CODE_PATTERN = re.compile(r"`([^`]+)`") - -BATCH_PROFILES = {"standard", "batch-lite"} -RELEASE_PROFILES = {"docs-only", "tooling", "app-service"} - -FINALIZED_STATUSES = { - "approved", - "archived", - "completed", - "confirmed", - "deprecated", - "feedback-captured", - "implemented", - "in-delivery", - "release-candidate", - "released", -} - -ACTIVE_INDEX_FILES = [ - Path("docs/discovery/index.md"), - Path("docs/srs/index.md"), - Path("docs/batches/index.md"), - Path("docs/releases/index.md"), -] - -BASELINE_DOC_FILES = [ - Path("docs/project-structure.md"), - Path("docs/runtime-flows.md"), -] - -OPTIONAL_CROSS_CUTTING_DOC_FILES = [ - Path("docs/interface-contract.md"), - Path("docs/data-model.md"), -] - -BOOTSTRAP_REQUIRED_FILES = ACTIVE_INDEX_FILES + BASELINE_DOC_FILES - - -@dataclass(frozen=True) -class Finding: - level: str - code: str - path: Path - message: str - - -@dataclass(frozen=True) -class TraceabilityRow: - req_id: str - status: str - discovery_ids: tuple[str, ...] - batch_ids: tuple[str, ...] - release_ready_batch_ids: tuple[str, ...] - release_ids: tuple[str, ...] - flags: tuple[str, ...] - - -@dataclass(frozen=True) -class FeedbackHandoffRow: - release_id: str - status: str - discovery_inputs: tuple[str, ...] - req_inputs: tuple[str, ...] - change_req_inputs: tuple[str, ...] - flags: tuple[str, ...] - - -@dataclass(frozen=True) -class ChangeReqImpact: - change_id: str - status_recommendation: str - reverification_needed: str - implementation_invalidated: str - - -class Doctor: - def __init__(self, workspace_root: Path, package_root: Path, strict_missing_docs: bool, report_path: Path | None = None) -> None: - self.workspace_root = workspace_root - self.package_root = package_root - self.strict_missing_docs = strict_missing_docs - self.report_path = report_path - self.report_written = False - self.findings: list[Finding] = [] - self.traceability_rows: list[TraceabilityRow] = [] - self.feedback_handoff_rows: list[FeedbackHandoffRow] = [] - - def add(self, level: str, code: str, path: Path, message: str) -> None: - self.findings.append(Finding(level=level, code=code, path=path, message=message)) - - def run(self) -> int: - self.check_req_type_contract() - self.check_design_template_contract() - self.check_markdown_links(self.package_markdown_files(), relative_to=self.package_root) - - workspace_mode = self.check_active_workspace_roots() - if workspace_mode: - self.check_discovery_index() - self.check_srs_index() - self.check_batch_index() - self.check_release_index() - self.build_traceability_matrix() - self.build_feedback_handoff_summary() - self.check_placeholders(self.workspace_markdown_files()) - self.check_markdown_links(self.workspace_markdown_files(), relative_to=self.workspace_root) - - if self.report_path is not None: - self.write_report() - self.print_report() - return 1 if any(f.level == "ERROR" for f in self.findings) else 0 - - def package_markdown_files(self) -> list[Path]: - patterns = [ - Path("README.md"), - Path("source/instruction.md"), - Path("source/.github/instructions"), - Path("skills"), - Path("templates"), - ] - files: list[Path] = [] - for pattern in patterns: - full_path = self.package_root / pattern - if full_path.is_file(): - files.append(full_path) - continue - if full_path.is_dir(): - files.extend(sorted(full_path.rglob("*.md"))) - return files - - def workspace_markdown_files(self) -> list[Path]: - docs_root = self.workspace_root / "docs" - if not docs_root.exists(): - return [] - return sorted(docs_root.rglob("*.md")) - - def check_active_workspace_roots(self) -> bool: - existing = [path for path in ACTIVE_INDEX_FILES if (self.workspace_root / path).exists()] - missing_bootstrap_files = [path for path in BOOTSTRAP_REQUIRED_FILES if not (self.workspace_root / path).exists()] - if not existing: - level = "ERROR" if self.strict_missing_docs else "WARN" - self.add( - level, - "missing-active-docs", - self.workspace_root, - "No active SDLC docs were found under docs/. Workspace-specific checks were skipped.", - ) - self.add_bootstrap_required_hint(missing_bootstrap_files) - return False - - for path in ACTIVE_INDEX_FILES: - if not (self.workspace_root / path).exists(): - self.add( - "ERROR", - "missing-root-index", - self.workspace_root / path, - "Missing active index file.", - ) - - if missing_bootstrap_files: - self.add_bootstrap_required_hint(missing_bootstrap_files) - else: - self.check_optional_cross_cutting_docs() - return True - - def check_optional_cross_cutting_docs(self) -> None: - if self.is_package_repo_self_check(): - return - - for path in OPTIONAL_CROSS_CUTTING_DOC_FILES: - if (self.workspace_root / path).exists(): - continue - self.add( - "WARN", - "missing-cross-cutting-baseline-doc", - self.workspace_root / path, - "Missing optional cross-cutting baseline doc. Run /bootstrap-baseline to backfill it or add the document manually if the project needs this shared contract.", - ) - - def add_bootstrap_required_hint(self, missing_files: list[Path]) -> None: - if not missing_files or self.is_package_repo_self_check(): - return - - missing_list = ", ".join(str(path) for path in missing_files[:6]) - if len(missing_files) > 6: - missing_list += ", ..." - - self.add( - "INFO", - "bootstrap-required", - self.workspace_root, - "Fresh host repos should run /bootstrap-baseline in Copilot Chat before the first real Discovery. " - f"Missing bootstrap files: {missing_list}", - ) - - def is_package_repo_self_check(self) -> bool: - if self.workspace_root.resolve() != self.package_root.resolve(): - return False - - required_paths = [ - Path("tools/install.sh"), - Path("examples/p3-change-management"), - ] - return all((self.workspace_root / path).exists() for path in required_paths) - - def check_req_type_contract(self) -> None: - skill_path = self.package_root / "skills/draft-req/SKILL.md" - template_path = self.package_root / "templates/srs/req-template.md" - index_path = self.package_root / "templates/srs/index.md" - - if not skill_path.exists() or not template_path.exists() or not index_path.exists(): - self.add("ERROR", "missing-req-contract-files", self.package_root, "REQ type contract files are missing.") - return - - skill_types = self.parse_req_types_from_skill(skill_path) - template_types = self.parse_req_types_from_template(template_path) - index_types = self.parse_req_types_from_index(index_path) - - if not skill_types or not template_types or not index_types: - self.add("ERROR", "unparseable-req-types", skill_path, "Could not parse REQ types from skill/template/index files.") - return - - normalized_sets = { - tuple(sorted(skill_types)), - tuple(sorted(template_types)), - tuple(sorted(index_types)), - } - if len(normalized_sets) != 1: - self.add( - "ERROR", - "req-type-mismatch", - skill_path, - "REQ type taxonomy differs across draft-req skill, req-template, and srs index.", - ) - - def check_design_template_contract(self) -> None: - skill_path = self.package_root / "skills/draft-batch-design/SKILL.md" - template_path = self.package_root / "templates/batches/design.md" - if not skill_path.exists() or not template_path.exists(): - self.add("ERROR", "missing-design-contract-files", self.package_root, "Batch design contract files are missing.") - return - - required_sections = self.parse_design_required_sections(skill_path) - template_sections = self.parse_markdown_headings(template_path) - missing = [section for section in required_sections if section not in template_sections] - for section in missing: - self.add( - "ERROR", - "design-template-missing-section", - template_path, - f"Missing required section '{section}' declared by draft-batch-design.", - ) - - def check_markdown_links(self, files: Iterable[Path], relative_to: Path) -> None: - for file_path in files: - text = file_path.read_text(encoding="utf-8") - for target in LINK_PATTERN.findall(text): - target = target.strip() - if not target or target.startswith(("http://", "https://", "mailto:")): - continue - if target.startswith("#"): - continue - if any(token in target for token in ("<", ">", "{{", "}}")): - continue - link_target = target.split("#", 1)[0] - resolved = (file_path.parent / link_target).resolve() - if not resolved.exists(): - self.add( - "ERROR", - "broken-link", - file_path, - f"Link target '{target}' does not exist.", - ) - - def check_placeholders(self, files: Iterable[Path]) -> None: - for file_path in files: - text = file_path.read_text(encoding="utf-8") - matches = sorted(set(PLACEHOLDER_PATTERN.findall(text))) - if not matches: - continue - status = self.parse_status(text) - level = "ERROR" if status in FINALIZED_STATUSES else "WARN" - preview = ", ".join(matches[:3]) - if len(matches) > 3: - preview += ", ..." - self.add(level, "placeholder-found", file_path, f"Found unresolved placeholders: {preview}") - - def check_discovery_index(self) -> None: - index_path = self.workspace_root / "docs/discovery/index.md" - if not index_path.exists(): - return - - for row in self.parse_markdown_table(index_path, "## Discovery 목록"): - doc_target = self.extract_link_target(row.get("문서", "")) - doc_path = index_path.parent / doc_target if doc_target else None - if not doc_target or not doc_path or not doc_path.exists(): - self.add("ERROR", "discovery-index-link", index_path, f"Discovery row has a missing document target: {row}") - continue - doc_text = doc_path.read_text(encoding="utf-8") - doc_status = self.parse_status(doc_text) - row_status = row.get("상태", "").strip().lower() - if doc_status and row_status and doc_status != row_status: - self.add("ERROR", "discovery-status-mismatch", doc_path, f"Index status '{row_status}' does not match document status '{doc_status}'.") - - def check_srs_index(self) -> None: - index_path = self.workspace_root / "docs/srs/index.md" - if not index_path.exists(): - return - - index_text = index_path.read_text(encoding="utf-8") - req_files = sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) - register = self.parse_markdown_table(index_path, "## Requirement Register") - register_map = {self.normalize_req_id(row.get("ID", "")): row for row in register if row.get("ID")} - - max_req_number = 0 - for req_path in req_files: - req_id = self.normalize_req_id(req_path.stem.split("_", 1)[0]) - max_req_number = max(max_req_number, self.req_id_number(req_id)) - text = req_path.read_text(encoding="utf-8") - req_status = self.parse_status(text) - req_type = self.parse_req_type(text) - latest_change_req_impact = self.parse_latest_change_req_impact(text) - expected_type = req_path.parent.name - if req_type and expected_type and req_type != expected_type: - self.add("ERROR", "req-folder-type-mismatch", req_path, f"REQ type '{req_type}' does not match folder '{expected_type}'.") - self.check_req_change_rollback(req_path, req_status, latest_change_req_impact) - if req_id not in register_map: - self.add("ERROR", "req-missing-register-row", req_path, "REQ file is missing from Requirement Register.") - continue - row = register_map[req_id] - row_status = row.get("Status", "").strip().lower() - row_type = row.get("Type", "").strip() - row_link = self.extract_link_target(row.get("Link", "")) - if req_status and row_status and req_status != row_status: - self.add("ERROR", "req-status-mismatch", req_path, f"Register status '{row_status}' does not match REQ status '{req_status}'.") - if req_type and row_type and req_type != row_type: - self.add("ERROR", "req-type-register-mismatch", req_path, f"Register type '{row_type}' does not match REQ type '{req_type}'.") - if row_link: - resolved = (index_path.parent / row_link).resolve() - if resolved != req_path.resolve(): - self.add("ERROR", "req-link-mismatch", req_path, f"Register link points to '{row_link}', expected '{req_path.relative_to(index_path.parent)}'.") - - for req_id, row in register_map.items(): - link_target = self.extract_link_target(row.get("Link", "")) - if not link_target: - self.add("ERROR", "req-register-link-missing", index_path, f"Requirement Register row for '{req_id}' is missing a link target.") - continue - linked_path = (index_path.parent / link_target).resolve() - if not linked_path.exists(): - self.add("ERROR", "req-register-link-broken", index_path, f"Requirement Register link '{link_target}' does not exist.") - - next_req = self.parse_next_requirement_id(index_text) - if next_req is not None: - expected_next = max_req_number + 1 if max_req_number else 1 - if next_req != expected_next: - self.add( - "ERROR", - "next-req-id-mismatch", - index_path, - f"Next Requirement ID is REQ-{next_req:03d}, expected REQ-{expected_next:03d}.", - ) - - def check_batch_index(self) -> None: - index_path = self.workspace_root / "docs/batches/index.md" - if not index_path.exists(): - return - - rows = self.parse_markdown_table(index_path, "## Batch Register") - row_map = {row.get("BAT ID", "").strip().lower(): row for row in rows if row.get("BAT ID")} - batch_dirs = sorted(path for path in (self.workspace_root / "docs/batches").glob("bat-*") if path.is_dir()) - for batch_dir in batch_dirs: - batch_id = batch_dir.name.split("_", 1)[0].lower() - batch_index = batch_dir / "index.md" - if not batch_index.exists(): - self.add("ERROR", "batch-index-missing", batch_dir, "Batch folder is missing index.md.") - continue - batch_index_text = batch_index.read_text(encoding="utf-8") - batch_status = self.parse_status(batch_index_text) - batch_profile = self.parse_batch_profile(batch_index_text) - if batch_profile and batch_profile not in BATCH_PROFILES: - self.add( - "ERROR", - "batch-profile-invalid", - batch_index, - f"Unsupported batch profile '{batch_profile}'. Expected 'standard' or 'batch-lite'.", - ) - for doc_name in self.required_batch_docs(batch_profile, batch_status): - if not (batch_dir / doc_name).exists(): - self.add("ERROR", "batch-doc-missing", batch_dir / doc_name, "Batch folder is missing a required document.") - if batch_id not in row_map: - self.add("ERROR", "batch-missing-register-row", batch_index, "Batch folder is missing from Batch Register.") - continue - row = row_map[batch_id] - row_profile = row.get("Profile", "").strip().lower() - row_status = row.get("Status", "").strip().lower() - if row_profile and batch_profile and row_profile != batch_profile: - self.add("ERROR", "batch-profile-mismatch", batch_index, f"Register profile '{row_profile}' does not match batch profile '{batch_profile}'.") - if batch_status and row_status and batch_status != row_status: - self.add("ERROR", "batch-status-mismatch", batch_index, f"Register status '{row_status}' does not match batch status '{batch_status}'.") - - for batch_id, row in row_map.items(): - folder_target = self.extract_link_target(row.get("Folder", "")) or row.get("Folder", "").strip() - if not folder_target: - self.add("ERROR", "batch-register-folder-missing", index_path, f"Batch Register row for '{batch_id}' is missing a folder path.") - continue - linked_path = (index_path.parent / folder_target).resolve() - if not linked_path.exists(): - self.add("ERROR", "batch-register-folder-broken", index_path, f"Batch Register folder '{folder_target}' does not exist.") - - def check_release_index(self) -> None: - index_path = self.workspace_root / "docs/releases/index.md" - if not index_path.exists(): - return - - rows = self.parse_markdown_table(index_path, "## Release Register") - row_map = {row.get("REL ID", "").strip().lower(): row for row in rows if row.get("REL ID")} - release_files = sorted((self.workspace_root / "docs/releases").glob("rel-*.md")) - for release_path in release_files: - rel_id = release_path.stem.split("_", 1)[0].lower() - if rel_id not in row_map: - self.add("ERROR", "release-missing-register-row", release_path, "Release document is missing from Release Register.") - continue - row = row_map[rel_id] - release_text = release_path.read_text(encoding="utf-8") - release_status = self.parse_status(release_text) - release_profile = self.parse_release_profile(release_text) - if release_profile and release_profile not in RELEASE_PROFILES: - self.add( - "ERROR", - "release-profile-invalid", - release_path, - f"Unsupported release profile '{release_profile}'. Expected 'docs-only', 'tooling', or 'app-service'.", - ) - row_profile = row.get("Profile", "").strip().lower() - row_status = row.get("Status", "").strip().lower() - if row_profile and release_profile and row_profile != release_profile: - self.add("ERROR", "release-profile-mismatch", release_path, f"Register profile '{row_profile}' does not match release profile '{release_profile}'.") - if release_status and row_status and release_status != row_status: - self.add("ERROR", "release-status-mismatch", release_path, f"Register status '{row_status}' does not match release status '{release_status}'.") - - for rel_id, row in row_map.items(): - link_target = self.extract_link_target(row.get("Link", "")) - if not link_target: - self.add("ERROR", "release-register-link-missing", index_path, f"Release Register row for '{rel_id}' is missing a link target.") - continue - linked_path = (index_path.parent / link_target).resolve() - if not linked_path.exists(): - self.add("ERROR", "release-register-link-broken", index_path, f"Release Register link '{link_target}' does not exist.") - - def build_traceability_matrix(self) -> None: - req_files = sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) - if not req_files: - return - - discovery_refs = self.parse_discovery_traceability() - batch_refs, batch_sources, batch_statuses = self.parse_batch_traceability() - release_refs, release_statuses, release_feedback_states = self.parse_release_traceability() - - for release_id, release_status in release_statuses.items(): - feedback_state = release_feedback_states.get(release_id, "missing") - release_path = self.resolve_release_path(release_id) - if release_status == "released": - if feedback_state != "resolved": - self.add( - "WARN", - "release-feedback-unlinked", - release_path, - "Released release has no resolved Feedback Handoff content.", - ) - else: - self.add( - "WARN", - "release-feedback-pending", - release_path, - "Released release has Feedback Handoff content but is not yet closed with feedback-captured status.", - ) - elif release_status == "feedback-captured" and feedback_state != "resolved": - self.add( - "ERROR", - "release-feedback-unresolved", - release_path, - "Release is marked feedback-captured but Feedback Handoff is still unresolved.", - ) - - rows: list[TraceabilityRow] = [] - for req_path in req_files: - req_id = self.normalize_req_id(req_path.stem.split("_", 1)[0]) - if not req_id: - continue - - req_text = req_path.read_text(encoding="utf-8") - req_status = self.parse_status(req_text) - req_batches = set(batch_refs.get(req_id, set())) - req_discoveries = set(discovery_refs.get(req_id, set())) - req_releases: set[str] = set() - - for batch_id in req_batches: - req_discoveries.update(batch_sources.get(batch_id, set())) - req_releases.update(release_refs.get(batch_id, set())) - - releasable_batches = { - batch_id - for batch_id in req_batches - if batch_statuses.get(batch_id) in {"release-candidate", "released"} - } - - flags: list[str] = [] - has_discovery = bool(req_discoveries) - has_batch = bool(req_batches) - - if req_status == "implemented" and not has_batch: - flags.append("implemented-without-batch") - self.add( - "ERROR", - "implemented-req-no-batch", - req_path, - "Implemented REQ has no batch traceability.", - ) - elif req_status in {"proposed", "approved", "implemented"} and not has_discovery and not has_batch: - flags.append("orphan") - self.add( - "WARN", - "orphan-req", - req_path, - "REQ has no Discovery or Batch linkage.", - ) - elif req_status == "approved" and not has_batch: - flags.append("approved-without-batch") - self.add( - "WARN", - "orphan-approved-req", - req_path, - "Approved REQ is not included in any batch.", - ) - elif req_status == "approved" and not releasable_batches: - flags.append("approved-without-release-candidate-batch") - self.add( - "WARN", - "approved-req-no-release-candidate-batch", - req_path, - "Approved REQ is not included in any release-candidate or released batch.", - ) - - rows.append( - TraceabilityRow( - req_id=req_id, - status=req_status or "unknown", - discovery_ids=tuple(sorted(req_discoveries)), - batch_ids=tuple(sorted(req_batches)), - release_ready_batch_ids=tuple(sorted(releasable_batches)), - release_ids=tuple(sorted(req_releases)), - flags=tuple(flags), - ) - ) - - self.traceability_rows = sorted(rows, key=lambda row: self.req_id_number(row.req_id)) - - def parse_discovery_traceability(self) -> dict[str, set[str]]: - refs: dict[str, set[str]] = {} - discovery_dir = self.workspace_root / "docs/discovery" - if not discovery_dir.exists(): - return refs - - for discovery_path in sorted(discovery_dir.glob("dcy-*.md")): - discovery_id = self.normalize_discovery_id(discovery_path.stem) - if not discovery_id: - continue - for line in discovery_path.read_text(encoding="utf-8").splitlines(): - if "생성된 REQ 참조" not in line: - continue - for req_id in self.extract_req_ids(line): - refs.setdefault(req_id, set()).add(discovery_id) - return refs - - def parse_batch_traceability(self) -> tuple[dict[str, set[str]], dict[str, set[str]], dict[str, str]]: - req_to_batches: dict[str, set[str]] = {} - batch_to_discoveries: dict[str, set[str]] = {} - batch_statuses: dict[str, str] = {} - batches_dir = self.workspace_root / "docs/batches" - if not batches_dir.exists(): - return req_to_batches, batch_to_discoveries, batch_statuses - - for batch_dir in sorted(path for path in batches_dir.glob("bat-*") if path.is_dir()): - batch_index = batch_dir / "index.md" - if not batch_index.exists(): - continue - batch_id = self.normalize_batch_id(batch_dir.name) - if not batch_id: - continue - - batch_text = batch_index.read_text(encoding="utf-8") - batch_statuses[batch_id] = self.parse_status(batch_text) - - for line in batch_text.splitlines(): - if "Included REQ" in line: - for req_id in self.extract_req_ids(line): - req_to_batches.setdefault(req_id, set()).add(batch_id) - if "Source Discovery" in line: - for discovery_id in self.extract_discovery_ids(line): - batch_to_discoveries.setdefault(batch_id, set()).add(discovery_id) - - return req_to_batches, batch_to_discoveries, batch_statuses - - def parse_release_traceability(self) -> tuple[dict[str, set[str]], dict[str, str], dict[str, str]]: - batch_to_releases: dict[str, set[str]] = {} - release_statuses: dict[str, str] = {} - release_feedback_states: dict[str, str] = {} - releases_dir = self.workspace_root / "docs/releases" - if not releases_dir.exists(): - return batch_to_releases, release_statuses, release_feedback_states - - for release_path in sorted(releases_dir.glob("rel-*.md")): - release_id = self.normalize_release_id(release_path.stem) - if not release_id: - continue - release_text = release_path.read_text(encoding="utf-8") - release_statuses[release_id] = self.parse_status(release_text) - release_feedback_states[release_id] = self.parse_release_feedback_state(release_text) - for line in release_text.splitlines(): - if "Included Batch" not in line: - continue - for batch_id in self.extract_batch_ids(line): - batch_to_releases.setdefault(batch_id, set()).add(release_id) - - return batch_to_releases, release_statuses, release_feedback_states - - def write_report(self) -> None: - if self.report_path is None: - return - - try: - self.report_path.parent.mkdir(parents=True, exist_ok=True) - self.report_path.write_text(self.render_markdown_report(), encoding="utf-8") - self.report_written = True - except OSError as exc: - self.add( - "ERROR", - "report-write-failed", - self.report_path, - f"Failed to write Markdown report: {exc}", - ) - - def render_markdown_report(self) -> str: - error_count, warn_count, info_count = self.summarize_findings() - lines = [ - "# StagePilot Doctor Report", - "", - "## Metadata", - "", - f"- Generated At: `{datetime.now().astimezone().isoformat(timespec='seconds')}`", - f"- Workspace Root: `{self.workspace_root}`", - f"- Package Root: `{self.package_root}`", - f"- Strict Missing Docs: `{str(self.strict_missing_docs).lower()}`", - ] - if self.report_path is not None: - lines.append(f"- Requested Report Path: `{self.report_path}`") - - lines.extend( - [ - "", - "## Summary", - "", - f"- Errors: {error_count}", - f"- Warnings: {warn_count}", - f"- Info: {info_count}", - "", - "## Findings", - "", - ] - ) - - sorted_findings = self.sorted_findings() - if not sorted_findings: - lines.append("- None") - else: - for level in ("ERROR", "WARN", "INFO"): - level_findings = [finding for finding in sorted_findings if finding.level == level] - if not level_findings: - continue - lines.append(f"### {level}") - lines.append("") - for finding in level_findings: - lines.append( - f"- [{finding.code}] `{self.display_path(finding.path)}`: {finding.message}" - ) - lines.append("") - - lines.extend(["## Traceability Matrix", ""]) - if not self.traceability_rows: - lines.append("- No traceability rows generated.") - else: - lines.extend( - [ - "| REQ ID | Status | Discovery | Batch | Release-Ready Batch | Release | Flags |", - "| --- | --- | --- | --- | --- | --- | --- |", - ] - ) - for row in self.traceability_rows: - discovery = ", ".join(row.discovery_ids) if row.discovery_ids else "-" - batch = ", ".join(row.batch_ids) if row.batch_ids else "-" - release_ready_batch = ", ".join(row.release_ready_batch_ids) if row.release_ready_batch_ids else "-" - release = ", ".join(row.release_ids) if row.release_ids else "-" - flags = ", ".join(row.flags) if row.flags else "-" - lines.append( - f"| {row.req_id} | {row.status} | {discovery} | {batch} | {release_ready_batch} | {release} | {flags} |" - ) - - lines.extend(["", "## Feedback Loop Summary", ""]) - if not self.feedback_handoff_rows: - lines.append("- No feedback handoff rows generated.") - else: - lines.extend( - [ - "| Release | Status | Discovery Input | REQ Input | Change Request Input | Flags |", - "| --- | --- | --- | --- | --- | --- |", - ] - ) - for row in self.feedback_handoff_rows: - discovery_inputs = ", ".join(row.discovery_inputs) if row.discovery_inputs else "-" - req_inputs = ", ".join(row.req_inputs) if row.req_inputs else "-" - change_req_inputs = ", ".join(row.change_req_inputs) if row.change_req_inputs else "-" - flags = ", ".join(row.flags) if row.flags else "-" - lines.append( - f"| {row.release_id} | {row.status} | {discovery_inputs} | {req_inputs} | {change_req_inputs} | {flags} |" - ) - - return "\n".join(lines) + "\n" - - def sorted_findings(self) -> list[Finding]: - severity_order = {"ERROR": 0, "WARN": 1, "INFO": 2} - return sorted( - self.findings, - key=lambda item: (severity_order.get(item.level, 99), str(item.path), item.code), - ) - - def summarize_findings(self) -> tuple[int, int, int]: - error_count = sum(1 for finding in self.findings if finding.level == "ERROR") - warn_count = sum(1 for finding in self.findings if finding.level == "WARN") - info_count = sum(1 for finding in self.findings if finding.level == "INFO") - return error_count, warn_count, info_count - - def print_report(self) -> None: - if not self.findings: - print("stagepilot-doctor: no issues found") - else: - for finding in self.sorted_findings(): - display_path = self.display_path(finding.path) - print(f"{finding.level} [{finding.code}] {display_path}: {finding.message}") - - if self.traceability_rows: - print() - print("Traceability Matrix") - print("REQ ID | Status | Discovery | Batch | Release-Ready Batch | Release | Flags") - print("--- | --- | --- | --- | --- | --- | ---") - for row in self.traceability_rows: - discovery = ", ".join(row.discovery_ids) if row.discovery_ids else "-" - batch = ", ".join(row.batch_ids) if row.batch_ids else "-" - release_ready_batch = ", ".join(row.release_ready_batch_ids) if row.release_ready_batch_ids else "-" - release = ", ".join(row.release_ids) if row.release_ids else "-" - flags = ", ".join(row.flags) if row.flags else "-" - print(f"{row.req_id} | {row.status} | {discovery} | {batch} | {release_ready_batch} | {release} | {flags}") - - if self.feedback_handoff_rows: - print() - print("Feedback Loop Summary") - print("Release | Status | Discovery Input | REQ Input | Change Request Input | Flags") - print("--- | --- | --- | --- | --- | ---") - for row in self.feedback_handoff_rows: - discovery_inputs = ", ".join(row.discovery_inputs) if row.discovery_inputs else "-" - req_inputs = ", ".join(row.req_inputs) if row.req_inputs else "-" - change_req_inputs = ", ".join(row.change_req_inputs) if row.change_req_inputs else "-" - flags = ", ".join(row.flags) if row.flags else "-" - print(f"{row.release_id} | {row.status} | {discovery_inputs} | {req_inputs} | {change_req_inputs} | {flags}") - - error_count, warn_count, info_count = self.summarize_findings() - print(f"stagepilot-doctor: {error_count} error(s), {warn_count} warning(s), {info_count} info message(s)") - if self.report_written and self.report_path is not None: - print(f"stagepilot-doctor: wrote Markdown report to {self.report_path}") - - def display_path(self, path: Path) -> str: - for base in (self.workspace_root, self.package_root): - try: - return str(path.resolve().relative_to(base.resolve())) or "." - except ValueError: - continue - return str(path) - - def resolve_release_path(self, release_id: str) -> Path: - releases_dir = self.workspace_root / "docs/releases" - matches = sorted(releases_dir.glob(f"{release_id}_*.md")) - if matches: - return matches[0] - return releases_dir / f"{release_id}.md" - - def check_req_change_rollback(self, req_path: Path, req_status: str, latest_change_req_impact: ChangeReqImpact | None) -> None: - if latest_change_req_impact is None: - return - - recommendation = latest_change_req_impact.status_recommendation - implementation_invalidated = latest_change_req_impact.implementation_invalidated == "yes" - reverification_needed = latest_change_req_impact.reverification_needed == "yes" - - if recommendation == "revert-to-approved" and req_status == "implemented": - self.add( - "ERROR", - "req-change-rollback-missing", - req_path, - f"Latest change '{latest_change_req_impact.change_id}' recommends reverting to Approved, but REQ status is still Implemented.", - ) - elif recommendation == "revert-to-proposed" and req_status in {"approved", "implemented"}: - self.add( - "ERROR", - "req-change-rollback-missing", - req_path, - f"Latest change '{latest_change_req_impact.change_id}' recommends reverting to Proposed, but REQ status is still '{req_status}'.", - ) - elif implementation_invalidated and req_status == "implemented": - self.add( - "ERROR", - "req-change-rollback-missing", - req_path, - f"Latest change '{latest_change_req_impact.change_id}' invalidates existing implementation, but REQ status is still Implemented.", - ) - elif reverification_needed and req_status == "implemented": - self.add( - "WARN", - "req-change-reverification-pending", - req_path, - f"Latest change '{latest_change_req_impact.change_id}' requires reverification while REQ remains Implemented.", - ) - - def build_feedback_handoff_summary(self) -> None: - releases_dir = self.workspace_root / "docs/releases" - if not releases_dir.exists(): - self.feedback_handoff_rows = [] - return - - existing_req_ids = { - self.normalize_req_id(path.stem.split("_", 1)[0]) - for path in sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) - } - rows: list[FeedbackHandoffRow] = [] - - for release_path in sorted(releases_dir.glob("rel-*.md")): - release_id = self.normalize_release_id(release_path.stem) - if not release_id: - continue - - release_text = release_path.read_text(encoding="utf-8") - feedback_inputs = self.parse_release_feedback_inputs(release_text) - discovery_inputs = self.normalize_feedback_values(feedback_inputs.get("discovery", [])) - req_inputs = self.normalize_feedback_values(feedback_inputs.get("req", [])) - change_req_inputs = self.normalize_feedback_values(feedback_inputs.get("change-req", [])) - - flags: list[str] = [] - if not discovery_inputs and not req_inputs and not change_req_inputs: - flags.append("no-follow-up-input") - - referenced_change_req_ids = { - req_id - for value in change_req_inputs - for req_id in self.extract_req_ids(value) - } - missing_change_req_ids = sorted(req_id for req_id in referenced_change_req_ids if req_id not in existing_req_ids) - if missing_change_req_ids: - flags.append("unknown-change-req-target") - self.add( - "WARN", - "feedback-change-req-target-missing", - release_path, - f"Change Request Input references missing REQ IDs: {', '.join(missing_change_req_ids)}", - ) - - rows.append( - FeedbackHandoffRow( - release_id=release_id, - status=self.parse_status(release_text) or "unknown", - discovery_inputs=discovery_inputs, - req_inputs=req_inputs, - change_req_inputs=change_req_inputs, - flags=tuple(flags), - ) - ) - - self.feedback_handoff_rows = rows - - @staticmethod - def parse_req_types_from_skill(path: Path) -> list[str]: - lines = path.read_text(encoding="utf-8").splitlines() - collecting = False - types: list[str] = [] - for line in lines: - if "아래 폴더 중 정확히 하나를 선택한다." in line: - collecting = True - continue - if collecting and line.startswith("- 새 REQ 경로"): - break - if collecting: - match = INLINE_CODE_PATTERN.search(line) - if match: - types.append(match.group(1)) - return types - - @staticmethod - def parse_req_types_from_template(path: Path) -> list[str]: - text = path.read_text(encoding="utf-8") - match = REQ_TYPE_LINE_PATTERN.search(text) - if not match: - return [] - return [part.strip() for part in match.group(1).split("|") if part.strip()] - - @staticmethod - def parse_req_types_from_index(path: Path) -> list[str]: - rows = Doctor.parse_markdown_table(path, "## Requirement 타입 분류 도표") - types: list[str] = [] - for row in rows: - type_name = row.get("타입", "").strip().strip("*") - if type_name: - types.append(type_name) - return types - - @staticmethod - def parse_design_required_sections(path: Path) -> list[str]: - lines = path.read_text(encoding="utf-8").splitlines() - collecting = False - sections: list[str] = [] - for line in lines: - if "design.md에는 최소한 아래 내용을 포함해야 한다." in line: - collecting = True - continue - if collecting and not line.startswith(("\t-", " -", "- ")): - if line.strip(): - break - if collecting: - entry = line.strip().lstrip("-").strip() - if not entry: - continue - cleaned = entry.strip("`") - if "(" in cleaned: - cleaned = cleaned.split("(", 1)[0].strip() - sections.append(cleaned) - return sections - - @staticmethod - def parse_markdown_headings(path: Path) -> set[str]: - headings = set() - for line in path.read_text(encoding="utf-8").splitlines(): - if line.startswith("## "): - headings.add(line[3:].strip()) - return headings - - @staticmethod - def parse_markdown_table(path: Path, heading: str) -> list[dict[str, str]]: - lines = path.read_text(encoding="utf-8").splitlines() - start_index = None - for index, line in enumerate(lines): - if line.strip() == heading: - start_index = index + 1 - break - if start_index is None: - return [] - - table_lines: list[str] = [] - for line in lines[start_index:]: - if line.strip().startswith("|"): - table_lines.append(line) - continue - if table_lines: - break - - if len(table_lines) < 2: - return [] - - header = [cell.strip() for cell in table_lines[0].strip().strip("|").split("|")] - rows: list[dict[str, str]] = [] - for line in table_lines[2:]: - cells = [cell.strip() for cell in line.strip().strip("|").split("|")] - if len(cells) != len(header): - continue - rows.append(dict(zip(header, cells))) - return rows - - @staticmethod - def extract_link_target(cell: str) -> str | None: - match = LINK_PATTERN.search(cell) - if match: - return match.group(1).split("#", 1)[0] - return None - - @staticmethod - def parse_status(text: str) -> str: - match = STATUS_PATTERN.search(text) - if not match: - return "" - value = match.group(1).strip().strip("`") - return value.split("|")[0].strip().lower() - - @staticmethod - def parse_batch_profile(text: str) -> str: - match = BATCH_PROFILE_PATTERN.search(text) - if not match: - return "standard" - value = match.group(1).strip().strip("`") - return value.split("|")[0].strip().lower() - - @staticmethod - def parse_release_profile(text: str) -> str: - match = RELEASE_PROFILE_PATTERN.search(text) - if not match: - return "app-service" - value = match.group(1).strip().strip("`") - return value.split("|")[0].strip().lower() - - @staticmethod - def required_batch_docs(profile: str, status: str) -> tuple[str, ...]: - normalized_profile = profile or "standard" - normalized_status = status or "draft" - if normalized_profile == "batch-lite": - required_docs = ["planning.md"] - if normalized_status in {"in-delivery", "release-candidate", "released", "archived"}: - required_docs.append("implementation.md") - if normalized_status in {"release-candidate", "released", "archived"}: - required_docs.append("verification.md") - return tuple(required_docs) - return ("planning.md", "design.md", "implementation.md", "verification.md") - - @staticmethod - def parse_req_type(text: str) -> str: - match = REQ_TYPE_LINE_PATTERN.search(text) - if not match: - return "" - value = match.group(1).strip() - return value.split("|")[0].strip() - - @staticmethod - def parse_next_requirement_id(text: str) -> int | None: - match = re.search(r"Current:\s*`REQ-(\d+)`", text) - if not match: - return None - return int(match.group(1)) - - @staticmethod - def parse_release_feedback_state(text: str) -> str: - lines = text.splitlines() - in_section = False - values: list[str] = [] - for line in lines: - stripped = line.strip() - if stripped == "## Feedback Handoff": - in_section = True - continue - if in_section and stripped.startswith("## "): - break - if not in_section: - continue - if stripped.startswith("- "): - value = stripped[2:].strip() - if value.endswith(":"): - continue - values.append(value) - - if not in_section: - return "missing" - if not values: - return "missing" - - normalized_values = {value.strip().lower() for value in values} - unresolved_tokens = ( - "<", - ">", - "{{", - "}}", - "후속 discovery 후보", - "후속 req 후보", - "tbd", - "미정", - ) - if any(any(token in value for token in unresolved_tokens) for value in normalized_values): - return "unresolved" - if normalized_values.issubset({"없음", "none", "n/a", "na"}): - return "resolved" - return "resolved" - - @staticmethod - def parse_latest_change_req_impact(text: str) -> ChangeReqImpact | None: - sections = re.split(r"^###\s+(CHG-\d{8}-\d+)\s*$", text, flags=re.MULTILINE) - if len(sections) < 3: - return None - - latest_change_id = sections[-2].strip() - latest_change_body = sections[-1] - status_recommendation = Doctor.parse_change_log_field(latest_change_body, "Status Recommendation") - reverification_needed = Doctor.parse_change_log_field(latest_change_body, "Reverification Needed") - implementation_invalidated = Doctor.parse_change_log_field(latest_change_body, "Existing Implementation Invalidated") - - if not status_recommendation and not reverification_needed and not implementation_invalidated: - return None - - return ChangeReqImpact( - change_id=latest_change_id, - status_recommendation=status_recommendation, - reverification_needed=reverification_needed, - implementation_invalidated=implementation_invalidated, - ) - - @staticmethod - def parse_change_log_field(text: str, field_name: str) -> str: - pattern = rf"^-\s*{re.escape(field_name)}:\s*(.+?)\s*$" - match = re.search(pattern, text, flags=re.MULTILINE) - if not match: - return "" - return match.group(1).strip().strip("`").lower() - - @staticmethod - def parse_release_feedback_inputs(text: str) -> dict[str, list[str]]: - lines = text.splitlines() - in_section = False - current_group: str | None = None - groups: dict[str, list[str]] = { - "observation": [], - "discovery": [], - "req": [], - "change-req": [], - } - group_labels = { - "Observation Summary": "observation", - "Discovery Input": "discovery", - "REQ Input": "req", - "Change Request Input": "change-req", - } - - for line in lines: - stripped = line.strip() - if stripped == "## Feedback Handoff": - in_section = True - current_group = None - continue - if in_section and stripped.startswith("## "): - break - if not in_section or not stripped.startswith("- "): - continue - - value = stripped[2:].strip() - if value.endswith(":"): - current_group = group_labels.get(value[:-1].strip()) - continue - if current_group is not None: - groups[current_group].append(value) - - return groups - - @staticmethod - def normalize_feedback_values(values: Iterable[str]) -> tuple[str, ...]: - normalized: list[str] = [] - for value in values: - cleaned = value.strip() - if not cleaned or cleaned.lower() in {"없음", "none", "n/a", "na"}: - continue - normalized.append(cleaned) - return tuple(normalized) - - @staticmethod - def extract_req_ids(text: str) -> set[str]: - return {f"req-{int(match.group(1)):03d}" for match in REQ_ID_PATTERN.finditer(text)} - - @staticmethod - def extract_batch_ids(text: str) -> set[str]: - return {f"bat-{int(match.group(1)):03d}" for match in BATCH_ID_PATTERN.finditer(text)} - - @staticmethod - def extract_discovery_ids(text: str) -> set[str]: - return {f"dcy-{int(match.group(1)):03d}" for match in DISCOVERY_ID_PATTERN.finditer(text)} - - @staticmethod - def extract_release_ids(text: str) -> set[str]: - return {f"rel-{int(match.group(1)):03d}" for match in RELEASE_ID_PATTERN.finditer(text)} - - @staticmethod - def normalize_req_id(raw_value: str) -> str: - match = REQ_ID_PATTERN.search(raw_value or "") - if not match: - return "" - return f"req-{int(match.group(1)):03d}" - - @staticmethod - def normalize_batch_id(raw_value: str) -> str: - match = BATCH_ID_PATTERN.search(raw_value or "") - if not match: - return "" - return f"bat-{int(match.group(1)):03d}" - - @staticmethod - def normalize_discovery_id(raw_value: str) -> str: - match = DISCOVERY_ID_PATTERN.search(raw_value or "") - if not match: - return "" - return f"dcy-{int(match.group(1)):03d}" - - @staticmethod - def normalize_release_id(raw_value: str) -> str: - match = RELEASE_ID_PATTERN.search(raw_value or "") - if not match: - return "" - return f"rel-{int(match.group(1)):03d}" - - @staticmethod - def req_id_number(req_id: str) -> int: - match = REQ_ID_PATTERN.search(req_id) - return int(match.group(1)) if match else 0 - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="Validate StagePilot package contracts and active SDLC workspace documents.") - parser.add_argument("root", nargs="?", default=".", help="Workspace root to validate (default: current directory)") - parser.add_argument( - "--strict-missing-docs", - action="store_true", - help="Treat a missing active docs workspace as an error instead of a warning.", - ) - parser.add_argument( - "--report", - help="Write a Markdown report to the given path.", - ) - return parser - - -def main() -> int: - parser = build_parser() - args = parser.parse_args() - - workspace_root = Path(args.root).resolve() - # tools/stagepilot-doctor.py 기준 한 단계 위가 패키지 루트. - # (호스트에서는 .stage-pilot subtree 루트, 개발 레포에서는 저장소 루트) - package_root = Path(__file__).resolve().parents[1] - report_path = Path(args.report).resolve() if args.report else None - - if not workspace_root.exists(): - parser.error(f"Workspace root does not exist: {workspace_root}") - - doctor = Doctor( - workspace_root=workspace_root, - package_root=package_root, - strict_missing_docs=args.strict_missing_docs, - report_path=report_path, - ) - return doctor.run() - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/.stage-pilot/tools/stagepilot.sh b/.stage-pilot/tools/stagepilot.sh deleted file mode 100644 index 841ae06..0000000 --- a/.stage-pilot/tools/stagepilot.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: stagepilot.sh [args] - -Commands: - bootstrap-seed [HOST_ROOT] [options] - Create .stage-pilot/bootstrap/baseline.yaml from interactive answers - or explicit CLI arguments. - doctor [HOST_ROOT] [--strict-missing-docs] [--report ] - Run the StagePilot validation script against a workspace. -EOF -} - -fail() { - printf '[stagepilot] ERROR: %s\n' "$*" >&2 - exit 1 -} - -run_doctor() { - local script_dir package_root doctor_script - script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" - package_root="$(cd "${script_dir}/.." && pwd -P)" - doctor_script="${package_root}/tools/stagepilot-doctor.py" - - [ -f "${doctor_script}" ] || fail "Doctor script not found: ${doctor_script}" - command -v python3 >/dev/null 2>&1 || fail "python3 is required to run doctor" - - python3 "${doctor_script}" "$@" -} - -run_bootstrap_seed() { - local script_dir package_root seed_script - script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" - package_root="$(cd "${script_dir}/.." && pwd -P)" - seed_script="${package_root}/tools/stagepilot-bootstrap-seed.py" - - [ -f "${seed_script}" ] || fail "Bootstrap seed helper not found: ${seed_script}" - command -v python3 >/dev/null 2>&1 || fail "python3 is required to generate a bootstrap seed" - - python3 "${seed_script}" "$@" -} - -main() { - local command="${1:-}" - if [ -z "${command}" ]; then - usage - exit 1 - fi - shift || true - - case "${command}" in - bootstrap-seed) - run_bootstrap_seed "$@" - ;; - doctor) - run_doctor "$@" - ;; - -h|--help|help) - usage - ;; - *) - usage - fail "Unknown command: ${command}" - ;; - esac -} - -main "$@" \ No newline at end of file diff --git a/.stage-pilot/tools/update.sh b/.stage-pilot/tools/update.sh deleted file mode 100755 index d9b2e58..0000000 --- a/.stage-pilot/tools/update.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: update.sh [options] [HOST_ROOT] - -Options: - --dry-run Show planned commands without executing them. - --remote Git remote name to use when available (default: stage-pilot) - --repo-url Fallback repository URL when remote is missing. - (default: https://github.com/genichin/stage-pilot.git) - --branch Upstream branch to sync (default: main) - --prefix Subtree prefix path (default: .stage-pilot) - --overwrite Overwrite conflicts during install (same as --policy replace). - (default behavior) - --no-overwrite Preserve host files on conflicts (same as --policy preserve). - --policy install.sh conflict policy: replace|preserve|fail - (default: replace) - --skip-install Only update subtree. Do not run install.sh. - -h, --help Show this help message. - -Arguments: - HOST_ROOT Host repository root (default: current directory) -EOF -} - -log() { - printf '[update] %s\n' "$*" -} - -fail() { - printf '[update] ERROR: %s\n' "$*" >&2 - exit 1 -} - -run_cmd() { - if [ "${DRY_RUN}" -eq 1 ]; then - log "[dry-run] $*" - else - "$@" - fi -} - -DRY_RUN=0 -REMOTE_NAME="stage-pilot" -REPO_URL="https://github.com/genichin/stage-pilot.git" -BRANCH="main" -PREFIX=".stage-pilot" -POLICY="replace" -SKIP_INSTALL=0 -ARGS=() - -while [ "$#" -gt 0 ]; do - case "$1" in - --dry-run) - DRY_RUN=1 - ;; - --remote) - [ "$#" -ge 2 ] || fail "--remote requires a value" - REMOTE_NAME="$2" - shift - ;; - --repo-url) - [ "$#" -ge 2 ] || fail "--repo-url requires a value" - REPO_URL="$2" - shift - ;; - --branch) - [ "$#" -ge 2 ] || fail "--branch requires a value" - BRANCH="$2" - shift - ;; - --prefix) - [ "$#" -ge 2 ] || fail "--prefix requires a value" - PREFIX="$2" - shift - ;; - --overwrite) - POLICY="replace" - ;; - --no-overwrite) - POLICY="preserve" - ;; - --policy) - [ "$#" -ge 2 ] || fail "--policy requires a value" - POLICY="$2" - shift - ;; - --skip-install) - SKIP_INSTALL=1 - ;; - -h|--help) - usage - exit 0 - ;; - --*) - fail "Unknown option: $1" - ;; - *) - ARGS+=("$1") - ;; - esac - shift -done - -HOST_ROOT="${ARGS[0]:-$(pwd)}" - -case "${POLICY}" in - replace|preserve|fail) - ;; - *) - fail "Invalid --policy '${POLICY}'. Allowed: replace|preserve|fail" - ;; -esac - -[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" - -if ! git -C "${HOST_ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - fail "Host root is not a git repository: ${HOST_ROOT}" -fi - -if git -C "${HOST_ROOT}" remote get-url "${REMOTE_NAME}" >/dev/null 2>&1; then - SOURCE_REF="${REMOTE_NAME}" - log "Using git remote '${REMOTE_NAME}'" -else - SOURCE_REF="${REPO_URL}" - log "Remote '${REMOTE_NAME}' not found; using URL '${REPO_URL}'" -fi - -if [ -d "${HOST_ROOT}/${PREFIX}" ]; then - run_cmd git -C "${HOST_ROOT}" subtree pull --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash -else - log "Subtree prefix not found; adding subtree at ${PREFIX}" - run_cmd git -C "${HOST_ROOT}" subtree add --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash -fi - -if [ "${SKIP_INSTALL}" -eq 1 ]; then - log "Skip install requested. Update completed." - exit 0 -fi - -INSTALL_SCRIPT="${HOST_ROOT}/${PREFIX}/tools/install.sh" -[ -f "${INSTALL_SCRIPT}" ] || fail "Install script not found after update: ${INSTALL_SCRIPT}" - -if [ "${DRY_RUN}" -eq 1 ]; then - run_cmd /bin/bash "${INSTALL_SCRIPT}" --dry-run "${HOST_ROOT}" "${POLICY}" -else - /bin/bash "${INSTALL_SCRIPT}" "${HOST_ROOT}" "${POLICY}" -fi - -log "Update completed successfully." diff --git a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/design.md b/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/design.md deleted file mode 100644 index e674ea8..0000000 --- a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/design.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-001 Design - -- Status: confirmed -- Batch: BAT-001 - -## Architecture Summary -- trex-core 를 `trex_core` import 패키지(배포명 `trex-core`)로 부트스트랩하고, 첫 도메인으로 slot 순수 도메인을 `trex_core/domain/slot/` 에 둔다. 도메인은 저장소를 `SlotRepository` Protocol 로 추상화하고 구현체(InMemorySlotRepository)를 주입받는 storage-agnostic 구조다. 인프라(향후 PostgreSQL/CLI/config)는 도메인을 의존할 수 있으나 그 역은 금지한다. - -## Changed Areas -- Docs: docs/project-structure.md, docs/runtime-flows.md (REQ-004 정합), docs/data-model.md·docs/interface-contract.md (계약 기준 참조) -- Modules: pyproject.toml, trex_core/__init__.py, trex_core/domain/__init__.py, trex_core/domain/slot/{__init__,state,models,errors,repository,service,in_memory_repository}.py -- Interfaces: 공개 API = SlotState, Slot, SlotCapacity, SlotTransitionRecord, SlotRepository, SlotService, 예외 4종, is_allowed_transition, SLOT_ADVISORY_LOCK_ID, ALLOWED_TRANSITIONS - -## Key Decisions -1. 네임스페이스 `trex_core.domain.slot`(dcy-001 OQ-1). 모듈 간 import 를 모두 `trex_core.domain.slot.*` 로 통일하고 `app.domain.slot` 잔존을 0으로 만든다. -2. 컬럼 계약 박제 = 계약 테스트(dcy-001 OQ-2). 도메인에 `EXPECTED_SLOT_FIELDS` 표준 필드 집합을 선언하고 `test_contract.py` 가 `{f.name for f in dataclasses.fields(Slot)}` 와의 일치를 단언. 타입 단언은 하지 않고 타입 매핑은 문서(부록 A/data-model)에 둔다. -3. 공개 API 는 인프라 심볼(`PostgreSQLSlotRepository`, `check_slot_domain_schema_drift`)을 노출하지 않는다 — 애초 `__init__` 이 이를 export 하지 않으므로 docstring 정리만 수행. -4. `SLOT_ADVISORY_LOCK_ID` 는 순수 도메인에 잔류(현행 유지). 영속화 계층 신설 시 재배치 여부는 후속 반복(dcy-001 OQ-3 인접) 판단. - -## Edge Cases -- `EXPECTED_SLOT_FIELDS` 와 `Slot` 필드 불일치 시 계약 테스트가 실패해야 한다(음성 케이스 의도적 검증). -- `closed→idle`/`pending_buy→idle` 복구 시 점유 필드 초기화 + 재진입 카운트 +1 불변식 보존 확인(이식 테스트 범위). -- in-memory repo 의 threading.Lock 이 advisory lock 의미를 모사하므로, 동시성 관련 테스트도 DB 없이 성립. - -## Architecture Impact -- both (project-structure + runtime-flows) - -## Reference Doc Update Plan -- project-structure.md: planned 트리·모듈 책임의 `(planned)` 표기를 실제 `trex_core/` 구조로 갱신(REQ-004). 네임스페이스는 이미 `trex_core/` 로 정정 완료, 잔여는 실재 반영. -- runtime-flows.md: in-memory 기반 도메인 흐름(preallocate/transit)이 실제 코드와 일치하는지 확인하고 "미구현/planned" 성격 표기를 정정(REQ-004). diff --git a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/implementation.md b/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/implementation.md deleted file mode 100644 index 71d10f9..0000000 --- a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/implementation.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-001 Implementation - -- Status: completed -- Batch: BAT-001 - -## Plan Summary -- REQ-001 부트스트랩 → REQ-002 slot 순수 도메인 이식 → REQ-003 테스트·계약 가드 → REQ-004 baseline 정합을 순차 구현했다. trex-orbit `app/domain/slot/` 의 순수 6개 모듈 + `__init__` 을 `trex_core.domain.slot` 로 복사하고 import 를 전면 치환, 테스트 2종 이식 + 계약 테스트 신설. - -## Changed Files -- 신규(REQ-001): `pyproject.toml`, `trex_core/__init__.py`, `trex_core/domain/__init__.py`, `tests/__init__.py`, `tests/domain/__init__.py`, `tests/domain/slot/__init__.py` -- 신규(REQ-002): `trex_core/domain/slot/{__init__,state,models,errors,repository,service,in_memory_repository}.py` - - import 경로 `app.domain.slot.*` → `trex_core.domain.slot.*` 전면 치환, `__init__` docstring 순수 도메인 기준 정리(인프라 export 없음 확인) -- 신규(REQ-003): `tests/domain/slot/{test_state,test_service,test_contract}.py`, `models.py` 에 `EXPECTED_SLOT_FIELDS` 선언 -- 갱신(REQ-004): `docs/project-structure.md`, `docs/runtime-flows.md` planned→실제 정합 - -## Execution Log -1. `pyproject.toml`(name=trex-core, requires-python>=3.12, setuptools, dev extras=pytest·ruff) + 패키지 레이아웃 생성. -2. 순수 6개 모듈 + `__init__` 이식, import 치환, `EXPECTED_SLOT_FIELDS` 추가. -3. `test_state.py`·`test_service.py` 이식(미사용 import 정리, `t1.start()/join()` 멀티스테이트먼트 분리), `test_contract.py` 신설. -4. `.venv` 생성 후 `pip install -e .[dev]` (PEP 668 외부관리 환경이라 venv 사용). -5. `pytest`, `ruff check .`, 공개 API import, import 스윕, 계약 음성 케이스 실행. -6. `docs/project-structure.md`·`docs/runtime-flows.md` 정합(REQ-004). - -## Validation -- `pytest -q`: **56 passed** (test_state 18 + test_service 35 + test_contract 3, 근사). -- `ruff check .`: **All checks passed**. -- 공개 API import: 15개 심볼 전부 import 성공(`SlotState`...`EXPECTED_SLOT_FIELDS`). -- import 스윕: `trex_core/domain/slot/*.py` 에 stdlib + `trex_core.domain.slot` 외 외부 의존 **0**. -- 공개 `__all__`: `PostgreSQL`/`schema` 인프라 심볼 노출 **0**. -- 계약 음성 케이스: `Slot` 에 rogue 필드 추가 시 계약 일치 단언이 False 로 떨어짐(실패 재현 확인). - -## Remaining Risks -- R-1 영속화 부재(in-memory 한정) — 후속 Discovery 인계. -- R-2 DB-side 컬럼 드리프트 검사 미도입 — 영속화 REQ 인계(계약 테스트는 코드 측 가드 확보). -- 환경: 시스템 Python 3.14.4 에서 venv 로 검증(requires-python>=3.12 충족). CI 표준 인터프리터 고정은 후속. diff --git a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/index.md b/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/index.md deleted file mode 100644 index b10c61c..0000000 --- a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-001: trex-core 베이스 부트스트랩 + slot 순수 도메인 시드 - -- Status: released -- Profile: standard -- Source Discovery: dcy-001 -- Included REQ: REQ-001, REQ-002, REQ-003, REQ-004 -- Excluded REQ: 없음 - -## Summary -- dcy-001 의 단일 가치(trex-core 베이스 수립 + slot 순수 도메인 시드)를 하나의 delivery 단위로 묶는다. 부트스트랩(REQ-001) → 도메인 이식(REQ-002) → 테스트·계약 가드(REQ-003) → baseline 정합(REQ-004)이 같은 iteration·같은 모듈 경로(`trex_core/domain/slot`)·같은 검증 흐름(`pytest`)을 공유한다. - -## Scope -- In Scope: - - REQ-001: `pyproject.toml`(배포명 `trex-core`) + `trex_core/`·`trex_core/domain/` 레이아웃 + pytest/ruff 설정. - - REQ-002: 순수 6개 모듈 + `__init__` 을 `trex_core.domain.slot` 로 이식, 공개 API 인프라 심볼 없이 노출. - - REQ-003: 단위테스트(`test_state`/`test_service`) 이식 green + 계약 가드(`EXPECTED_SLOT_FIELDS` + `test_contract`). - - REQ-004: 이식 후 `docs/project-structure.md`·`docs/runtime-flows.md` planned→실제 정합. -- Out of Scope: - - PostgreSQL 구현체/ SQL DDL/ `db init`/ schema drift 감지. - - `trex slot` CLI, config 해석, coordinator 연동, archive/prune. - - trex-orbit 수정 및 trex-orbit↔trex-core 코드 수렴. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery 는 dcy-001 단일. 모든 REQ 가 dcy-001 FR-1~FR-5 에서 분해됨. -- 공통 리스크: 영속화 부재(in-memory 한정, dcy-001 R-1), 컬럼 계약 드리프트(REQ-003 계약 테스트로 완화, R-2), 이중 진실(병행, R-3). -- 네임스페이스 `trex_core.domain.slot`(dcy-001 OQ-1), 컬럼 계약 박제 = 계약 테스트(OQ-2). DB-side 드리프트 검사는 영속화 반복 인계. diff --git a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/planning.md b/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/planning.md deleted file mode 100644 index 86b2427..0000000 --- a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/planning.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-001 Planning - -- Status: confirmed -- Batch: BAT-001 -- Profile: standard - -## Included REQ -- REQ-001 (Installation): trex-core Python 프로젝트 부트스트랩 -- REQ-002 (Interface): slot 순수 도메인 이식 및 공개 API 확정 -- REQ-003 (Testing): slot 도메인 단위테스트 이식 및 컬럼 계약 가드 -- REQ-004 (Documentation): baseline 문서 planned→실제 정합 - -## Delivery Plan -- 선형 의존을 따라 순차 구현한다. - 1. REQ-001: `pyproject.toml`(name=`trex-core`, requires-python>=3.12, setuptools, dev extras=pytest·ruff) + `trex_core/__init__.py`, `trex_core/domain/__init__.py` + `tests/` 스캐폴딩 + ruff/pytest 설정. - 2. REQ-002: trex-orbit `app/domain/slot/` 의 순수 6개 모듈 + `__init__` 을 `trex_core/domain/slot/` 로 복사하고 모듈 간 import 를 `trex_core.domain.slot.*` 로 치환, `__init__` docstring 정리(인프라 export 없음 확인). - 3. REQ-003: `tests/domain/slot/test_state.py`·`test_service.py` 이식, `trex_core/domain/slot` 에 `EXPECTED_SLOT_FIELDS` 선언, `test_contract.py` 신설(`fields(Slot)` 일치 단언). - 4. REQ-004: 위 산출물 확정 후 `docs/project-structure.md`·`docs/runtime-flows.md` 의 planned 표기를 실제 구조로 정합. - -## Dependencies -- Internal: - - REQ-002 는 REQ-001(패키지 레이아웃) 선행 필요. - - REQ-003 은 REQ-002(도메인 코드) 선행 필요. - - REQ-004 는 REQ-002(실제 구조 확정) 선행 필요. -- External: - - 없음(외부 서비스·DB 미사용). 개발 의존성: pytest, ruff(dev extras). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 패키지 네임스페이스(`trex_core`) 도입 + 공개 API 표면 확정 + baseline 구조/런타임 문서 정합이 함께 걸린다(구조·인터페이스 영향). planning 만으로는 import 경계·공개 API·계약 가드 위치 결정이 드리프트할 수 있어 design.md 를 같은 턴에 작성한다. - -## Milestones -1. M1 — REQ-001: 부트스트랩 완료(`pip install -e .[dev]` + `pytest` 수집 성공 + `ruff check` 동작). -2. M2 — REQ-002: 도메인 이식 + 공개 API import 성공 + import 스윕 외부 의존 0. -3. M3 — REQ-003: 이식 테스트 2종 green + 계약 테스트 green(필드 변경 시 실패 재현). -4. M4 — REQ-004: baseline 문서 정합(`app/` 잔존 0, 트리/흐름 실제 일치). - -## Risks -- R-1 (Medium): 영속화 부재 → in-memory repo 한정 구동. 대응: 이번 batch 목표를 "도메인 코어 시드 + 테스트 green"으로 한정, 영속화는 후속 Discovery. -- R-2 (Medium): 컬럼 계약 드리프트. 대응: REQ-003 계약 테스트(`EXPECTED_SLOT_FIELDS`)로 자동 강제, DB-side 검사 인계. -- R-3 (Low): trex-orbit·trex-core 이중 진실. 대응: 의도된 임시 비용으로 수용, 수렴은 별도 Discovery. -- R-4 (Low): import 경로 치환 누락(`app.domain.slot` 잔존). 대응: 이식 후 grep 으로 잔존 import 0 확인(REQ-002 AC). diff --git a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/verification.md b/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/verification.md deleted file mode 100644 index 379875d..0000000 --- a/docs/batches/bat-001_20260603_trex-core-slot-domain-seed/verification.md +++ /dev/null @@ -1,46 +0,0 @@ -# BAT-001 Verification - -- Status: confirmed -- Batch: BAT-001 - -## Acceptance Mapping -- REQ-001 (Installation): - - AC1 설치/import → `pip install -e .[dev]` 성공 + `import trex_core` OK (.venv, Python 3.14.4 ≥ 3.12). **PASS** - - AC2 pytest 수집 → `pytest -q` 56 passed (수집/실행 정상). **PASS** - - AC3 pyproject 메타 → name=`trex-core`, requires-python>=3.12, setuptools, dev extras=pytest·ruff 확인. **PASS** - - AC4 ruff 동작 → `ruff check .` All checks passed. **PASS** -- REQ-002 (Interface): - - AC1 공개 API import → 15개 심볼(SlotState…EXPECTED_SLOT_FIELDS) import 성공. **PASS** - - AC2 외부 의존 0 → import 스윕 결과 stdlib + `trex_core.domain.slot` 외 import 없음. **PASS** - - AC3 인프라 심볼 비노출 → `__all__` 에 PostgreSQL/schema 심볼 0. **PASS** - - AC4 도메인 불변식 보존 → test_state(전이 14종) + test_service(advisory lock 직렬화·closed→idle 재진입+1·race 감지) 통과. **PASS** -- REQ-003 (Testing): - - AC1 이식 테스트 green → test_state + test_service 통과(56 passed 에 포함). **PASS** - - AC2 계약 테스트 green → test_contract `fields(Slot)==EXPECTED_SLOT_FIELDS` 통과. **PASS** - - AC3 필드 변경 시 실패 재현 → rogue 필드 추가 시 계약 일치 False 확인(live negative case). **PASS** - - AC4 타입 단언 배제 → test_contract 는 이름/존재만 단언, 타입 매핑은 부록 A/data-model 문서. **PASS** -- REQ-004 (Documentation): - - AC1 project-structure 정합 → `(planned)` 제거, 실제 `trex_core/domain/slot` 트리·모듈 책임 반영. **PASS** - - AC2 runtime-flows 정합 → preallocate/transit 흐름이 실제 코드 기반임을 Change Log 반영, 영속화 미구현 표기 유지(정확). **PASS** - - AC3 네임스페이스 일관 → `docs/project-structure.md`·`docs/runtime-flows.md` 에 `app/` 잔존 0(Change Log 정정 이력 1건 제외). **PASS** - -## Evidence -- Test: - - `pytest -q` → `56 passed in 0.06s` (test_state / test_service / test_contract). - - 계약 음성 케이스: `dataclasses.make_dataclass` 로 rogue 필드 추가 → 계약 일치 단언 False. -- Manual: - - import 스윕 grep: `trex_core/domain/slot/*.py` 외부/인프라 import 0. - - `__all__` 점검: 인프라 심볼 0. - - baseline 문서 `app/` 잔존 grep: 정정 Change Log 1건만 매칭. -- Logs: - - run-batch-implementation 단계 실행 로그(implementation.md Validation 절) 참조. - -## Baseline Sync Evidence (Architecture Impact = both) -- docs/project-structure.md: planned 트리·모듈 책임을 실제 `trex_core/` 구조로 갱신 + Change Log 기록(REQ-004). -- docs/runtime-flows.md: 도메인 흐름이 실제 코드 기반임을 반영 + Change Log 기록(REQ-004). -- design.md `Reference Doc Update Plan` 의 두 항목 모두 반영 완료. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음 (모든 포함 REQ AC 가 evidence 와 연결됨. 잔여 리스크 R-1/R-2 는 후속 영속화 반복 인계 사항으로 비차단) diff --git a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/design.md b/docs/batches/bat-002_20260603_slot-core-daemon-http-api/design.md deleted file mode 100644 index 686d7c3..0000000 --- a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/design.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-002 Design - -- Status: confirmed -- Batch: BAT-002 - -## Architecture Summary -- onion 3-레이어로 구성한다. **domain**(`trex_core.domain.slot`, BAT-001, 무의존) ← **application**(intent admission/슬롯 전이 유스케이스, SlotService 호출) ← **adapters**(FastAPI HTTP 입구 + psycopg PostgreSQL SlotRepository) + **runtime**(데몬 엔트리포인트/uvicorn/lifecycle). 코어 데몬이 `daily_slot` 단일 writer + intent_log 소유. 전략 데몬은 HTTP 클라이언트. - -## Changed Areas -- Docs: docs/interface-contract.md(/v1 API), docs/runtime-flows.md(데몬·요청 흐름), docs/project-structure.md(레이어), docs/data-model.md(intent_log + 영속 테이블) — REQ-009 -- Modules: - - runtime: 데몬 엔트리포인트(console_scripts), uvicorn 기동, lifecycle(startup: preallocate / shutdown: graceful), `/v1/healthz` - - adapters/api: FastAPI 앱 + `/v1` 라우터(intents buy/sell/{id}, slots, capacity) + pydantic 요청/응답 스키마 - - adapters/persistence: psycopg `SlotRepository` 구현 + 마이그레이션 SQL - - application: intent 유스케이스(admission → SlotService.transit_slot + intent_log 기록, intent_id 멱등) -- Interfaces: `/v1` HTTP 6종(dcy-002 OQ-3) - -## Key Decisions -1. 동기 admission+커밋: HTTP 핸들러가 단일 트랜잭션(advisory lock)에서 admission→슬롯 전이→intent_log 기록을 커밋하고 admit/reject 동기 응답(dcy-002 OQ-1). 완전 비동기 outbox 는 브로커 Discovery 로 Deferred. -2. 멱등: `intent_log.intent_id` UNIQUE + 수락 전 dedup 조회. 재전송 시 최초 decision 재응답. `GET /v1/intents/{id}` 로 재조회. -3. 단일 writer(불변식 A): `daily_slot` 쓰기는 application 의 SlotService 경로뿐. read API(REQ-008)는 쓰기 없음. 동시 intent 는 `pg_advisory_xact_lock(SLOT_ADVISORY_LOCK_ID)` 로 직렬화. -4. 스키마(dcy-002 OQ-4): 부록 A 단일 기준 trex-core 소유 마이그레이션. 테이블 = daily_slot/daily_symbol_entries/slot_transition_log/intent_log. archive/prune 제외. orbit-v2 DDL 은 참조. -5. 도메인 순수성(NFR-1): FastAPI/psycopg 의존은 adapters/runtime 에만. `trex_core.domain.slot` 은 import 스윕 0 유지(BAT-001 계약 테스트 패턴 확장). - -## Edge Cases -- 동일 intent_id 재전송(네트워크 재시도) → 슬롯 중복 전이 없이 최초 decision 반환. -- 동시 buy intent 가 같은 idle 슬롯/마지막 슬롯 경합 → advisory lock 직렬화, 한쪽은 admit 한쪽은 reject(슬롯 소진). -- sell intent 의 slot_id 가 이미 holding 이 아님(이미 전이됨) → reject + 사유. -- PostgreSQL 미가용(검증 환경) → 통합테스트 evidence blocker 로 분리(코드/단위는 별도 통과). -- preallocate 재기동 → 멱등(이미 있으면 0). - -## Architecture Impact -- both (project-structure + runtime-flows). interface-contract/data-model 도 신규 계약/테이블로 갱신. - -## Reference Doc Update Plan -- project-structure.md: domain/application/adapters/runtime 레이어와 신규 모듈 트리 반영(REQ-009). -- runtime-flows.md: 데몬 lifecycle 흐름 + intent 요청 흐름(admission→전이→intent_log) 추가(REQ-009). -- interface-contract.md: `/v1` 6종 입력/출력/에러/멱등 계약 추가(REQ-009). -- data-model.md: intent_log 엔티티 + 영속 테이블 계약 반영(REQ-009). diff --git a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/implementation.md b/docs/batches/bat-002_20260603_slot-core-daemon-http-api/implementation.md deleted file mode 100644 index c2d58e5..0000000 --- a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/implementation.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-002 Implementation - -- Status: completed -- Batch: BAT-002 - -## Plan Summary -- REQ-005 런타임 → REQ-006 영속화 → REQ-007 intent API → REQ-008 조회 API → REQ-009 baseline 정합을 순차 구현. onion 4-레이어(domain/application/adapters/runtime). 도메인(BAT-001)은 무수정 재사용. - -## Changed Files -- pyproject.toml: fastapi/uvicorn/psycopg 의존성 + dev httpx + console_scripts `trex-core-daemon`. -- application: trex_core/application/{ports.py(IntentStore), intents.py(IntentService/BuyIntent/SellIntent/IntentDecision)} -- adapters/persistence: postgresql.py(PostgreSQLSlotRepository + PostgreSQLIntentStore), in_memory_intent_store.py, migrate.py, sql/001_create_slot_core_tables.sql -- adapters/api: schemas.py(pydantic), app.py(create_app, /v1 라우터) -- runtime: config.py(DaemonConfig), daemon.py(build_app + lifespan migrate/preallocate + uvicorn main) -- tests: tests/adapters/api/test_intent_api.py, tests/application/test_intent_concurrency.py, tests/adapters/persistence/test_postgresql_repository.py, tests/adapters/persistence/test_migration_and_drift.py, tests/conftest.py -- docs(REQ-009): interface-contract.md, runtime-flows.md, project-structure.md, data-model.md - -## Execution Log -1. SQL 마이그레이션(daily_slot[부록 A 16컬럼]/daily_symbol_entries/slot_transition_log/intent_log) + migrate 러너. -2. PostgreSQL SlotRepository(연결-당-호출, pg_advisory_xact_lock) + PostgreSQLIntentStore(intent_id UNIQUE dedup gate). -3. IntentService: dedup(try_insert) → admission(find idle / fetch holding) → SlotService.transit_slot(advisory lock) → finalize intent_log. last-idle 경합은 SlotStateMismatchError 재시도. -4. FastAPI create_app(/v1 healthz·intents/buy·sell·{id}·slots·capacity), DI 로 in-memory/PG 양쪽 구동. -5. 데몬 엔트리포인트: lifespan 에서 migrate + 거래일 preallocate(공개 엔드포인트 아님). -6. 테스트 작성 + 로컬 PostgreSQL 18 기동 DB(trex_core_test)로 통합 실행. - -## Validation -- `pytest`: **74 passed** (PostgreSQL DSN 설정 시) / **66 passed + 8 skipped** (DSN 미설정 시 PG 통합 graceful skip). -- `ruff check .`: **All checks passed**. -- 도메인 순수성(NFR-1): `trex_core/domain/` 에 fastapi/uvicorn/psycopg/starlette import **0**(grep 확인). -- 멱등(NFR-3): API/PG 양쪽에서 동일 intent_id 재전송 시 동일 decision·슬롯 중복 전이 없음(테스트 + 데몬 스모크). -- 단일 writer(NFR-2): 20-thread 동시 buy → 정확히 idle 수(5)만 admit, 슬롯 중복 사용 0. -- 드리프트(REQ-006): `EXPECTED_SLOT_FIELDS` ↔ `daily_slot` information_schema 일치 + 음성 케이스 검출. -- 데몬 E2E 스모크: lifespan migrate+preallocate(5 idle) → buy admit→pending_buy → 멱등 resubmit 동일 slot_id → capacity 갱신 → intent lookup, PostgreSQL 영속 확인. - -## Remaining Risks -- intent 비원자성 미세 창: dedup insert 와 transit 가 별 트랜잭션이라, 둘 사이 크래시 시 intent_log 가 'processing' 으로 남을 수 있음(슬롯 중복 전이는 UNIQUE gate 로 차단). 완전 원자/outbox 는 브로커 Discovery 인계. -- admission 은 슬라이스 1 기준 슬롯 가용성 중심(orbit REQ-028 다중 게이트는 범위 밖). -- 연결-당-호출 repo 는 고부하 시 풀링 필요(후속 최적화). diff --git a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/index.md b/docs/batches/bat-002_20260603_slot-core-daemon-http-api/index.md deleted file mode 100644 index af095ab..0000000 --- a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-002: trex-core 슬롯 코어 데몬화 + HTTP intent API (슬라이스 1) - -- Status: released -- Profile: standard -- Source Discovery: dcy-002 -- Included REQ: REQ-005, REQ-006, REQ-007, REQ-008, REQ-009 -- Excluded REQ: 없음 - -## Summary -- dcy-002 의 슬라이스 1(데몬 + HTTP API + PostgreSQL 영속화 + 멱등 intent)을 하나의 delivery 단위로 묶는다. BAT-001 의 순수 도메인을 안쪽 코어로 두고 그 바깥에 런타임(REQ-005)·영속화(REQ-006)·API(REQ-007/008)·문서정합(REQ-009)을 얹는다. - -## Scope -- In Scope: - - REQ-005: 데몬 런타임 + lifecycle(기동/정상종료/`GET /v1/healthz`) + 거래일 preallocate(lifecycle 내부) + FastAPI/uvicorn/psycopg 의존성·entrypoint. - - REQ-006: PostgreSQL `SlotRepository` + 신규 마이그레이션(daily_slot/daily_symbol_entries/slot_transition_log/intent_log) + 드리프트 테스트. - - REQ-007: `POST /v1/intents/buy|sell` + 동기 admission/전이 + intent_id 멱등 + `intent_log` + `GET /v1/intents/{id}`. - - REQ-008: `GET /v1/slots?status=holding` + `GET /v1/capacity` (read-only). - - REQ-009: interface-contract/runtime-flows/project-structure/data-model 정합. -- Out of Scope (후속 Discovery): - - 브로커 단일 세션/주문 집행(불변식 B), safety floor(forced_close/EOD/손절 backstop), 완전 비동기 outbox, 매수·매도 전략 데몬, 인증/인가 고도화·수평 확장. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery 는 dcy-002 단일. 모든 REQ 가 dcy-002 FR-1~FR-6 에서 분해됨. -- 확정 설계(dcy-002 OQ): 동기 커밋+intent_log(OQ-1) / FastAPI+uvicorn(OQ-2) / `/v1` 엔드포인트 6종(OQ-3) / 부록 A 기준 신규 스키마(OQ-4). -- 핵심 리스크: 실제 PostgreSQL·HTTP·신규 의존 도입(검증 환경 필요), 도메인 순수성 유지(어댑터 격리), 단일 writer 불변식. -- 검증 환경: PostgreSQL 인스턴스 필요(통합테스트). 미가용 시 일부 evidence 가 blocker 로 남을 수 있음(verification 에서 분리 판단). diff --git a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/planning.md b/docs/batches/bat-002_20260603_slot-core-daemon-http-api/planning.md deleted file mode 100644 index cd43884..0000000 --- a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-002 Planning - -- Status: confirmed -- Batch: BAT-002 -- Profile: standard - -## Included REQ -- REQ-005 (Installation): 슬롯 코어 데몬 런타임 부트스트랩 -- REQ-006 (Data): PostgreSQL SlotRepository 영속화 + 스키마 -- REQ-007 (Interface): intent 제출 HTTP API + 멱등 -- REQ-008 (Interface): 슬롯 read-only 조회 HTTP API -- REQ-009 (Documentation): 데몬/API/영속화 baseline 정합 - -## Delivery Plan -- 의존 순서대로 구현한다(기반 → API → 문서). - 1. REQ-005: pyproject 에 FastAPI/uvicorn/psycopg + console_scripts; 데몬 lifecycle(기동/shutdown/`/v1/healthz`); preallocate 를 lifecycle 내부로. - 2. REQ-006: psycopg 기반 `SlotRepository` 구현 + trex-core 소유 마이그레이션(daily_slot[부록 A 16컬럼]/daily_symbol_entries/slot_transition_log/intent_log) + `pg_advisory_xact_lock` + 드리프트 테스트. - 3. REQ-007: FastAPI 라우터 `POST /v1/intents/buy|sell` → 애플리케이션 서비스가 `SlotService` 로 admission/전이(advisory lock 직렬화) + intent_log 동기 기록 + intent_id 멱등 + `GET /v1/intents/{id}`. - 4. REQ-008: `GET /v1/slots?status=holding` + `GET /v1/capacity` read-only. - 5. REQ-009: baseline 4문서 정합. - -## Dependencies -- Internal: - - REQ-006·REQ-007·REQ-008 은 REQ-005(런타임 기반) 선행. - - REQ-007·REQ-008 은 REQ-006(영속화, 특히 intent_log) 선행. - - REQ-009 는 REQ-005~008 구현 완료 후. - - 안쪽 코어는 BAT-001 의 `trex_core.domain.slot`(완성) 재사용. -- External: - - PostgreSQL 인스턴스(통합테스트/런타임). psycopg, FastAPI, uvicorn(dev/runtime 의존). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 3개 레이어(데몬 런타임 + FastAPI API + psycopg 영속화)와 onion 경계(domain/application/adapters), 단일 writer 강제 지점, intent_log/멱등 트랜잭션 경계를 구현 전에 고정해야 한다. 구조/인터페이스/런타임 영향이 모두 있어 design.md 를 같은 턴에 작성한다. - -## Milestones -1. M1 — REQ-005: 데몬 기동 + `/v1/healthz` 200 + graceful shutdown. -2. M2 — REQ-006: 마이그레이션 적용 + PostgreSQL repo 로 도메인 테스트 통과 + 드리프트 테스트 green. -3. M3 — REQ-007: buy/sell intent 전이 + 멱등 재전송 + 동시성 단일 writer 통합테스트. -4. M4 — REQ-008: holding 스냅샷 + capacity 조회. -5. M5 — REQ-009: baseline 4문서 정합. - -## Risks -- R-1 (Medium-High): 실제 PostgreSQL/HTTP/신규 의존 도입 — 검증 환경(PG 인스턴스) 필요. 대응: 통합테스트용 PG 준비; 미가용 시 해당 evidence 를 verification 의 blocker/missing-evidence 로 분리(코드/단위는 별도 통과). -- R-2 (Medium): 도메인 순수성 오염(웹/psycopg). 대응: domain/application/adapters 레이어 분리 + import 스윕 가드(NFR-1) — `trex_core.domain.slot` import 0 유지. -- R-3 (Medium): 단일 writer 불변식 붕괴(API 핸들러 경유 다중 writer). 대응: 모든 쓰기를 `SlotService`+advisory lock 단일 경로로 강제, 동시성 통합테스트로 검증(NFR-2). -- R-4 (Medium): intent 멱등/내구성 결함. 대응: intent_id dedup + intent_log 동기 커밋 + 재전송/재조회 테스트(NFR-3). -- R-5 (Low): 컬럼 계약 드리프트. 대응: `EXPECTED_SLOT_FIELDS`↔information_schema 드리프트 테스트(REQ-006). diff --git a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/verification.md b/docs/batches/bat-002_20260603_slot-core-daemon-http-api/verification.md deleted file mode 100644 index b1903df..0000000 --- a/docs/batches/bat-002_20260603_slot-core-daemon-http-api/verification.md +++ /dev/null @@ -1,53 +0,0 @@ -# BAT-002 Verification - -- Status: confirmed -- Batch: BAT-002 - -## Acceptance Mapping -- REQ-005 (daemon runtime): - - AC1 기동/`/v1/healthz` 200 → 데몬 E2E 스모크(TestClient lifespan) healthz `{status:ok}`. **PASS** - - AC2 graceful shutdown → lifespan 컨텍스트 정상 종료(연결-당-호출, 풀 없음). **PASS** - - AC3 lifecycle preallocate(멱등, 비공개) → lifespan 에서 preallocate 5 idle 확인, 재기동 멱등. **PASS** - - AC4 deps/entrypoint → pyproject 에 fastapi/uvicorn/psycopg + console_scripts `trex-core-daemon` 설치 확인. **PASS** -- REQ-006 (PostgreSQL persistence): - - AC1 마이그레이션·컬럼 부록 A 일치 → `test_migrations_create_expected_tables` + `test_daily_slot_columns_match_expected_contract`. **PASS** - - AC2 PG repo 로 도메인 테스트 통과 → `test_postgresql_repository.py`(preallocate/전이/재진입+1). **PASS** - - AC3 드리프트 테스트 → EXPECTED_SLOT_FIELDS↔information_schema 일치 + 음성 케이스(`test_drift_detected_when_contract_extended`). **PASS** - - AC4 advisory lock → `pg_advisory_xact_lock` 구현 + 동시성 테스트로 직렬화 확인. **PASS** -- REQ-007 (intent API + 멱등): - - AC1 buy 전이 → `test_buy_intent_admits_and_transitions` + PG `test_buy_intent_persists_transition`. **PASS** - - AC2 sell 전이/거부 → `test_sell_intent_admits_from_holding` / `test_sell_intent_requires_holding`. **PASS** - - AC3 멱등 재전송(NFR-3) → `test_buy_intent_idempotent_resubmit`(API) + `test_buy_intent_idempotent_on_pg`(PG) + 데몬 스모크(동일 slot_id). **PASS** - - AC4 동시성 단일 writer(NFR-2) → `test_concurrent_buys_admit_exactly_idle_count`(20스레드→정확히 5 admit) + `test_duplicate_intent_id_admits_once_under_concurrency`. **PASS** - - AC5 intent 재조회 → `test_get_intent_roundtrip`(`GET /v1/intents/{id}`). **PASS** -- REQ-008 (read-only API): - - AC1/2 holding 스냅샷·필터 → `test_list_slots_and_capacity`(status/trading_date 필터). **PASS** - - AC3 capacity → 동 테스트(idle/holding/pending + scarcity_ratio). **PASS** -- REQ-009 (baseline 정합): - - AC1 interface-contract /v1 계약 → docs/interface-contract.md 에 6 엔드포인트 + 불변식 추가. **PASS** - - AC2 runtime-flows/project-structure 레이어 → 데몬/intent 흐름 + application/adapters/runtime 레이어 반영. **PASS** - - AC3 data-model intent_log → docs/data-model.md 에 intent_log 엔티티 + PostgreSQL 백엔드 반영. **PASS** - -## Evidence -- Test: - - `pytest`: **74 passed** (TREX_POSTGRESQL_TEST_DSN=trex_core_test) / **66 passed + 8 skipped** (DSN 미설정 시 PG graceful skip). - - 동시성: 20-thread buy → 5 admit / 15 reject, pending=5·idle=0. - - 드리프트: EXPECTED_SLOT_FIELDS == daily_slot 컬럼; 음성 케이스 검출. -- Manual: - - 도메인 import 스윕: `trex_core/domain/` 에 fastapi/uvicorn/psycopg/starlette import 0(NFR-1). - - 데몬 E2E 스모크(PostgreSQL 18): healthz→capacity(5 idle)→buy admit(pending_buy)→멱등 resubmit 동일 slot_id→capacity(4 idle/1 pending)→intent lookup; `daily_slot`·`intent_log` 영속 확인. - - `ruff check .`: All checks passed. -- Logs: - - implementation.md Validation 절 + 본 batch 작업 로그. - -## Baseline Sync Evidence (Architecture Impact = both) -- interface-contract.md: HTTP `/v1` 6 엔드포인트 계약 + 멱등/단일writer 불변식 추가(REQ-009). -- runtime-flows.md: 데몬 lifecycle + 매수/매도 intent 흐름 추가, Primary Runtime=daemon+library. -- project-structure.md: application/adapters/runtime 레이어 + entrypoint 반영. -- data-model.md: PostgreSQL 백엔드 + intent_log 엔티티 반영. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 모든 포함 REQ AC 가 evidence 와 연결됨(로컬 PostgreSQL 18 로 통합 evidence 확보). - - 비차단 잔여: intent dedup/transit 비원자성 미세 창(슬롯 중복 전이는 UNIQUE gate 로 차단), 연결 풀링 미도입 — 후속 인계. diff --git a/docs/batches/bat-003_20260604_daemon-dotenv-config/implementation.md b/docs/batches/bat-003_20260604_daemon-dotenv-config/implementation.md deleted file mode 100644 index da3e49c..0000000 --- a/docs/batches/bat-003_20260604_daemon-dotenv-config/implementation.md +++ /dev/null @@ -1,28 +0,0 @@ -# BAT-003 Implementation - -- Status: completed -- Batch: BAT-003 - -## Plan Summary -- REQ-010: 데몬 기동 시 `.env` best-effort 로드(OS env 우선). minor-change batch-lite. - -## Changed Files -- pyproject.toml: `python-dotenv>=1.0` 의존성 추가. -- trex_core/runtime/config.py: `_load_dotenv()`(best-effort, `override=False`, `find_dotenv(usecwd=True)`) 추가 + `from_env()` 시작부 호출. -- .gitignore: `/.env` 추가. -- .env.example: 키 목록 참고용 신규(추적 대상). -- tests/runtime/test_config_dotenv.py: 로드/우선순위/부재/필수누락 4 테스트. - -## Execution Log -1. config 에 best-effort dotenv 로더 추가(미설치/부재 시 no-op). -2. pyproject 의존성 + .gitignore + .env.example. -3. 단위테스트 작성, `pip install -e .` 후 실행. - -## Validation -- `pytest tests/runtime/test_config_dotenv.py`: 4 passed. -- 전체 `pytest`: **78 passed** (기존 74 + 신규 4), ruff clean. -- gitignore: `.env` IGNORED, `.env.example` trackable. -- 스모크: `./.env` 만으로 `DaemonConfig.from_env()` 가 dsn/total/port 로드(OS env 미export) 확인. - -## Remaining Risks -- 없음(저위험). dotenv 미설치/.env 부재 시 graceful no-op로 회귀 없음(AC-3 테스트). diff --git a/docs/batches/bat-003_20260604_daemon-dotenv-config/index.md b/docs/batches/bat-003_20260604_daemon-dotenv-config/index.md deleted file mode 100644 index c675b4e..0000000 --- a/docs/batches/bat-003_20260604_daemon-dotenv-config/index.md +++ /dev/null @@ -1,28 +0,0 @@ -# BAT-003: 데몬 설정 .env 로딩 (minor-change) - -- Status: released -- Profile: batch-lite -- Source Discovery: dcy-002 (REQ-010 은 데몬 설정 편의 개선) -- Included REQ: REQ-010 -- Excluded REQ: 없음 - -## Summary -- REQ-010 단일 저위험 Configuration 요구사항을 minor-change fast path 로 처리한다. 데몬 기동 시 `.env` 를 로드(실 OS env 우선)하고, `.env` 는 gitignore, `.env.example` 제공. - -## Scope -- In Scope: - - python-dotenv 의존성 추가, `DaemonConfig.from_env()` 에 `.env` best-effort 로드. - - `.gitignore` 에 `.env` 추가, `.env.example` 신규. - - 설정 로드/우선순위/부재 회귀 단위테스트. -- Out of Scope: - - 다른 설정 방식(TOML/CLI/시크릿 매니저) — 본 변경과 무관. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite, no architecture impact) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- minor-change: 구조/인터페이스/런타임 흐름 변경 없음(기동 시 env 소스 추가뿐). design 불요. -- dotenv 미설치/.env 부재 시 graceful no-op → 회귀 없음. diff --git a/docs/batches/bat-003_20260604_daemon-dotenv-config/planning.md b/docs/batches/bat-003_20260604_daemon-dotenv-config/planning.md deleted file mode 100644 index 51030bf..0000000 --- a/docs/batches/bat-003_20260604_daemon-dotenv-config/planning.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-003 Planning - -- Status: confirmed -- Batch: BAT-003 -- Profile: batch-lite - -## Included REQ -- REQ-010 (Configuration): 데몬 설정 .env 파일 로딩 - -## Delivery Plan -1. pyproject.toml 에 `python-dotenv` 의존성 추가. -2. `trex_core/runtime/config.py` 의 `DaemonConfig.from_env()` 시작부에서 best-effort `load_dotenv()`(미설치/부재 시 no-op, OS env override 안 함). -3. `.gitignore` 에 `.env` 추가, `.env.example` 신규(키 목록 참고용). -4. `tests/runtime/test_config_dotenv.py`: .env 로드 / OS env 우선 / .env 부재 회귀. - -## Dependencies -- Internal: REQ-005(DaemonConfig) 위 확장. -- External: python-dotenv. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 구조/인터페이스/런타임 흐름 변경 없음. 기동 시 환경변수 소스(.env)만 추가하는 국소 설정 변경이라 batch-lite + design 생략(minor-change). - -## Milestones -1. M1 — config 에 .env 로딩 + deps + gitignore/.env.example. -2. M2 — 단위테스트 green(로드/우선순위/부재). - -## Risks -- R-1 (Low): .env 가 실 OS env 를 덮어쓰면 운영 사고. 대응: `load_dotenv(override=False)` 기본(실 env 우선)으로 AC-2 검증. -- R-2 (Low): dotenv 미설치 환경 회귀. 대응: best-effort import(no-op) + AC-3 회귀 테스트. diff --git a/docs/batches/bat-003_20260604_daemon-dotenv-config/verification.md b/docs/batches/bat-003_20260604_daemon-dotenv-config/verification.md deleted file mode 100644 index 313f4a2..0000000 --- a/docs/batches/bat-003_20260604_daemon-dotenv-config/verification.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-003 Verification - -- Status: confirmed -- Batch: BAT-003 - -## Acceptance Mapping -- REQ-010: - - AC1 .env 로드 → `test_loads_values_from_dotenv` + 스모크(dsn/total/port). **PASS** - - AC2 OS env 우선 → `test_os_env_takes_precedence_over_dotenv`(override=False). **PASS** - - AC3 .env 부재 회귀 → `test_no_dotenv_uses_os_env_only` + `test_missing_dsn_raises`. **PASS** - - AC4 gitignore/.env.example → `git check-ignore`: `.env` ignored, `.env.example` trackable. **PASS** - -## Evidence -- Test: `pytest tests/runtime/test_config_dotenv.py` 4 passed; 전체 78 passed; ruff clean. -- Manual: `git check-ignore .env` → ignored, `.env.example` → trackable. `./.env` 단독 로드 스모크. - -## no architecture impact confirmed -- batch-lite, planning Design Gate=no. 구조/인터페이스/런타임 흐름 변경 없음(기동 시 env 소스 추가뿐). baseline 문서 영향 없음 → 동기화 evidence 불요. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. diff --git a/docs/batches/bat-004_20260604_broker-execution-and-outbox/design.md b/docs/batches/bat-004_20260604_broker-execution-and-outbox/design.md deleted file mode 100644 index b042da1..0000000 --- a/docs/batches/bat-004_20260604_broker-execution-and-outbox/design.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-004 Design - -- Status: confirmed -- Batch: BAT-004 - -## Architecture Summary -- BAT-002 onion(domain/application/adapters/runtime) 위에 집행을 추가한다. **application** 에 `BrokerPort`(submit/cancel/poll_fill)와 execution 유스케이스, **adapters/broker** 에 paper/stub + KIS 어댑터, **runtime** 에 백그라운드 execution 루프(데몬 lifecycle 에서 기동/종료). 코어가 단일 브로커 세션(불변식 B) + 단일 슬롯 writer(불변식 A)를 모두 소유. intent API 는 pending 전이까지만, 집행은 루프가 비동기. - -## Changed Areas -- Docs: runtime-flows(집행/체결/reconcile 흐름), project-structure(broker/execution 레이어), interface-contract(BrokerPort 경계), data-model(broker_order_id=실행큐/멱등) — REQ-015 -- Modules: - - application: `BrokerPort`+`FillStatus`, execution 유스케이스(claim/dispatch/poll/transition/reconcile) - - adapters/broker: paper(결정적 async 더블), kis(KIS Open API) - - runtime: execution 루프(lifespan 백그라운드), config(KIS 자격증명/base URL/게이팅/폴링 주기) -- Interfaces: `BrokerPort`(내부 포트) — 외부 HTTP API(/v1)는 불변(집행은 백그라운드) - -## Key Decisions -1. 옵션 A 실행큐(OQ-3): pending 슬롯 + `broker_order_id IS NULL` 을 `FOR UPDATE SKIP LOCKED` 로 claim, 송신 후 broker_order_id 기록(멱등 가드). 별도 outbox 테이블 없음. -2. 통일 비동기 체결(OQ-2): `submit_order→accepted`, `poll_fill→filled/partial/rejected`. paper 도 동기 단축 없이 같은 계약(결정적 더블), KIS 는 체결조회 폴링. -3. 별도 execution 루프(OQ-4): 데몬 프로세스 내 백그라운드(claim→submit→poll→transition→reconcile). 단일 인스턴스=단일 writer/세션. -4. 단일 writer(불변식 A): 집행 루프도 슬롯 전이는 SlotService(advisory lock)로만. 불변식 B: 주문/취소는 BrokerPort 단일 세션만. -5. KIS 안전(R-3): 기본 모의투자, 실계좌는 config 게이팅. 자격증명 .env(REQ-010 패턴). 도메인 순수성(NFR-2): broker/HTTP 의존은 adapters/runtime 에만. - -## Edge Cases -- 송신 후 broker_order_id 기록 전 크래시 → reconciliation 으로 브로커 상태 대조(중복 주문 방지). -- 부분체결 → 슬라이스는 완전체결 기준 전이(부분체결 누적/분할주문 본격화는 옵션 B 승격 후속). -- KIS 토큰 만료/레이트리밋 → 어댑터 내 갱신/백오프, 실패 시 슬롯 전이 안 함(다음 틱 재시도). -- 거부/취소 → pending_buy→idle 롤백(점유 초기화), pending_sell→holding 복귀(매도 거부). -- KIS 자격증명 미가용 → 통합테스트 skip, paper 경로는 완전 동작. - -## Architecture Impact -- both (project-structure + runtime-flows). interface-contract(BrokerPort)·data-model(broker_order_id 의미) 도 갱신. - -## Reference Doc Update Plan -- runtime-flows.md: 집행 루프(claim→submit→poll→transition)·reconcile·KIS 흐름 추가(REQ-015). -- project-structure.md: application(BrokerPort/execution)·adapters/broker(paper,kis)·runtime(execution 루프) 레이어 반영. -- interface-contract.md: `BrokerPort` 내부 포트 계약(submit/cancel/poll_fill) + KIS 어댑터 노트. -- data-model.md: `broker_order_id` 를 실행큐 claim 술어 + 멱등 가드로 쓰는 의미 명시. diff --git a/docs/batches/bat-004_20260604_broker-execution-and-outbox/implementation.md b/docs/batches/bat-004_20260604_broker-execution-and-outbox/implementation.md deleted file mode 100644 index 3156ce1..0000000 --- a/docs/batches/bat-004_20260604_broker-execution-and-outbox/implementation.md +++ /dev/null @@ -1,26 +0,0 @@ -# BAT-004 Implementation - -- Status: draft -- Batch: BAT-004 - -## Plan Summary -- run-batch-implementation 단계에서 채운다. 예정: REQ-011 BrokerPort+paper → REQ-013 execution 루프 → REQ-014 reconciliation → REQ-012 KIS 어댑터 → REQ-015 baseline 정합. - -## Changed Files -- (구현 시 기록) 예정: - - trex_core/application/broker_port.py (BrokerPort, FillStatus) - - trex_core/application/execution.py (claim/dispatch/poll/transition/reconcile) - - trex_core/adapters/broker/{paper.py, kis.py} - - trex_core/runtime/(execution 루프 lifespan 통합), config.py(KIS 자격증명/게이팅/폴링 주기) - - pyproject.toml (httpx 등 KIS HTTP 의존) - - tests/(paper lifecycle/멱등/롤백, 크래시 reconciliation, KIS 토큰/오류 + 모의투자 통합) - - docs/{runtime-flows,project-structure,interface-contract,data-model}.md (REQ-015) - -## Execution Log -1. (구현 시 기록) - -## Validation -- (구현 시 기록) 예정: `pytest`(paper lifecycle/멱등/롤백/reconciliation, KIS 단위) + KIS 모의투자 통합(가용 시) + `ruff` + import 스윕(도메인 broker 의존 0). - -## Remaining Risks -- (구현 시 갱신) planning R-1~R-5 참조. 특히 R-3(KIS 실 API)·R-5(자격증명 가용성). diff --git a/docs/batches/bat-004_20260604_broker-execution-and-outbox/index.md b/docs/batches/bat-004_20260604_broker-execution-and-outbox/index.md deleted file mode 100644 index 5a1d85b..0000000 --- a/docs/batches/bat-004_20260604_broker-execution-and-outbox/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# BAT-004: 브로커 집행 + KIS 어댑터 + durable 핸드오프 - -- Status: released -- Profile: standard -- Source Discovery: dcy-003 -- Included REQ: REQ-011, REQ-012, REQ-013, REQ-014, REQ-015 -- Excluded REQ: 없음 - -## Summary -- dcy-003 슬라이스(브로커 단일 세션·주문 집행·비동기 체결·durable 핸드오프)를 하나의 delivery 단위로 묶는다. BAT-002 의 슬롯 코어 데몬 위에 BrokerPort + paper/KIS 어댑터 + 백그라운드 execution 루프 + reconciliation 을 얹어 intent→실제 주문을 잇는다. - -## Scope -- In Scope: - - REQ-011: `BrokerPort`(submit/cancel/poll_fill) + paper/stub async 더블(불변식 B 경계). - - REQ-012: 실제 KIS Open API 어댑터(토큰/레이트리밋/모의투자 검증, 실계좌 게이팅). - - REQ-013: 백그라운드 execution 루프 — pending claim(옵션 A) → 주문 송신 → poll 체결 → holding/closed 전이 + 거부 롤백(idle). - - REQ-014: durable 핸드오프 + 재기동 reconciliation(broker_order_id 멱등, 중복 주문 0). - - REQ-015: runtime-flows/project-structure/interface-contract/data-model 정합. -- Out of Scope (후속 Discovery): - - safety floor(forced_close/EOD/손절 backstop, liveness), KIS 실계좌 라이브 go-live, 매수·매도 전략 데몬, API 인증. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery dcy-003 단일. 확정 결정: KIS 어댑터까지(OQ-1) / 통일 비동기 체결(OQ-2) / 옵션 A 실행큐(OQ-3) / 별도 execution 루프(OQ-4). -- 핵심 리스크: 실거래(R-1 중복주문/R-2 KIS 비동기체결/R-5 KIS 실 API) — paper 더블 + KIS 모의투자 + reconciliation + 실계좌 게이팅으로 완화(dcy-003 confirm 수용). -- 검증 환경: KIS 모의투자 자격증명(.env, REQ-010 패턴) 가용 시 REQ-012 통합 검증, 미가용 시 skip 하고 paper 기반(REQ-011/013/014) 완전 검증 — verification 에서 분리 판단. diff --git a/docs/batches/bat-004_20260604_broker-execution-and-outbox/planning.md b/docs/batches/bat-004_20260604_broker-execution-and-outbox/planning.md deleted file mode 100644 index ad61e08..0000000 --- a/docs/batches/bat-004_20260604_broker-execution-and-outbox/planning.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-004 Planning - -- Status: confirmed -- Batch: BAT-004 -- Profile: standard - -## Included REQ -- REQ-011 (Integration): BrokerPort + paper/stub async 더블 -- REQ-012 (Integration): KIS Open API 브로커 어댑터 -- REQ-013 (Integration): 주문 집행 루프 (claim·송신·체결·롤백) -- REQ-014 (Exception): 주문 핸드오프 durability + reconciliation -- REQ-015 (Documentation): 브로커/집행 baseline 문서 정합 - -## Delivery Plan -- 의존 순서대로 구현한다. - 1. REQ-011: `trex_core/application/broker_port.py`(BrokerPort + FillStatus) + `adapters/broker/paper.py`(결정적 async 더블). - 2. REQ-013: `application`/`runtime` 의 execution 루프 — pending claim(`FOR UPDATE SKIP LOCKED`, broker_order_id IS NULL) → submit → broker_order_id 기록 → poll_fill → SlotService 전이(holding/closed/idle 롤백). paper 로 lifecycle 검증. - 3. REQ-014: 루프에 reconcile 단계 + 재기동 복구(broker_order_id 있는 pending 을 브로커 상태와 대조). 크래시 주입 테스트. - 4. REQ-012: `adapters/broker/kis.py`(토큰/레이트리밋/모의투자 base URL/게이팅) + config. KIS 모의투자 검증(가용 시). - 5. REQ-015: baseline 4문서 정합. - -## Dependencies -- Internal: REQ-013/014 는 REQ-011(BrokerPort) 선행. REQ-012 는 REQ-011 포트 구현. 모두 BAT-002 슬롯 코어 데몬 위. -- External: KIS Open API(모의투자) + 자격증명(.env), HTTP 클라이언트(httpx). PostgreSQL(기존). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 BrokerPort 경계(불변식 B) + 백그라운드 execution 루프(동시성·단일 writer 유지) + 비동기 체결/reconciliation + KIS 외부 연동이 구조/인터페이스/런타임에 모두 영향. 구현 전 경계·루프 lifecycle·멱등/복구 규칙을 design.md 에 고정. - -## Milestones -1. M1 — REQ-011: BrokerPort + paper 더블(결정적 fill 시나리오). -2. M2 — REQ-013: paper 로 매수/매도 lifecycle(claim→송신→체결→holding/closed) + 멱등 + 거부 롤백. -3. M3 — REQ-014: 크래시 주입 reconciliation(송신 전/후) + 중복 주문 0. -4. M4 — REQ-012: KIS 어댑터(토큰/주문/체결조회) + 모의투자 검증(가용 시) + 게이팅. -5. M5 — REQ-015: baseline 4문서 정합. - -## Risks -- R-1 (High): 실주문 중복/유실. 대응: broker_order_id 멱등 가드 + reconciliation(REQ-014), paper 크래시 시나리오 테스트. -- R-2 (High): KIS 비동기 체결 복잡(부분/지연/거부). 대응: 통일 submit→poll 계약, paper async 더블로 결정적 재현 + KIS 모의투자. -- R-3 (High): KIS 실 API(토큰/레이트리밋/실자금). 대응: 모의투자만 검증, 실계좌 config 게이팅(Out of Scope), 자격증명 .env. -- R-4 (Medium): 단일 writer/도메인 순수성 침식. 대응: 슬롯 쓰기 SlotService 단일 경로, broker adapters 격리, import 스윕. -- R-5 (Medium): KIS 모의투자 자격증명 미가용 시 REQ-012 검증 공백. 대응: paper 기반(011/013/014) 완전 검증, KIS 통합은 skip+blocker 분리(verification). diff --git a/docs/batches/bat-004_20260604_broker-execution-and-outbox/verification.md b/docs/batches/bat-004_20260604_broker-execution-and-outbox/verification.md deleted file mode 100644 index 1d10042..0000000 --- a/docs/batches/bat-004_20260604_broker-execution-and-outbox/verification.md +++ /dev/null @@ -1,48 +0,0 @@ -# BAT-004 Verification - -- Status: confirmed -- Batch: BAT-004 - -## Acceptance Mapping -- REQ-011 (BrokerPort + paper double): - - AC1 submit→poll 비동기(동기 단축 없음) → `test_submit_does_not_synchronously_fill`. **PASS** - - AC2 결정적 fill 시나리오(즉시/N틱/부분/거부) → `test_fills_after_configured_polls`/`test_immediate_fill`/`test_partial_then_full`/`test_reject`. **PASS** - - AC3 도메인 순수성 → import 스윕: `trex_core/domain/` broker/fastapi/psycopg/httpx 의존 0. **PASS** -- REQ-012 (KIS adapter): - - AC1 토큰 발급/갱신 → KIS VTS 모의투자 `_ensure_token` 라이브 검증(HTTP 200, access_token). **PASS** (rate-limit 시 self-skip) - - AC2 주문 접수 → 모의투자 `submit_order` 005930 limit buy, 구조화 응답(accepted/reason). **PASS** - - AC3 체결 조회 → `poll_fill`(inquire-daily-ccld) 구현; 모의투자 미체결 시 accepted 반환. **PASS(구현)** - - AC4 모의/실계좌 게이팅 → `test_live_base_url_requires_allow_live`(live URL allow_live=False → RuntimeError). **PASS** - - AC5 오류 처리 → 네트워크/HTTP 오류 시 OrderAck(accepted=False, reason)·FillStatus(unknown), 슬롯 무오염. **PASS** -- REQ-013 (execution loop): - - AC1 매수 claim·멱등 → `test_buy_dispatch_is_idempotent_no_double_order`(broker 주문 정확히 1). **PASS** (NFR-2) - - AC2 매수 체결→holding → `test_buy_lifecycle_to_holding`(avg_price/qty). **PASS** - - AC3 거부 롤백→idle → `test_buy_reject_rolls_back_to_idle`. **PASS** - - AC4 매도 체결→closed → `test_sell_lifecycle_to_closed_bumps_entry_count`. **PASS** - - AC5 단일 writer → 집행 루프도 SlotService(advisory lock)로만 전이(코드/도메인 순수성). **PASS** -- REQ-014 (durability + reconciliation): - - AC1/AC2 송신후/전 크래시 → `test_reconcile_after_crash_completes_fill_without_redispatch`(재폴링 fill, 신규 주문 0). **PASS** - - AC3 체결 수렴 → 동 테스트 holding 수렴. **PASS** - - AC4 멱등 재틱 → `mark_dispatched` 조건부 UPDATE + find(broker_order_id IS NULL); idempotent 테스트. **PASS** (NFR-3) -- REQ-015 (baseline 정합): - - AC1 runtime-flows 집행 루프/reconcile 흐름 추가. **PASS** - - AC2 project-structure broker/execution 레이어 반영. **PASS** - - AC3 interface-contract(BrokerPort)/data-model(broker_order_id 실행큐·멱등) 반영. **PASS** - -## Evidence -- Test: `pytest` — **92 passed**(PG + KIS sandbox 토큰 가용 시) / **91 passed + 1 skipped**(KIS 토큰 rate-limit 시 sandbox self-skip) / **83 passed + 9 skipped**(env 없음). ruff clean. - - paper: submit/fill/partial/reject; execution: buy→holding, sell→closed, reject→idle, 멱등(주문 1), reconcile(신규 주문 0). - - KIS: 계정분해/모의 tr_id/라이브 게이팅 단위 + **KIS VTS 모의투자 토큰+주문 라이브 round-trip 통과**(이 세션 확인). -- Manual: 도메인 import 스윕 broker/web/db 의존 0. 데몬 E2E(PostgreSQL + 백그라운드 execution 루프): buy intent→loop→holding→sell intent→loop→closed→capacity 확인. -- Logs: implementation.md Validation 절 + 본 batch 작업 로그. - -## Baseline Sync Evidence (Architecture Impact = both) -- runtime-flows.md: `주문 집행 루프`(dispatch/poll/reconcile) flow 추가. -- project-structure.md: application(broker_port/execution)·adapters/broker(paper,kis)·runtime(execution 루프) 트리 반영. -- interface-contract.md / data-model.md: BrokerPort 경계 + broker_order_id 실행큐·멱등 의미 Change Log 반영. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. paper 경로(REQ-011/013/014)는 완전 결정적 검증. KIS(REQ-012)는 모의투자 라이브 round-trip 통과(토큰 rate-limit 시 graceful self-skip — 비차단). - - 비차단 잔여: KIS 모의투자 체결은 시장 개장/잔고에 따라 비결정적(미체결 가능) — 완전 체결 lifecycle 은 paper 로 보장. 부분체결 누적·옵션 B outbox·실계좌 라이브 go-live·연결 풀링은 후속 인계. diff --git a/docs/batches/bat-005_20260604_kis-token-redis-cache/implementation.md b/docs/batches/bat-005_20260604_kis-token-redis-cache/implementation.md deleted file mode 100644 index 8314eda..0000000 --- a/docs/batches/bat-005_20260604_kis-token-redis-cache/implementation.md +++ /dev/null @@ -1,29 +0,0 @@ -# BAT-005 Implementation - -- Status: completed -- Batch: BAT-005 - -## Plan Summary -- REQ-016: KISBroker 에 선택적 Redis 토큰 캐시(L1 인메모리 → L2 캐시 → 발급) + graceful fallback. orbit Redis 패턴 이식. - -## Changed Files -- trex_core/adapters/broker/token_cache.py (신규): `TokenCache` Protocol, `RedisTokenCache`(redis.from_url, 키 `trex:broker:kis:{account_ref}:token`, JSON 토큰+만료+app_key suffix, ex=ttl, 예외 graceful), `InMemoryTokenCache`(더블/폴백). -- trex_core/adapters/broker/kis.py: `token_cache` 주입, `_ensure_token` 3-레벨(L1/L2/issue) + 발급 시 store. -- trex_core/runtime/config.py: `KISConfig.redis_url` + `TREX_KIS_REDIS_URL`. -- trex_core/runtime/daemon.py: redis_url 있으면 `RedisTokenCache` 주입. -- pyproject.toml: `redis>=5` 의존. -- tests/adapters/broker/test_token_cache.py (신규). - -## Execution Log -1. TokenCache 추상화 + Redis/InMemory 구현(app_key suffix 안전, 만료 검증, 예외 graceful). -2. KISBroker _ensure_token 3-레벨 + store. -3. config/daemon wiring + pyproject redis. -4. 테스트 작성, redis 라이브러리 설치 후 실행. - -## Validation -- `pytest tests/adapters/broker/test_token_cache.py`: 4 passed (캐시 히트 HTTP 0 / 인스턴스 간 공유 / graceful fallback / app_key suffix 무효화). -- 전체 `pytest`: **96 passed** (기존 92 + 신규 4), ruff clean. -- 실 Redis 서버 불요: 인메모리 캐시 더블 + httpx.post 스텁으로 결정적 검증. RedisTokenCache 의 redis.from_url 경로는 운영 시 사용(라이브러리 import OK 확인). - -## Remaining Risks -- 없음(저위험). Redis 장애 시 graceful fallback(발급 진행). 실 Redis 서버 통합 검증은 운영 환경에서(라이브러리/와이어링 확인 완료). diff --git a/docs/batches/bat-005_20260604_kis-token-redis-cache/index.md b/docs/batches/bat-005_20260604_kis-token-redis-cache/index.md deleted file mode 100644 index 25ac301..0000000 --- a/docs/batches/bat-005_20260604_kis-token-redis-cache/index.md +++ /dev/null @@ -1,28 +0,0 @@ -# BAT-005: KIS 토큰 Redis 캐시 (minor-change) - -- Status: released -- Profile: batch-lite -- Source Discovery: dcy-003 (REQ-012 운영 견고성 보완) -- Included REQ: REQ-016 -- Excluded REQ: 없음 - -## Summary -- REQ-016 단일 저위험 Integration 요구사항을 minor-change 로 처리. KISBroker 에 선택적 Redis 토큰 캐시(L1 인메모리 → L2 Redis → 발급)를 더해 재기동/다중 프로세스 토큰 생존 + rate-limit 회피. orbit Redis 캐시 패턴 이식. - -## Scope -- In Scope: - - `TokenCache` 추상화 + `RedisTokenCache`(redis.from_url, 키 `trex:broker:kis:{account_ref}:token`, app_key suffix 안전, ex=ttl) + 테스트용 인메모리 더블. - - KISBroker `_ensure_token` 3-레벨 + graceful fallback. config `TREX_KIS_REDIS_URL` + daemon wiring. pyproject redis 의존. - - 캐시 히트/공유/폴백/suffix 단위테스트. -- Out of Scope: - - 다른 캐시 백엔드(PG/파일), 토큰 외 응답 캐시. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite, no external interface/flow change) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- minor-change: 기존 BrokerPort 뒤 토큰 캐시 계층 추가, 외부 인터페이스·슬롯 흐름 불변. design 불요. -- 실 Redis 없을 때도 graceful fallback → 단일 데몬 회귀 없음. 테스트는 인메모리 캐시 더블로 결정적 검증(실 Redis 서버 불요). diff --git a/docs/batches/bat-005_20260604_kis-token-redis-cache/planning.md b/docs/batches/bat-005_20260604_kis-token-redis-cache/planning.md deleted file mode 100644 index 3d87cb2..0000000 --- a/docs/batches/bat-005_20260604_kis-token-redis-cache/planning.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-005 Planning - -- Status: confirmed -- Batch: BAT-005 -- Profile: batch-lite - -## Included REQ -- REQ-016 (Integration): KIS 인증 토큰 Redis 캐시 - -## Delivery Plan -1. `trex_core/adapters/broker/token_cache.py`: `TokenCache` Protocol(load/store) + `RedisTokenCache`(redis.from_url, JSON 토큰+만료+app_key suffix, ex=ttl, 예외 graceful) + `InMemoryTokenCache`(테스트/폴백 더블). -2. `kis.py`: `token_cache` 주입, `_ensure_token` = L1 인메모리 → L2 캐시(load, suffix 검증, 만료 확인) → 발급+store. -3. `runtime/config.py`: `TREX_KIS_REDIS_URL` → KISConfig.redis_url. `runtime/daemon.py`: redis_url 있으면 RedisTokenCache 주입. -4. `pyproject.toml`: `redis>=5` 의존. -5. `tests/adapters/broker/test_token_cache.py`: 캐시 히트(HTTP 0)/공유 재사용/graceful fallback/app_key suffix. - -## Dependencies -- Internal: REQ-012 KISBroker 확장. -- External: redis 라이브러리(+ 운영 시 Redis 서버). 테스트는 인메모리 더블. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 기존 BrokerPort 뒤 토큰 캐시 계층 추가뿐. 외부 인터페이스/슬롯 런타임 흐름 변경 없음. batch-lite + design 생략(minor-change). - -## Milestones -1. M1 — TokenCache + RedisTokenCache + KISBroker 3-레벨. -2. M2 — config/daemon wiring + pyproject. -3. M3 — 단위테스트 green(히트/공유/폴백/suffix). - -## Risks -- R-1 (Low): Redis 장애가 주문 경로를 막음. 대응: 캐시 예외 graceful fallback(발급으로 진행), 캐시는 best-effort. -- R-2 (Low): 자격증명 교체 후 stale 토큰. 대응: app_key suffix 검증으로 캐시 무효화(AC-4). diff --git a/docs/batches/bat-005_20260604_kis-token-redis-cache/verification.md b/docs/batches/bat-005_20260604_kis-token-redis-cache/verification.md deleted file mode 100644 index 72cf6be..0000000 --- a/docs/batches/bat-005_20260604_kis-token-redis-cache/verification.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-005 Verification - -- Status: confirmed -- Batch: BAT-005 - -## Acceptance Mapping -- REQ-016: - - AC1 캐시 히트 → HTTP 발급 없음 → `test_cache_hit_avoids_issuance`(L1 drop 후 L2 히트, token call 1회 유지). **PASS** - - AC2 인스턴스 간 공유(재기동/다중프로세스) → `test_shared_cache_across_instances`(B 인스턴스 재발급 0). **PASS** - - AC3 graceful fallback → `test_graceful_fallback_when_cache_errors`(캐시 miss/오류 시 발급 진행). **PASS** - - AC4 app_key suffix 안전 → `test_app_key_suffix_mismatch_ignores_cache`(자격증명 교체 시 stale 무시 재발급). **PASS** - -## Evidence -- Test: `pytest` 4 token-cache passed; 전체 96 passed; ruff clean. -- Manual: redis 라이브러리 import + RedisTokenCache wiring 확인. 실 Redis 서버 없이 인메모리 더블로 결정적 검증. - -## no architecture impact confirmed -- batch-lite, planning Design Gate=no. 기존 BrokerPort 뒤 토큰 캐시 계층 추가뿐 — 외부 인터페이스/슬롯 런타임 흐름 변경 없음. baseline 문서 영향 없음. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 실 Redis 서버 통합은 운영 환경 검증 사항(라이브러리/와이어링/폴백 검증 완료, 비차단). diff --git a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/design.md b/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/design.md deleted file mode 100644 index 2f8be7e..0000000 --- a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-006 Design - -- Status: draft -- Batch: BAT-006 - -## Architecture Summary -- BAT-004 execution loop 위에 ledger write-back 레이어를 추가한다. **application** 에 `LedgerPort`와 ledger outbox use case, **adapters/ledger** 에 in-memory/stub 및 portfolio-hub HTTP 어댑터, **runtime** 에 ledger retry worker/config 를 둔다. execution fill 전이는 슬롯 상태 전이를 먼저 보장하고 ledger 기록은 outbox 로 적재해 eventually-consistent 하게 처리한다. - -## Changed Areas -- Docs: runtime-flows(체결→ledger outbox→portfolio-hub retry/conflict 흐름), interface-contract(`LedgerPort`, `FillStatus` 확장, portfolio-hub endpoint), project-structure(ledger adapter/outbox 위치), data-model(ledger trade fields, outbox work item, correlation_id). -- Modules: - - application: `LedgerPort`, ledger exceptions, ledger outbox command/service, execution fill hook. - - adapters/ledger: in-memory/stub ledger, portfolio-hub HTTP adapter. - - adapters/broker: paper/KIS `FillStatus.execution_id` 합성, fee 계산 운반. - - runtime: portfolio-hub config, ledger retry loop lifecycle. - - persistence: ledger outbox work item store/schema. -- Interfaces: `LedgerPort.record_trade(...)`, `FillStatus.execution_id/fee`, portfolio-hub `POST /v1/portfolios/{portfolio_id}/ledger/trades` (body `quantity`/`fee_amount`/`tax_amount`, 성공 201). (정정: REQ-017 CHG-20260606-01 — 구 `POST /v1/assets/{asset_id}/...`, body `qty`/`fee`/`tax` 폐기) - -## Key Decisions -1. `asset_id == portfolio_id`: 슬롯의 `portfolio_id` 를 ledger path param 으로 직접 사용한다. 매핑 조회나 asset master 동기화는 후속 범위다. -2. outbox 우선: fill 전이 경로는 ledger 직접 HTTP write 대신 durable outbox work item 을 적재한다. 슬롯 전이는 ledger write 성공과 분리한다. -3. 멱등 키: `correlation_id = {asset_id}:{order_id}:{execution_id}` 로 로컬 dedup 하고 portfolio-hub 409 는 `conflict_blocked` 로 분류한다. -4. outcome 어휘: `completed`, `duplicate_completed`, `retryable_failure`, `conflict_blocked` 네 가지로 worker 결과를 고정한다. -5. 도메인 순수성: `trex_core/domain` 은 portfolio-hub, HTTP, ledger adapter 를 import 하지 않는다. LedgerPort 는 application 경계, HTTP adapter 는 adapters 경계에 둔다. -6. 단일 기록 주체: 전략/도메인이 아니라 execution fill path 만 ledger outbox item 을 생성한다. - -## Edge Cases -- 동일 `execution_id` work item 이 2회 생성/처리됨 → correlation_id 로 dedup, 원장 중복 0, outcome `duplicate_completed`. -- portfolio-hub 일시 실패/timeout → `retryable_failure`, 다음 worker tick 에 재시도, 슬롯 상태는 이미 전이된 상태 유지. -- portfolio-hub 409 conflict → `conflict_blocked`, 자동 무한 재시도 중단, 운영자 해소 대상으로 남김. -- `portfolio_id` 없는 체결 → 정의된 정책에 따라 ledger 기록 skip 또는 blocked outcome 으로 처리하되 슬롯 전이는 정상 수행. -- KIS 체결 데이터 부족 → 결정적 execution_id 합성 규칙으로 보강하고, fee 는 BrokerPort 계산, tax 는 0. -- retry worker 재시작 → persisted outbox 상태를 기준으로 미완료 item 만 재처리. - -## Architecture Impact -- both (project-structure + runtime-flows). interface-contract(`LedgerPort`, `FillStatus`)와 data-model(outbox/correlation_id)도 갱신한다. - -## Reference Doc Update Plan -- project-structure.md: `application` ledger port/outbox service, `adapters/ledger`, runtime retry worker, persistence schema 위치를 반영한다. -- runtime-flows.md: fill transition 이후 outbox 적재, retry worker, completed/duplicate/conflict/retryable 흐름을 추가한다. -- interface-contract.md: `LedgerPort.record_trade(...)`, `FillStatus.execution_id/fee`, portfolio-hub ledger endpoint/409 mapping 을 추가한다. -- data-model.md: ledger trade payload fields, outbox work item fields/status/outcome, `correlation_id` 멱등 규칙을 추가한다. diff --git a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/implementation.md b/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/implementation.md deleted file mode 100644 index 5623a32..0000000 --- a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/implementation.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-006 Implementation - -- Status: complete -- Batch: BAT-006 - -## Plan Summary -- LedgerPort 계약 + portfolio-hub HTTP 어댑터 + 결정론 stub 원장을 추가하고, 집행 루프 fill 전이 직후에 ledger outbox 에 work item 을 적재하도록 연결했다. `LedgerWriter` 가 outbox 를 drain 해 portfolio-hub write 를 시도하고, NEW/DUPLICATE_OK/conflict/retryable 결과를 `completed/duplicate_completed/retryable_failure/conflict_blocked` outcome 으로 분류한다. paper/KIS 브로커는 결정적 `execution_id` 와 `fee` 를 `FillStatus` 로 운반한다. baseline 4문서를 ledger 레이어로 정합시켰다. - -## Changed Files -- 신규 - - `trex_core/application/ledger_port.py` — `LedgerPort` Protocol, `LedgerTrade`, `LedgerWriteResult`, `LedgerError`/`LedgerConflictError`/`LedgerRetryableError` (REQ-017). - - `trex_core/application/ledger_outbox.py` — `LedgerOutbox` Protocol, `LedgerWorkItem`, `OutboxOutcome`, `WorkItemStatus`, `LedgerWriter`, `InMemoryLedgerOutbox` (REQ-019). - - `trex_core/adapters/ledger/__init__.py`, `in_memory.py` (`InMemoryLedger` stub), `portfolio_hub.py` (`PortfolioHubLedger` HTTP 어댑터) (REQ-017). - - `trex_core/adapters/persistence/sql/002_create_ledger_outbox.sql` — `ledger_outbox` 테이블, `correlation_id` PK, status/outcome CHECK (REQ-019). - - `tests/adapters/ledger/{__init__,test_in_memory_ledger,test_portfolio_hub_ledger}.py` — REQ-017 AC-1/2/3. - - `tests/adapters/persistence/test_postgresql_ledger_outbox.py` — REQ-019 PG outbox (PG-gated). - - `tests/application/{test_execution_ledger,test_ledger_outbox}.py` — REQ-018 + REQ-019. - - `tests/domain/test_domain_import_sweep.py` — REQ-017 AC-4 (NFR-1) 정적 import 스윕. -- 수정 - - `trex_core/application/broker_port.py` — `FillStatus.execution_id/fee/tax` 확장 + 공유 `calculate_fee(side, amount)` 헬퍼 (REQ-018). - - `trex_core/adapters/broker/paper.py` — `filled` 상태에서 `paper:{broker_order_id}:{filled_qty}` execution_id + fee 합성. - - `trex_core/adapters/broker/kis.py` — `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}` execution_id + fee 운반. - - `trex_core/application/execution.py` — `ExecutionService` 가 fill 전이 직후 `LedgerWorkItem` 을 outbox 에 enqueue, `portfolio_id` 부재면 skip, slot 전이 우선 보장 (REQ-018 + REQ-019). - - `trex_core/adapters/persistence/postgresql.py` — `PostgreSQLLedgerOutbox` (`enqueue`/`list_pending`/`get`/`mark_outcome`). - - `trex_core/runtime/config.py` — `PortfolioHubConfig`, `DaemonConfig.portfolio_hub`/`ledger_interval_sec`, env 로딩. - - `trex_core/runtime/daemon.py` — `_build_ledger`, ledger writer 백그라운드 루프, `ExecutionService(..., ledger_outbox=outbox)`. - - `tests/conftest.py`, `tests/adapters/persistence/test_migration_and_drift.py` — `ledger_outbox` 테이블 반영. - - `.env.example` — `TREX_PORTFOLIO_HUB_BASE_URL/TOKEN/TIMEOUT`, `TREX_LEDGER_INTERVAL_SEC` 추가. - -## Execution Log -1. baseline 테스트 88 passed / 8 skipped 확인. -2. REQ-017: `LedgerPort` + `LedgerTrade` + 예외 + `InMemoryLedger` stub + `PortfolioHubLedger` 어댑터 추가, contract 테스트 4종 작성. -3. REQ-018: `FillStatus.execution_id/fee/tax` 확장, paper/KIS 결정적 합성 + `calculate_fee` 헬퍼, `ExecutionService._on_filled` 에서 fill 전이 직후 ledger outbox enqueue, `portfolio_id` 부재 시 skip + `ExecutionResult.ledger_enqueued/ledger_skipped` 카운터 추가. -4. REQ-019: `LedgerOutbox` Protocol + `LedgerWorkItem`/`OutboxOutcome`/`WorkItemStatus`, `LedgerWriter` (correlation_id 로컬 dedup + 어댑터 `LedgerWriteResult.DUPLICATE_OK` 매핑 → `duplicate_completed`), `InMemoryLedgerOutbox`, `PostgreSQLLedgerOutbox`, `002_create_ledger_outbox.sql` 마이그레이션. -5. 데몬 lifespan 에 ledger writer thread 추가, `PortfolioHubConfig` env 로딩. -6. REQ-020: runtime-flows / interface-contract / project-structure / data-model 4문서를 ledger 레이어로 정합. -7. 정적 import 스윕 테스트(`tests/domain/test_domain_import_sweep.py`) 로 `trex_core/domain` 의 ledger/HTTP 의존 0 검증. -8. 최종 테스트: 109 passed / 12 skipped (PG·KIS 게이트). ruff lint 0 issues. - -## Validation -- 명령: `.venv/bin/python -m pytest -q` - - 결과: `109 passed, 12 skipped, 1 warning`. -- 명령: `.venv/bin/ruff check trex_core tests` - - 결과: `All checks passed!` -- 추가 테스트 21개(REQ-017 7 / REQ-018 3 / REQ-019 7 / NFR-1 import 스윕 1 / PG outbox 4 PG-skip). -- portfolio-hub 어댑터 contract 는 `httpx.MockTransport` 로 path/body/auth/409/5xx/network-error 모두 커버. -- buy/sell lifecycle 테스트로 슬롯 전이와 ledger 기록 분리, dedup, 재시도 결국 기록, 409 무한루프 방지, ledger 장애 시 슬롯 전이 우선 보장을 확인. - -## Remaining Risks -- portfolio-hub 의 실제 4xx 응답 본문(특히 `duplicate` 플래그) 계약은 orbit 패턴을 가정한다. 실통합 시 응답 본문이 다르면 `PortfolioHubLedger.record_trade` 의 `LedgerWriteResult.DUPLICATE_OK` 판별을 조정해야 한다. 본 batch 는 stub/contract 수준 evidence 로 검증했다. -- KIS `execution_id` 합성에 사용되는 `ord_dt`/`ord_gno_brno` 가 일부 KIS 응답에서 비어 있으면 결정성이 약화될 수 있다. 현재는 누락 시 `today`/빈 문자열로 폴백한다. 운영 데이터 관찰 후 보강 여부 판단(후속 후보). -- 부분체결(partially_filled) 단계에서는 ledger 기록을 enqueue 하지 않는다. 부분체결 누적 fill 의 정밀 회계는 후속 Discovery 범위로 남는다. diff --git a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/index.md b/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/index.md deleted file mode 100644 index 5f140ea..0000000 --- a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/index.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-006: portfolio-hub ledger 통합 - -- Status: released -- Profile: standard -- Source Discovery: dcy-004 -- Included REQ: REQ-017, REQ-018, REQ-019, REQ-020 -- Excluded REQ: 없음 - -## Summary -- dcy-004 슬라이스(체결을 외부 회계/포지션 원장에 기록)를 하나의 delivery 단위로 묶는다. BAT-004 의 주문 집행 루프 위에 `LedgerPort` + portfolio-hub 어댑터 + fill hook + ledger outbox 재시도/멱등 처리를 추가하고, baseline 문서를 실제 ledger 레이어와 정합시킨다. - -## Scope -- In Scope: - - REQ-017: `LedgerPort.record_trade(...)` 계약, portfolio-hub ledger 어댑터, 결정론 stub/in-memory 원장, `TREX_PORTFOLIO_HUB_*` config. - - REQ-018: 매수/매도 fill 전이 시 ledger 기록 hook, `FillStatus.execution_id/fee` 확장, `asset_id = slot.portfolio_id` 사용, 단일 기록 주체 유지. - - REQ-019: ledger outbox work item, correlation_id 멱등, retry worker, outcome(`completed`/`duplicate_completed`/`retryable_failure`/`conflict_blocked`) 분류. - - REQ-020: runtime-flows/interface-contract/project-structure/data-model ledger baseline 정합. -- Out of Scope (후속 Discovery): - - `post_portfolio_transaction` 입출금 등 거래 외 트랜잭션, asset master 동기화, portfolio-hub ↔ core 양방향 reconciliation, 포지션 재구성, safety floor, 매수·매도 전략 데몬, 실계좌 go-live. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery dcy-004 단일. 확정 결정: `asset_id == portfolio_id`, 별도 ledger outbox + retry loop, execution_id 결정적 합성(KIS/paper), fee 는 BrokerPort 계산, tax 기본 0. -- 핵심 리스크: 원장 기록 유실/중복(R-1), portfolio-hub 장애가 execution path 를 막는 결합(R-3). outbox + dedup + adapter 격리 + stub failure injection 으로 완화한다. -- portfolio-hub 실제 계약/자격증명은 orbit `post_ledger_trade` 동일 가정을 시작점으로 두되, verification 에서 adapter contract evidence 또는 미가용 blocker 를 분리 기록한다. diff --git a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/planning.md b/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/planning.md deleted file mode 100644 index f5ebba4..0000000 --- a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-006 Planning - -- Status: draft -- Batch: BAT-006 -- Profile: standard - -## Included REQ -- REQ-017 (Integration): LedgerPort 추상화 + portfolio-hub 어댑터 + stub 원장 -- REQ-018 (Integration): 체결→원장 기록 fill hook -- REQ-019 (Exception): ledger outbox durability + correlation_id 멱등 -- REQ-020 (Documentation): ledger 레이어 baseline 문서 정합 - -## Delivery Plan -- 의존 순서대로 구현한다. - 1. REQ-017: `trex_core/application` 에 `LedgerPort` 계약과 ledger 예외를 정의하고, `trex_core/adapters/ledger` 에 in-memory/stub 원장과 portfolio-hub HTTP 어댑터를 추가한다. - 2. REQ-018: `FillStatus` 가 `execution_id`/`fee` 를 운반하도록 broker port 와 paper/KIS 어댑터를 확장하고, execution fill 전이에 ledger 기록 입력을 연결한다. - 3. REQ-019: 직접 write 대신 ledger outbox 적재 + retry worker 로 경로를 고정하고, `correlation_id = {asset_id}:{order_id}:{execution_id}` dedup/outcome 분류를 구현한다. - 4. REQ-020: 실제 코드 경계와 data model 이 확정된 뒤 baseline 4문서(runtime-flows/interface-contract/project-structure/data-model)를 갱신한다. - -## Dependencies -- Internal: - - REQ-018 은 REQ-017 의 `LedgerPort`와 stub 원장 선행. - - REQ-019 는 REQ-018 의 fill 기록 이벤트를 직접 write 가 아니라 outbox work item 으로 전환. - - REQ-020 은 REQ-017~019 구현 결과에 의존. - - BAT-004 의 execution loop, BrokerPort, FillStatus, PostgreSQL runtime 위에서 확장한다. -- External: - - portfolio-hub ledger API(`POST /v1/assets/{asset_id}/ledger/trades`, orbit `post_ledger_trade` 패턴). - - portfolio-hub URL/token 환경 변수(`TREX_PORTFOLIO_HUB_*`). - - HTTP client, PostgreSQL(outbox persistence), 기존 .env loading(REQ-010). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 application port + external adapter + execution fill lifecycle + outbox durability/retry worker + baseline data model 이 모두 구조/인터페이스/런타임에 영향. 구현 전 `record_trade` 계약, fill event shape, outbox schema/state machine, failure/outcome policy 를 design.md 에 고정해야 한다. - -## Milestones -1. M1 — REQ-017: LedgerPort + stub ledger + portfolio-hub adapter contract/409 handling + config. -2. M2 — REQ-018: FillStatus execution_id/fee 확장 + paper/KIS 결정적 값 운반 + buy/sell fill hook. -3. M3 — REQ-019: ledger outbox store + retry worker + correlation_id dedup + conflict/retry outcomes. -4. M4 — REQ-020: baseline 4문서 정합. -5. M5 — 통합 verification: buy/sell lifecycle, duplicate 0, retry eventually completed, conflict_blocked, import sweep. - -## Risks -- R-1 (High): 원장 기록 유실/중복. 대응: outbox persistence + correlation_id dedup + duplicate_completed/conflict_blocked outcome + failure injection tests. -- R-2 (High): fill 전이와 ledger write 결합으로 슬롯 전이 지연/실패. 대응: 슬롯 전이는 우선 진행하고 ledger write 는 outbox retry 로 분리. -- R-3 (Medium): portfolio-hub 계약/자격증명 미가용. 대응: stub/in-memory 완전 검증, adapter contract test, 실제 통합은 evidence 또는 blocker 로 분리. -- R-4 (Medium): 도메인 순수성/단일 기록 주체 침식. 대응: LedgerPort 는 application 계약, HTTP 는 adapters 격리, `trex_core/domain` import sweep, 기록 주체는 execution path 로 한정. -- R-5 (Medium): REQ-020 baseline 이 구현보다 먼저 확정되어 drift 발생. 대응: baseline 갱신은 M4 로 후행하고 verification 에 코드-문서 정합 점검 포함. diff --git a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/verification.md b/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/verification.md deleted file mode 100644 index dc75e94..0000000 --- a/docs/batches/bat-006_20260604_portfolio-hub-ledger-integration/verification.md +++ /dev/null @@ -1,79 +0,0 @@ -# BAT-006 Verification - -- Status: approved -- Batch: BAT-006 - -## Acceptance Mapping -- REQ-017: - - AC-1: in-memory stub 원장 `record_trade(...)` 1회 호출 시 거래 1건 적재와 필드 보존 evidence. - - AC-2: portfolio-hub adapter 가 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 에 `execution_id` 포함 payload(body `quantity`/`fee_amount`/`tax_amount`)를 전송하는 contract evidence. (정정: REQ-017 CHG-20260606-01 — 아래 Re-verification Addendum) - - AC-3: portfolio-hub 409 응답이 `LedgerConflictError` 또는 `conflict_blocked` 경로로 표면화되는 evidence. - - AC-4: `trex_core/domain` portfolio-hub/HTTP 의존 0 import sweep evidence. -- REQ-018: - - AC-1: idle 슬롯 매수 intent 체결 시 holding 전이 + buy ledger item/record 1건 evidence. - - AC-2: holding 슬롯 매도 intent 체결 시 closed 전이 + sell ledger item/record 1건 evidence. - - AC-3: `FillStatus` 가 결정적 `execution_id`, fee, tax=0 을 운반하는 evidence. - - AC-4: `portfolio_id` 부재 체결 정책(skip/block)과 슬롯 전이 일관성 evidence. - - AC-5: ledger 기록 주체가 execution path 로 한정되는 code review/import evidence. -- REQ-019: - - AC-1: 동일 execution_id 2회 처리 시 원장 중복 0 evidence. - - AC-2: retryable portfolio-hub failure 후 retry 로 eventually completed evidence. - - AC-3: 409 conflict 가 `conflict_blocked` 로 남고 무한 자동 재시도하지 않는 evidence. - - AC-4: 이미 기록된 work item 재처리 시 `duplicate_completed` evidence. - - AC-5: portfolio-hub 불가 직후에도 슬롯 전이는 진행되는 분리 evidence. -- REQ-020: - - AC-1: 4개 baseline 문서에 ledger 레이어 반영 evidence. - - AC-2: LedgerPort, FillStatus, outbox 구조, 원장 필드 설명이 코드와 모순되지 않는 review evidence. - - AC-3: baseline 문서 변경 이력에 REQ-017~020 반영 기록 evidence. - -## Evidence - -### Test -- 명령: `.venv/bin/python -m pytest -q` - - 결과: `109 passed, 12 skipped, 1 warning in 0.98s` (PG 4건 + KIS 1건 + 기존 PG 7건은 DSN/credentials 게이트로 skip). -- REQ-017 AC-1: `tests/adapters/ledger/test_in_memory_ledger.py::test_record_trade_preserves_fields_and_is_idempotent_on_execution_id` — 단일 호출 후 `count()==1` + 필드 보존, 동일 execution_id 재호출 후에도 `count()==1`. -- REQ-017 AC-2: `tests/adapters/ledger/test_portfolio_hub_ledger.py::test_post_contract_includes_execution_id_and_path_uses_portfolio_id` — `httpx.MockTransport` 로 path `/v1/portfolios/PF-77/ledger/trades`, body 에 `execution_id`/`side`/`quantity`/`fee_amount`/`tax_amount` 포함(+`qty`/`fee`/`tax` 부재), `Authorization: Bearer …` 헤더 캡처. (정정: REQ-017 CHG-20260606-01 — 아래 Re-verification Addendum) -- REQ-017 AC-3: `tests/adapters/ledger/test_portfolio_hub_ledger.py::test_409_surfaces_as_ledger_conflict_error` + `tests/application/test_execution_ledger.py::test_conflict_blocks_without_auto_retry` — 409 → `LedgerConflictError`; outbox row `status=conflict_blocked`, 후속 writer tick 에서 어댑터 재호출 없음. -- REQ-017 AC-4: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` — `trex_core/domain` AST 워크가 `httpx`/`fastapi`/`uvicorn`/`psycopg`/`redis`/`trex_core.adapters`/`trex_core.runtime`/`trex_core.application` 0 회 import 확인. -- REQ-018 AC-1: `tests/application/test_execution_ledger.py::test_buy_fill_enqueues_one_ledger_item_and_writer_records_it` — buy intent → execution.run_once → slot=holding, ledger_enqueued=1, writer.run_once → ledger.count()=1, `LedgerTrade(asset_id="PF-1", side="buy", qty=10, price=1000.0)`. -- REQ-018 AC-2: `tests/application/test_execution_ledger.py::test_sell_fill_enqueues_sell_ledger_item` — buy→holding→sell intent→execution.run_once → slot=closed, ledger 에 `["buy","sell"]` 순서. -- REQ-018 AC-3: 위 buy 테스트가 `t.execution_id.startswith("paper:PAPER-")`, `t.fee > 0.0`, `t.tax == 0.0` 직접 검증. -- REQ-018 AC-4: `tests/application/test_execution_ledger.py::test_portfolio_id_absent_skips_ledger_but_transitions_slot` — portfolio_id=None → ledger_enqueued=0, ledger_skipped=1, 슬롯은 HOLDING. -- REQ-018 AC-5: `tests/domain/test_domain_import_sweep.py` (도메인 격리) + `tests/application/test_execution_ledger.py` (오직 `ExecutionService._record_ledger` 만 outbox.enqueue 호출 — code review). `grep -r "ledger_outbox.enqueue\|LedgerOutbox.enqueue" trex_core/` 결과는 `ExecutionService` 단일 호출지점. -- REQ-019 AC-1: `tests/application/test_execution_ledger.py::test_duplicate_execution_id_results_in_zero_duplicate_in_ledger` — 같은 trade 재 enqueue 시 outbox row 1개 유지, 별도 outbox 에서 재처리해도 ledger.count()=1. -- REQ-019 AC-2: `tests/application/test_execution_ledger.py::test_retryable_failure_then_eventually_completes` + `tests/application/test_ledger_outbox.py::test_writer_retries_until_success` — retryable 2회 + completed 1회로 ledger.count()=1, 슬롯은 즉시 HOLDING. -- REQ-019 AC-3: `tests/application/test_execution_ledger.py::test_conflict_blocks_without_auto_retry` + `tests/application/test_ledger_outbox.py::test_writer_marks_conflict_blocked_and_does_not_retry` — 후속 writer tick stats 모두 0. -- REQ-019 AC-4: `tests/application/test_execution_ledger.py::test_duplicate_completed_when_reprocessing_terminal_item` + `test_duplicate_execution_id_results_in_zero_duplicate_in_ledger` — 별도 outbox 에 동일 trade 재 enqueue → writer.run_once stats.duplicates=1, stats.completed=0, ledger.count()=1. -- REQ-019 AC-5: `tests/application/test_execution_ledger.py::test_slot_transition_proceeds_when_ledger_is_down` — `InMemoryLedger(fail_n_then_succeed=100)` 으로 모든 writer tick 실패, slot 은 HOLDING, outbox row `status=PENDING` 유지(무유실). -- PG 통합 (DSN 제공 시): `tests/adapters/persistence/test_postgresql_ledger_outbox.py` 4건 — enqueue 멱등, writer completed/conflict_blocked/retryable 행 컬럼 검증. CI 환경에 DSN 없어 skip 4건은 명시적으로 PG 게이트. - -### Manual -- baseline 문서 ↔ 코드 정합 리뷰 (REQ-020 AC-1/AC-2): - - `docs/runtime-flows.md` 새 섹션 `Flow: 체결 → ledger outbox` + `Flow: ledger writer` 가 `ExecutionService._record_ledger` (`trex_core/application/execution.py:117-141`) 과 `LedgerWriter.run_once`/`_process` (`trex_core/application/ledger_outbox.py:96-149`) 로직 정합. paper/KIS execution_id 합성 규칙은 `trex_core/adapters/broker/paper.py:73-79`, `trex_core/adapters/broker/kis.py:177-192` 와 일치. - - `docs/interface-contract.md` 새 섹션 `Interface: LedgerPort` + `Interface: LedgerOutbox/LedgerWriter` 가 `trex_core/application/ledger_port.py` 시그니처, `LedgerWriteResult.NEW/DUPLICATE_OK`, `LedgerConflictError`/`LedgerRetryableError` 와 정합. portfolio-hub endpoint 표는 `trex_core/adapters/ledger/portfolio_hub.py:_payload`/`record_trade` 와 정합. - - `docs/project-structure.md` 디렉토리 트리에 `trex_core/application/ledger_port.py`, `ledger_outbox.py`, `trex_core/adapters/ledger/`, persistence 의 `PostgreSQLLedgerOutbox` + `002_create_ledger_outbox.sql`, runtime 의 ledger writer 루프가 반영. 실제 디렉토리 트리(`find trex_core -type f -name '*.py'`)와 일치 확인. - - `docs/data-model.md` 새 엔티티 `LedgerTrade`(application DTO) + `ledger_outbox` 테이블 정의가 `002_create_ledger_outbox.sql` (PK `correlation_id`, status/last_outcome CHECK) 와 1:1 정합. `FillStatus` 확장(`execution_id/fee/tax`) 도 명시. -- code-search (REQ-020 AC-2 + REQ-018 AC-5 보조): - - `grep -r "LedgerOutbox.enqueue\|self._ledger_outbox.enqueue" trex_core/` → `trex_core/application/execution.py` 단 1곳(execution path 단일 기록 주체). - - `grep -r "import httpx\|from httpx" trex_core/domain/` → 결과 0(NFR-1 보강). - -### Logs -- `.venv/bin/ruff check trex_core tests` → `All checks passed!` (style/lint blocker 0). -- 정적 import 스윕 자동화: `tests/domain/test_domain_import_sweep.py` 가 CI 의 일부로 회귀 가드. -- portfolio-hub 실 통합 evidence 는 본 batch 범위 밖(`PortfolioHubLedger` 는 `httpx.MockTransport` 로 contract 검증). 실 통합은 운영 환경에서 수집 — `Remaining Risks` 와 `docs/srs/Integration/req-017_…md` Notes 의 A-1/DATA 위임 항목으로 추적. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. -- Notes: - - portfolio-hub 실 계약/자격증명 evidence 는 stub + contract 수준이며, 실 통합 검증은 운영 단계에서 수집한다(dcy-004 DATA 위임 사항). - - 부분체결 누적/정밀 세금 모델은 후속 Discovery 로 분리됨(이번 범위 밖). - -## Re-verification Addendum (2026-06-06, REQ-017 CHG-20260606-01) -- 트리거: RabbitMQ 매수 인텐트 → 체결 → ledger 기록 실 e2e 중 ledger write 가 live portfolio-hub 에서 404 발생. 원인은 어댑터가 구 orbit 가정 계약(`POST /v1/assets/{asset_id}/ledger/trades`, body `qty`/`fee`/`tax`)을 사용한 것. 실 계약은 openapi `LedgerTradeRequest` 기준 `POST /v1/portfolios/{portfolio_id}/ledger/trades` + `quantity`/`fee_amount`/`tax_amount`(성공 201). -- 정정 구현: `trex_core/adapters/ledger/portfolio_hub.py` (path + body 키), `tests/adapters/ledger/test_portfolio_hub_ledger.py` (실 계약 단언). -- 재검증 결과: - - 단위/통합: `.venv/bin/python -m pytest tests/adapters/ledger/ tests/application/test_execution_ledger.py -q` → `16 passed`. `.venv/bin/ruff check trex_core/adapters/ledger tests/adapters/ledger` → All checks passed. - - 실 e2e (live portfolio-hub 127.0.0.1:18000): 데몬 기동 → 매수 인텐트(005930, 10주, 300,000) 어드미션 → 슬롯 `idle→pending_buy→holding`(qty=10, avg_price=300000) → ledger writer `POST /v1/portfolios/{portfolio_id}/ledger/trades` **201 Created** → `ledger_outbox.status=completed`(last_outcome=completed). 직전 404 로 쌓였던 outbox 항목이 정정 후 재기동 시 무유실 드레인되어 REQ-019 durability 도 동시 실증(attempts 누적, 유실 0). -- 영향: REQ-017 Implemented 유지(정정 후 실 계약 일치). REQ-018/019/020 evidence 는 영향 없음(포트/실행 경로/outbox 시맨틱 불변). REL-001(confirmed) 의 ledger write release blocker 해소. diff --git a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/design.md b/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/design.md deleted file mode 100644 index fc7aa12..0000000 --- a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/design.md +++ /dev/null @@ -1,56 +0,0 @@ -# BAT-007 Design - -- Status: draft -- Batch: BAT-007 - -## Architecture Summary -- 기존 FastAPI/uvicorn HTTP `/v1` 어댑터를 제거하고, trex-core ↔ 전략 모듈 통신을 **RabbitMQ(AMQP) 기반 메시지 transport** 로 전환한다. **application** 에 `MessagingTransport` Protocol 을 두고, **adapters/messaging** 에 `RabbitMQTransport`(`pika` 동기) 와 `InMemoryTransport`(stub) 구현체를, **runtime** 에 sync+thread daemon(consumer/execution/ledger 스레드 + 이벤트 publisher) 구성을 둔다. 토폴로지는 3 패턴(스냅샷 RPC + intent admission RPC + 이벤트 topic) 이며, intent 는 슬롯 Postgres durable commit 후 ack + correlation_id dedup 으로 effectively-once 효과를 낸다. broker 장애 시 어댑터의 connection 상태가 application 레이어로 노출되어 신규 매매를 보수적으로 중단한다(버퍼링 0). HTTP 표면은 `healthz` 포함 0 으로 떨어지고 liveness 는 프로세스 감독이 담당한다. - -## Changed Areas -- Docs: interface-contract(메시지 계약 + 어댑터 신선도 노출, HTTP `/v1` 제거), runtime-flows(스냅샷 RPC / intent RPC / 이벤트 발행·구독 / degrade 분기 / 스레드 구성 / HTTP 흐름 제거), project-structure(adapters/messaging 신설, adapters/api 제거, runtime/daemon 의 스레드 구성과 의존성 변경). -- Modules: - - application: 신규 `messaging.py`(`MessagingTransport` Protocol + RPC 요청/응답 DTO + 이벤트 DTO). 기존 `intents.py`/(dcy-005 스냅샷 서비스 자리) 는 transport-무관 호출 인자만 받는다. - - adapters/messaging: 신규 패키지. `topology.py`(exchange/queue/binding/routing-key 상수 + 매핑 헬퍼), `in_memory.py`(결정론 stub), `rabbitmq.py`(pika BlockingConnection 기반 publish/subscribe/RPC, 스레드별 channel, 재연결 백오프). - - adapters/api: **제거** (`app.py`, `schemas.py`, `__init__.py`). - - runtime: `config.py` 에 `RabbitMQConfig(url, timeout)` 추가, `TREX_RABBITMQ_URL` 로딩, `TREX_API_HOST`/`TREX_API_PORT` 폐기. `daemon.py` 에서 uvicorn import/실행 제거, FastAPI base 제거, intent RPC consumer 스레드 + snapshot RPC consumer 스레드 추가, 기존 execution/ledger 스레드 유지, graceful shutdown 에 connection close 포함. - - persistence: 변경 없음 (intent_log 를 dedup 게이트로 재사용; 단, RabbitMQ consumer 전용 dedup 컬럼이 필요하면 design 결정 1 참조). - - pyproject: `pika>=1.3` 추가, `fastapi`/`uvicorn` 제거 (httpx 는 ledger 어댑터가 유지). -- Interfaces: `MessagingTransport.publish_event(routing_key, payload, mandatory=True)`, `MessagingTransport.consume_rpc(queue, handler)`, `MessagingTransport.call_rpc(queue, payload, timeout)`, 그리고 토폴로지 상수 + routing-key 빌더. - -## Key Decisions -1. **dedup 게이트 = intent_log 재사용(dcy-003/REQ-007 멱등 게이트 직접 활용)**. RabbitMQ intent consumer 는 `correlation_id`(= intent 멱등키, dcy-003 패턴)를 `IntentStore.try_insert`(application port) 의 `intent_id` 로 그대로 사용해 단일 dedup 진실원을 유지한다. consumer 전용 dedup table 은 두지 않는다 — at-least-once 재전달 시 두 번째 호출은 `try_insert==False` 로 끝나 슬롯 전이 0, 첫 결과를 그대로 response 로 회신한다. (장점: 새 스키마 0, 기존 멱등 가드 직접 활용. 단점: intent admission RPC 외 다른 RPC 가 늘면 별도 dedup 전략 필요 — 본 batch 범위에서는 intent + read-only snapshot 만 있어 충분.) -2. **ack-after-commit 시퀀스**: intent consumer 는 (a) `IntentService.submit_buy/sell` 호출 → (b) Postgres durable commit 안에서 `intent_log` row 와 슬롯 전이가 함께 atomically commit → (c) reply-to 큐로 admission 응답 publish → (d) 메시지 manual ack. (b)와 (c) 사이 크래시는 재전달 시 (1) 의 dedup 으로 재실행되지 않고, (c)와 (d) 사이 크래시는 재전달 시 같은 응답을 다시 보내고 ack — 동일 `correlation_id` 응답은 호출자가 dedup. publisher confirms 로 reply publish 의 broker 수락을 확인. -3. **단일 consumer + prefetch=1**: intent RPC consumer 는 단일 스레드 + `basic_qos(prefetch_count=1)` 로 FIFO 보장(불변식 A 단일 writer 와 정합). snapshot RPC consumer 는 read-only 이므로 prefetch>1 허용 가능하지만 본 batch 는 단일 + prefetch=1 로 일관성 우선. -4. **이벤트 페이로드 = 현재 상태 + 단조 시퀀스(슬롯 `updated_at` 또는 별도 version 컬럼)**. delta 가 아니라 last-wins 가능한 스냅샷을 운반해 순서 보증 불요. 미수신/중복은 스냅샷 RPC reconcile 로 보정. `daily_slot.updated_at` 을 시퀀스로 사용(이미 존재, 새 컬럼 0). -5. **publisher confirms + DLQ**: 이벤트 publisher 는 `confirm_delivery()` 모드 + `basic_publish(mandatory=True)`. 발행 거부(NACK) 또는 unroutable 은 별도 `trex.events.dlq` 큐로 라우팅, last_error 로그. intent RPC reply publish 도 동일. -6. **per-strategy durable queue + TTL/max-length**: 전략별 이벤트 queue 는 durable, queue 인자 `x-message-ttl=3600000`(1h) + `x-max-length=10000` + `x-overflow=drop-head`(drop-oldest). 초과/만료는 스냅샷 RPC reconcile 로 회복. -7. **재연결 백오프 상태 머신**: `connected → disconnected(트리거: socket error/heartbeat timeout) → reconnecting(백오프 1s,2s,4s,8s,...,30s cap) → connected`. 재연결 성공 후 토폴로지 재선언 + consumer rebind 책임은 어댑터 내부. application 레이어는 `transport.health()` 로 enum 조회. -8. **스레드별 channel + add_callback_threadsafe**: pika `BlockingConnection` 은 thread-safe 가 아니므로 (a) 각 consumer 스레드는 자기 connection+channel 을 소유하고 (b) execution/ledger 스레드의 이벤트 발행은 publisher 스레드 channel 의 `add_callback_threadsafe` 로 enqueue 한다. 또는 별도 publisher worker 스레드 도입 — design 검토 후 단순화를 위해 publisher worker 스레드 1개 + queue 기반 cross-thread enqueue 채택. -9. **HTTP 표면 0 회귀 가드**: 데몬 lifecycle 테스트가 기동 후 `import trex_core.runtime.daemon` 결과에 `fastapi`/`uvicorn`/`starlette` import 가 없음을 정적 가드로 검사. 추가로 포트 스캔(socket bind 0 검증) 통합 테스트(미가용 시 skip). -10. **broker outage = transport.health() != connected 일 때 application 분기**. `IntentService` 진입점은 변경하지 않고, 새 `intent RPC consumer` 가 message 를 수신해도 transport.health() == disconnected 면 reply publish 가 어차피 실패 → 그 경우 메시지 reject(requeue=False) + DLQ 로 격리(전략은 timeout 으로 감지 후 보류). 스냅샷 RPC 도 동일. **버퍼링 금지**: 어댑터에 로컬 outbox 를 두지 않는다(ledger outbox 는 별도 — broker→ledger 가 아니라 core→portfolio-hub 방향이라 영향 없음). - -## Edge Cases -- 동일 `correlation_id` 의 intent 메시지가 재전달 → `intent_log.try_insert==False` → 기존 결과 fetch 후 reply, 슬롯 전이 0. -- intent commit 직후 reply publish 전 크래시 → 재기동 후 메시지 재전달 → dedup 으로 같은 reply 재전송 → 호출자 dedup. 응답은 같은 `correlation_id`. -- pika connection drop 도중 publish/consume → 자동 재연결 백오프 + 채널 재선언 + consumer rebind. 미전송 message 는 broker 가 redeliver 시 dedup. -- 전략 queue down 중 trex-core 이벤트 발행 → durable queue 가 catch-up 보관 → 전략 복구 시 수신. TTL/max-length 초과분은 drop-oldest + reconcile. -- portfolio-hub 가 아닌 RabbitMQ 가 down → 신규 intent 거부(requeue=False + DLQ) + 스냅샷 RPC timeout → 전략 보류. core 자율 안전 매도(미구현, A-5)는 transport 와 독립 동작 전제. -- 전략 모듈 미전환 상태에서 cut-over 시 통신 단절 → release 단계 cut-over 순서(전략 모듈 → trex-core release) 합의 + stub 사전 검증으로 회피(R-2). -- HTTP 클라이언트(이전 전략 모듈/외부 tool)가 옛 `POST /v1/intents/buy` 호출 시도 → connection refused (HTTP 표면 0). release notes 에 cut-over 경고 명시. - -## Architecture Impact -- both (project-structure + runtime-flows). interface-contract 도 갱신한다(메시지 계약 + HTTP `/v1` 제거). - -## Reference Doc Update Plan -- project-structure.md: - - 트리에서 `trex_core/adapters/api/` 제거, `trex_core/adapters/messaging/` (`topology.py`, `rabbitmq.py`, `in_memory.py`) 추가. - - `trex_core/application/messaging.py` (Protocol + DTO) 추가. - - runtime/daemon 의 스레드 구성(intent RPC + snapshot RPC + execution + ledger + publisher worker) 반영. - - 의존성 변경(`pika` 추가, `fastapi`/`uvicorn` 제거) 반영. -- runtime-flows.md: - - 기존 매수/매도 intent HTTP 흐름 제거. - - 신규: 스냅샷 RPC 흐름, intent admission RPC 흐름(2단계 시맨틱: admission 동기 + fill 비동기 이벤트), 체결/슬롯 이벤트 발행·구독 흐름(routing key `{event_type}.{strategy_id}.{symbol}` + per-strategy queue), broker degrade 분기 흐름, 재연결 백오프, 스레드 구성. -- interface-contract.md: - - HTTP `/v1` 섹션 제거. - - 신규: 메시지 계약(스냅샷 RPC `trex.snapshot.req` / intent RPC `trex.intent.req` / 이벤트 topic `trex.events`), routing key 형식, RPC payload/response 셰이프, `correlation_id`/reply-to 규약, durable/persistent/ack 정책, publisher confirms + DLQ, TTL/max-length, 어댑터 신선도 상태(`connected/disconnected/reconnecting`) 노출. -- data-model.md: 변경 없음 — 이벤트 페이로드는 entity 가 아니라 interface contract 가 담는 편이 자연스러움(REQ-026 Notes 참조). diff --git a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/implementation.md b/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/implementation.md deleted file mode 100644 index de40b6f..0000000 --- a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/implementation.md +++ /dev/null @@ -1,66 +0,0 @@ -# BAT-007 Implementation - -- Status: complete -- Batch: BAT-007 - -## Plan Summary -- `MessagingTransport` Protocol(application) + `InMemoryTransport` stub + `RabbitMQTransport`(pika 동기) 어댑터를 신설하고, 데몬을 sync+thread 4 스레드(intent RPC consumer + snapshot RPC consumer + execution + ledger) 구조로 재구성했다. intent RPC consumer 는 `IntentService.submit_*` 를 호출해 Postgres durable commit 후 manual ack 하며, `intent_log` 멱등 게이트가 중복 메시지를 흡수한다. ExecutionService 에 event emitter hook 을 더해 fill 전이 직후 `trex.events` 토픽으로 routing key `{event_type}.{strategy_id}.{symbol}` 이벤트를 발행한다(publisher confirms + DLQ). 어댑터의 connection 상태가 application 으로 노출되어 broker outage 시 신규 매매가 보수적으로 중단된다. `adapters/api/` 트리와 `fastapi`/`uvicorn` 의존을 제거하고 baseline 3문서를 정합시켰다. - -## Changed Files -- 신규 - - `trex_core/application/messaging.py` — `MessagingTransport` Protocol, `RpcRequest`/`RpcResponse`, `TransportHealth`/`TransportUnavailableError`/`RpcTimeoutError`. - - `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler` / `make_snapshot_rpc_handler` / `slot_transition_event_payload`. - - `trex_core/adapters/messaging/__init__.py`, `topology.py`(상수 + routing key 빌더 + STRATEGY_QUEUE_ARGS), `in_memory.py`(결정론 stub + 가용성 토글 + DLQ 수집), `rabbitmq.py`(pika BlockingConnection + 재연결 백오프 + publisher confirms + manual ack-after-handler). - - `tests/adapters/messaging/{__init__,test_topology,test_in_memory_transport,test_rabbitmq_transport,test_rabbitmq_intent_e2e}.py` — REQ-021/022/023 검증 (RabbitMQ 통합 5종 + intent dedup E2E 2종 + stub/토폴로지 11종). - - `tests/application/{test_messaging_intent_consumer,test_messaging_snapshot_consumer,test_messaging_event_publisher}.py` — REQ-021/022/023/024 handler·이벤트 흐름·degrade 분기. - - `tests/runtime/test_daemon_no_http_surface.py` — REQ-025 HTTP 표면 0 회귀 가드 5종. -- 수정 - - `trex_core/application/execution.py` — `SlotEventEmitter` callback, `ExecutionService.__init__` 에 `event_emitter` 인자 추가, `_on_filled` 에서 transition + ledger enqueue 후 `_emit_event` 호출(publish 실패가 슬롯 전이를 롤백하지 않음). - - `trex_core/runtime/config.py` — `RabbitMQConfig` 추가, `TREX_RABBITMQ_URL`/`TREX_RABBITMQ_TIMEOUT` 로딩, `DaemonConfig.host/port` 와 `TREX_API_HOST`/`TREX_API_PORT` 폐기. - - `trex_core/runtime/daemon.py` — uvicorn/FastAPI import 제거, `Daemon` 클래스로 4 스레드(execution/ledger/intent-rpc/snapshot-rpc) + 이벤트 publisher transport + `signal` 기반 graceful shutdown + connection close 보장. - - `tests/domain/test_domain_import_sweep.py` — 금지 prefix 에 `pika`, `starlette` 추가. - - `tests/conftest.py` — 게이트 env 설명을 RabbitMQ/KIS 포함하도록 갱신. - - `pyproject.toml` — `pika>=1.3` 추가, `fastapi`/`uvicorn` 제거, `httpx` 를 dev → runtime 의존성으로 이동(ledger 어댑터가 사용). - - `.env.example` — `TREX_RABBITMQ_URL`/`TREX_RABBITMQ_TIMEOUT` 추가, `TREX_API_HOST`/`TREX_API_PORT` 제거. - - `docs/runtime-flows.md` / `docs/interface-contract.md` / `docs/project-structure.md` — messaging 레이어 반영, HTTP `/v1` 섹션 제거, sync+thread 데몬 + 4 스레드 구성 반영 (REQ-026). - - `docs/discovery/dcy-002_…md` — transport supersede 메모 (REQ-025 AC-5 cross-reference). -- 제거 - - `trex_core/adapters/api/` 전체 (`app.py`, `schemas.py`, `__init__.py`). - - `tests/adapters/api/` 전체. - -## Execution Log -1. baseline 테스트 109 passed / 12 skipped 확인. -2. RabbitMQ 가용성 확인: `127.0.0.1:5672` (admin/admin) 으로 `pika.BlockingConnection` 성공. -3. `pika>=1.3` 설치, `pyproject.toml` 의 `fastapi`/`uvicorn` 제거. -4. REQ-021: `topology.py` 에 토폴로지 상수 + routing key 빌더 + STRATEGY_QUEUE_ARGS(TTL/max-length/drop-head/DLX) 추가. -5. REQ-022: `application/messaging.py` (Protocol + DTO + TransportHealth), `adapters/messaging/in_memory.py` (process-local pub/sub + sync RPC + 가용성 토글), `adapters/messaging/rabbitmq.py` (pika BlockingConnection + 재연결 백오프 + publisher confirms + manual ack-after-handler). -6. REQ-021/022/023: `application/messaging_handlers.py` 에 intent/snapshot RPC handler + 이벤트 payload builder. intent handler 는 `correlation_id` 를 intent_id 로 재사용해 `intent_log` 멱등 게이트(REQ-007)를 dedup source 로 활용. -7. REQ-023: ExecutionService 에 event emitter hook 추가. publisher confirms + DLQ 라우팅은 RabbitMQTransport 가 담당. per-strategy durable queue 는 stub 과 실 broker 둘 다 검증. -8. REQ-024: `TransportHealth` enum, `TransportUnavailableError` 즉시-실패, 재연결 백오프(1s..30s cap), stub `set_available(False)` 회귀 가드. -9. REQ-025: `adapters/api/` 전체 제거, `runtime/daemon.py` 를 `Daemon` 클래스 + 4 스레드 + signal handler 로 재작성, `runtime/config.py` 의 `host`/`port` 필드 + `TREX_API_HOST/PORT` 로딩 제거. `.env.example` 정리. -10. REQ-026: baseline 3문서(runtime-flows / interface-contract / project-structure) messaging 섹션 신설 + HTTP `/v1` 섹션 제거 + sync+thread 4 스레드 구성 반영. dcy-002 헤더에 transport supersede 메모 추가(dcy-005 는 이미 cross-reference 완료). -11. 회귀 가드 추가: `tests/runtime/test_daemon_no_http_surface.py` (import 가드 + `adapters/api` 트리 0 + grep fastapi/uvicorn 0 + pyproject 0 + config env 0), `tests/domain/test_domain_import_sweep.py` 에 pika/starlette 금지 prefix. -12. 최종 검증: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/"` 로 `pytest -q` → **135 passed / 12 skipped** (PG/KIS 게이트만 skip; 실 RabbitMQ 통합 7건 모두 실행/통과). `ruff check trex_core tests` → All checks passed. - -## Validation -- 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" .venv/bin/python -m pytest -q` - - 결과: `135 passed, 12 skipped`. -- 명령: `.venv/bin/ruff check trex_core tests` - - 결과: `All checks passed!`. -- 추가 테스트 ~26개: - - 토폴로지: 4종. - - InMemoryTransport stub: 5종(routing pattern, RPC dispatch, degrade, DLQ). - - RabbitMQTransport 통합: 5종(topology declare 멱등, RPC roundtrip, per-strategy event filter, RPC timeout, close). - - RabbitMQ intent E2E: 2종(correlation_id dedup, queue topology PRECONDITION assertion). - - intent handler: 7종(admit, dedup, no_idle, sell from holding, bad payload, unknown type, correlation_id fallback). - - snapshot handler: 3종(all/open view, strategy filter). - - event publisher: 3종(routing + state + sequence, per-strategy filter, publish failure non-blocking). - - HTTP-표면-0 가드: 5종(import 가드 + 트리 부재 + grep + pyproject + config env). -- RabbitMQ 통합 evidence: 실 broker(127.0.0.1:5672, admin/admin) 로 topology declare, RPC roundtrip, per-strategy queue 필터, RPC timeout, intent E2E dedup 통과. - -## Remaining Risks -- core 자율 안전 매도(A-5, REQ-024 Notes) 는 본 batch 미구현. broker 장기 outage 시 열린 포지션 자동 보호 없음 — 운영자 수동 대응 의존. 별도 Discovery 로 트래킹 필요. -- 전략 모듈 측 RabbitMQ 클라이언트 전환은 본 batch 범위 외. release 단계에서 cut-over 순서(전략 모듈 → trex-core) 합의 필요 (R-2 / R-5 dcy-006 risk). -- pika thread-safety 우회는 스레드별 transport 인스턴스 + 짧은 채널 lifecycle 로 단순화했으나, 장기 부하/연결 누수 가능성은 운영 환경 모니터링으로 보강 필요(본 batch 단위 테스트로는 충분). -- snapshot RPC 응답 셰이프는 dcy-005 logical 계약이 아직 REQ 정규화되지 않아 stub-shape 수준(슬롯 목록 + capacity). dcy-005 REQ 확정 시 후속 batch 에서 정합 보강(R-7). -- `httpx` 의존을 dev → runtime 으로 이동했다. `portfolio-hub` ledger 어댑터(BAT-006) 가 실제로 사용하므로 적정 위치이며 BAT-006 verification 에 영향 없음. diff --git a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/index.md b/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/index.md deleted file mode 100644 index cd185b6..0000000 --- a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-007: 전략 모듈 RabbitMQ transport 전환 - -- Status: released -- Profile: standard -- Source Discovery: dcy-006 -- Included REQ: REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026 -- Excluded REQ: 없음 - -## Summary -- dcy-006 transport 슬라이스(trex-core ↔ 전략 모듈 통신을 HTTP `/v1` API 에서 RabbitMQ(AMQP) 로 완전 전환)를 하나의 delivery 단위로 묶는다. 토폴로지/계약(REQ-021) → transport 포트 + RabbitMQ 어댑터(`pika` 동기) + stub + sync+thread daemon 재구성(REQ-022) → delivery 신뢰성(effectively-once, durable, DLQ, publisher confirms, REQ-023) → 가용성 degrade 정책(NFR-3, REQ-024) → HTTP `/v1` API + healthz + adapters/api + fastapi/uvicorn 의존 완전 제거(REQ-025) → baseline 3문서 정합(REQ-026) 까지 한 batch 로 deliver 한다. - -## Scope -- In Scope: - - REQ-021: 통신 토폴로지(스냅샷 RPC `trex.snapshot.req`, intent admission RPC `trex.intent.req` + RabbitMQ direct reply-to + `correlation_id`, 이벤트 topic exchange `trex.events` + routing key `{event_type}.{strategy_id}.{symbol}`) 와 요청/이벤트 매핑 원칙(view·strategy_id payload, event routing key) 선언. - - REQ-022: `trex_core/application/messaging.py` 신규 `MessagingTransport` Protocol, `trex_core/adapters/messaging/{rabbitmq,in_memory}.py`, 데몬을 sync+thread (intent RPC consumer + snapshot RPC consumer + execution/ledger 스레드)로 재구성, pika 자동 재연결 직접 구현. NFR-1 (도메인 순수성) import 스윕 가드 확장. - - REQ-023: intent durable+persistent + manual ack-after-commit + dedup(dcy-003 correlation_id), 단일 consumer + prefetch=1 FIFO, 이벤트 durable per-strategy queue + 상태/단조 시퀀스 운반 + last-wins, publisher confirms + DLQ, TTL/max-length drop-oldest + 스냅샷 reconcile. - - REQ-024: broker outage 시 버퍼링 금지 + 신규 매매 보수적 중단, 어댑터 connection 신선도(`connected/disconnected/reconnecting`) 노출, core 자율 안전 매도 독립 정책 contract 명시(구현은 별도 Discovery, A-5). - - REQ-025: `trex_core/adapters/api/` 전체 제거, `trex_core/runtime/daemon.py` 에서 uvicorn import/실행 제거, `runtime/config.py` 의 `TREX_API_HOST`/`TREX_API_PORT` 폐기, `pyproject.toml` 의 `fastapi`/`uvicorn` 의존 정리, dcy-002·dcy-005 의 HTTP 가정 cross-reference 갱신. - - REQ-026: `docs/interface-contract.md`/`docs/runtime-flows.md`/`docs/project-structure.md` 정합 (메시지 계약·MQ 흐름·messaging 어댑터·sync+thread 런타임 반영, HTTP `/v1` 섹션 제거). -- Out of Scope (후속 Discovery / 다른 Discovery 소관): - - 스냅샷/intent 의 logical 계약 자체 변경(dcy-005/002/003 소관), 전략 의사결정 로직(전략 모듈 책임), RabbitMQ 클러스터/HA 운영 세부, portfolio-hub 등 다른 외부 연동의 transport, core 자율 안전 매도(stop-loss·forced_close 자동 트리거) 실제 구현(A-5, 별도 Discovery). - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery dcy-006 단일. 핵심 확정 결정: RabbitMQ 가 HTTP 를 **대체**(병행 없음, OQ-1) + HTTP 표면 0(`healthz` 포함, OQ-7) + 3패턴 토폴로지(스냅샷 RPC + intent RPC + 이벤트 topic, OQ-2) + effectively-once(at-least-once + idempotency, OQ-3) + 보수적 degrade(버퍼링 금지·신규 매매 중단·core 안전 매도 독립, OQ-4) + pika 동기 + sync+thread 런타임(OQ-5) + `TREX_RABBITMQ_URL` env(OQ-6). -- 핵심 리스크: 가용성 결합 — RabbitMQ 가 SPOF 성격(R-3 High, HTTP fallback 없음). REQ-024 degrade 정책 + core 안전 매도 독립(A-5 미구현, 별도 트래킹)으로 신규 매매 중단으로 영향 한정. big-bang 마이그레이션(R-5) 은 본 batch 가 REQ-022/025 동시 deliver 로 회피. -- DATA(비차단): 실 RabbitMQ broker 인스턴스/자격증명 provision 은 delivery/운영 작업 — verification 은 stub `InMemoryTransport` 로 충분 검증 가능하며 실 broker 미가용은 PG/KIS 와 동일하게 게이트 skip 으로 분리한다. diff --git a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/planning.md b/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/planning.md deleted file mode 100644 index 68d59fe..0000000 --- a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/planning.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-007 Planning - -- Status: draft -- Batch: BAT-007 -- Profile: standard - -## Included REQ -- REQ-021 (Integration): messaging 토폴로지 + 메시지 계약 매핑 -- REQ-022 (Integration): messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon -- REQ-023 (Exception): messaging delivery 신뢰성 (effectively-once + durable + DLQ + publisher confirms) -- REQ-024 (Non-Functional): RabbitMQ 가용성 degrade 정책 -- REQ-025 (Interface): HTTP `/v1` API 완전 제거 (healthz 포함, HTTP 표면 0) -- REQ-026 (Documentation): messaging 레이어 baseline 문서 정합 - -## Delivery Plan -- 의존 순서대로 구현한다. - 1. REQ-021: `trex_core/adapters/messaging/topology.py`(또는 등가) 에 exchange/queue/binding/routing-key 정의. 매핑 원칙(view·strategy_id payload, event routing key)을 헬퍼로 고정. - 2. REQ-022: `trex_core/application/messaging.py` Protocol(publish/subscribe/RPC), `adapters/messaging/in_memory.py`(결정론 stub), `adapters/messaging/rabbitmq.py`(pika BlockingConnection, 스레드별 channel + `add_callback_threadsafe`, 자체 재연결 백오프). `runtime/config.py` 에 `PortfolioHubConfig` 옆에 `RabbitMQConfig(url, timeout)` 추가, `TREX_RABBITMQ_URL` env 로딩. - 3. REQ-025: `runtime/daemon.py` 에서 uvicorn import/실행과 FastAPI base 제거, `_execution_loop`/`_ledger_loop` 옆에 `_intent_rpc_consumer`/`_snapshot_rpc_consumer` 스레드 추가. `runtime/config.py` 의 `TREX_API_HOST`/`TREX_API_PORT` 폐기. `trex_core/adapters/api/` 전체 트리 + 해당 테스트 제거. `pyproject.toml` 의 `fastapi`/`uvicorn` 의존 제거. - 4. REQ-023: consumer 에 manual ack-after-postgres-commit + correlation_id dedup (intent_log 또는 dedicated dedup store), 단일 consumer + prefetch=1, 이벤트 publisher 에 publisher confirms + DLQ exchange 라우팅, per-strategy durable queue + TTL/max-length. - 5. REQ-024: 어댑터의 connection 상태 enum(`connected/disconnected/reconnecting`) 노출 + application 레이어 broker-down 분기(스냅샷 RPC timeout → 매매 보류, intent publish 실패 → 거부), 버퍼링 금지 회귀 가드 테스트. - 6. REQ-026: REQ-021~025 구현이 안정화된 뒤 `docs/interface-contract.md`/`docs/runtime-flows.md`/`docs/project-structure.md` 3문서를 messaging 레이어로 정합 (HTTP `/v1` 섹션 제거, messaging 섹션 신설, sync+thread 런타임 반영). -- 데몬 reconcile 시점은 변경하지 않는다(REQ-014 broker reconcile, REQ-019 ledger outbox 처리 그대로 유지). - -## Dependencies -- Internal: - - REQ-022 는 REQ-021 의 토폴로지 정의 선행. - - REQ-023 은 REQ-022 의 어댑터 ack/publish 경로에 통합. - - REQ-024 는 REQ-022 의 connection 상태 노출에 의존. - - REQ-025 는 REQ-022 의 sync+thread 런타임이 동시에 들어가야 통신 단절(R-5) 회피. - - REQ-026 은 REQ-021~025 구현 결과에 의존. - - 기존 자산: BAT-002 의 `IntentService`/`SlotService` 호출 경계, BAT-004 의 broker execution + ledger outbox 스레드, BAT-006 의 ledger writer 스레드는 그대로 재사용한다. - - dcy-003 의 멱등키(`correlation_id` 패턴)와 `intent_log` durable dedup 게이트(REQ-007/intent_log) 를 RabbitMQ intent consumer 의 dedup 으로 재사용한다. -- External: - - RabbitMQ broker 인스턴스 + 자격증명(`amqp://user:pass@host:5672/vhost`) — `TREX_RABBITMQ_URL` env 로 주입. 실 broker 미가용 시 stub 검증 + RabbitMQ 통합 테스트는 게이트 skip. - - `pika>=1.3` 신규 의존성 추가, `fastapi`/`uvicorn` 의존 제거. - - 전략 모듈 측 RabbitMQ 클라이언트 전환(외부 작업) — 본 batch 는 trex-core 측 transport 만 제공한다. 전략 모듈 전환 일정과 cut-over 는 운영 release 단계 의제. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 transport 포트 + 외부 어댑터 + 데몬 런타임 재구성(uvicorn 제거 + sync+thread, intent/snapshot RPC consumer 스레드 신설) + delivery 신뢰성 규약(ack 시점, dedup 게이트, DLQ, publisher confirms, TTL/max-length) + degrade 정책의 connection 상태 머신 + HTTP 어댑터 전체 제거가 구조/인터페이스/런타임을 동시에 흔든다. 구현 전 토폴로지(exchange/queue/routing key/reply-to), ack-after-commit 시퀀스, 재연결 백오프 + degrade 상태 머신, 스레드별 채널 관리 규약을 design.md 에 고정해야 한다. - -## Milestones -1. M1 — REQ-021: 토폴로지 선언(exchanges/queues/bindings/routing keys) + 매핑 헬퍼 + stub 어댑터 RPC/event 골격. -2. M2 — REQ-022: `MessagingTransport` 포트 + `InMemoryTransport`(stub) + `RabbitMQTransport`(pika 동기) + `RabbitMQConfig` + `TREX_RABBITMQ_URL` 로딩. -3. M3 — REQ-025: `adapters/api/` 제거 + `runtime/daemon.py` 의 uvicorn/FastAPI 제거 + intent/snapshot RPC consumer 스레드 신설 + `pyproject.toml`/`.env.example` 정리. -4. M4 — REQ-023: ack-after-commit + correlation_id dedup + 단일 consumer prefetch=1 + 이벤트 publisher confirms + DLQ + per-strategy durable queue + TTL/max-length. -5. M5 — REQ-024: connection 상태 노출 + application broker-down 분기 + 회귀 가드(버퍼링 0, 복구 후 자동 재생 0). -6. M6 — REQ-026: baseline 3문서 정합 + 변경 이력 추가. -7. M7 — 통합 verification: stub 어댑터 RPC/이벤트 lifecycle, dedup, durable+catch-up, conflict/DLQ, degrade 분기, HTTP 표면 0(포트/grep), 도메인 import 스윕, baseline 문서-코드 정합 리뷰. - -## Risks -- R-1 (High, dcy-006 R-3): 가용성 결합 — RabbitMQ 가 SPOF 성격, HTTP fallback 없음. 대응: REQ-024 degrade 정책으로 신규 매매 중단으로 영향 한정. **A-5 (core 자율 안전 매도) 가 미구현 상태**이므로 운영 release 전에 broker outage 시 열린 포지션 처리 절차(운영 도구 또는 수동 SQL)를 별도 트래킹. -- R-2 (Medium, dcy-006 R-5): big-bang 마이그레이션 — HTTP 제거 시점과 전략 모듈 MQ 전환이 어긋나면 통신 단절. 대응: REQ-021/022/023/025 한 batch + stub 사전 검증 + release 단계에서 cut-over 순서(전략 모듈 → trex-core release) 합의. -- R-3 (Medium): 멱등 dedup store 선택 — intent_log(BAT-002, REQ-007) 를 그대로 dedup 게이트로 재사용할지, RabbitMQ consumer 전용 dedup table 을 새로 둘지. 대응: design 단계에서 결정 후 ack 시퀀스에 반영. 기본 추정은 intent_log 재사용(dcy-006 OQ-3 의 dcy-003 correlation_id idempotency 명시). -- R-4 (Medium): pika 자동 재연결 미지원으로 백오프 + 채널 재선언 + consumer rebind 를 직접 구현해야 함. 잘못 짜면 메시지 유실/중복 위험. 대응: design 단계에서 재연결 상태 머신 + 재선언 책임 명시 + 회귀 테스트. -- R-5 (Low): pika BlockingConnection 의 thread-safety 미보장 — 스레드별 connection/channel 또는 `add_callback_threadsafe` 규약을 design 으로 고정하고 stub/실 어댑터 둘 다 회귀 테스트. -- R-6 (Low): 실 RabbitMQ 인프라 미가용 — verification evidence 가 stub 수준에 집중될 수 있음. 대응: PG/KIS 와 동일하게 `TREX_RABBITMQ_TEST_URL` 게이트로 분리해 미가용 시 skip 명시. 실 통합 evidence 는 운영 단계에서 보강. -- R-7 (Low): dcy-005(스냅샷 read 계약) 가 아직 REQ 로 정규화되지 않은 상태에서 본 batch 의 snapshot RPC 가 먼저 도입됨. 응답 셰이프는 stub 수준에서 잠정 정의하고, dcy-005 REQ 가 확정되면 contract 정합을 후속 batch 에서 보강. diff --git a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/verification.md b/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/verification.md deleted file mode 100644 index c38e156..0000000 --- a/docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/verification.md +++ /dev/null @@ -1,104 +0,0 @@ -# BAT-007 Verification - -- Status: approved -- Batch: BAT-007 - -## Acceptance Mapping -- REQ-021: - - AC-1: 데몬 기동 시 `trex.snapshot.req`, `trex.intent.req` 큐와 `trex.events` 토픽 익스체인지 선언 evidence (stub + 실 RabbitMQ). - - AC-2: 스냅샷 RPC 응답 logical 필드 + `correlation_id` 일치 evidence. - - AC-3: intent admission RPC 가 admission 응답을 reply-to 로 반환하고 체결/종료는 후속 이벤트로 발행되는 2단계 evidence. - - AC-4: strategy_id=A 이벤트가 `*.A.#` 바인딩 queue 만 수신 evidence (stub + 실 RabbitMQ). - - AC-5: 요청 메시지 payload 검사로 `view`/`strategy_id` 가 payload 에 있고 routing key 에 없음 evidence. -- REQ-022: - - AC-1: `InMemoryTransport` stub publish/subscribe/RPC 외부 broker 없이 동작 evidence. - - AC-2: `RabbitMQTransport` 가 `TREX_RABBITMQ_URL` 로 connection/channel 열고 송수신 evidence (게이트 통과, 실 broker 실행됨). - - AC-3: `Daemon` lifecycle 에서 intent/snapshot RPC + execution/ledger 스레드 동시 동작 + graceful shutdown(connection close 포함) evidence. `uvicorn.run` 의존 0 evidence (정적 import 가드). - - AC-4: connection drop 후 백오프 재연결 + degrade 정책에 따른 신규 처리 중단 evidence (재연결 코드 + stub 가용성 토글 회귀 가드). - - AC-5: `tests/domain/test_domain_import_sweep.py` 가 `pika`/`adapters.messaging` 0 import 확인 (NFR-1). -- REQ-023: - - AC-1: 동일 `correlation_id` intent 메시지 2회 처리 → 슬롯 전이 1회 + dedup evidence (NFR-2). - - AC-2: ack-after-commit 시퀀스 + intent_log durable dedup 으로 재전달 시 추가 전이 0 evidence (간접: dedup 테스트가 ack 순서를 전제로 통과). - - AC-3: intent 큐 토폴로지가 durable + DLQ pointer + prefetch=1 검사 evidence (실 broker PRECONDITION_FAILED 검사로 확인). - - AC-4: per-strategy durable queue 의 catch-up 동작은 토폴로지 args (`x-message-ttl`/`x-max-length`/`x-overflow=drop-head`/`x-dead-letter-exchange`) + 실 broker 의 per-strategy filter 테스트로 evidence. - - AC-5: 이벤트 페이로드에 `sequence_at` 단조 토큰 포함 evidence (테스트 단언). - - AC-6: publisher confirms + UnroutableError → DLQ 라우팅 코드 + stub DLQ 수집기 evidence. - - AC-7: TTL/max-length drop-oldest 는 토폴로지 args 선언으로 evidence (실 broker queue arguments 일치). -- REQ-024: - - AC-1: broker 불가 상태에서 publish/RPC 시 `TransportUnavailableError` 즉시 실패 + 슬롯 전이 0 evidence. - - AC-2: 로컬 버퍼링 없음 — `MessagingTransport` 인터페이스에 enqueue 가 없음 + 어댑터 코드 review. - - AC-3: `transport.health()` enum 노출 + 어댑터 `_mark_disconnected` 상태 머신 evidence. - - AC-4: A-5 정책 contract 가 REQ-024 Notes 와 BAT-007 implementation/verification Remaining Risks 에 명시 evidence. - - AC-5: 어댑터에 outbox/버퍼 없음 (코드 review) + 발행 실패가 슬롯 전이를 롤백하지 않음 evidence. -- REQ-025: - - AC-1: `tests/runtime/test_daemon_no_http_surface.py::test_adapters_api_tree_does_not_exist` + `::test_no_fastapi_or_uvicorn_in_trex_core_source` evidence. - - AC-2: 데몬 import 가드 (`test_daemon_module_has_no_fastapi_or_uvicorn_import`) evidence. - - AC-3: `test_config_env_no_longer_references_api_host_or_port` evidence. - - AC-4: `test_pyproject_dependencies_have_no_fastapi_or_uvicorn` evidence. - - AC-5: dcy-002 헤더 transport supersede 메모 + dcy-005 가 이미 dcy-006 cross-reference 갖추고 있음 evidence. - - AC-6: `Daemon` 클래스는 `signal.SIGINT/SIGTERM` 만 처리하고 HTTP 포트 바인딩 없음 (코드 review). -- REQ-026: - - AC-1: 3 baseline 문서 messaging 섹션 신설 + `Interface Summary`/`Covered Entry Points`/구조 트리에 반영 evidence. - - AC-2: baseline ↔ 코드 정합 manual review evidence. - - AC-3: 3 baseline 문서 Change Log 에 2026-06-05 BAT-007 항목 evidence. - - AC-4: `docs/interface-contract.md` 의 HTTP `/v1` 섹션 제거 + `docs/project-structure.md` 트리에서 `adapters/api/` 제거 evidence. - -## Evidence - -### Test -- 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" .venv/bin/python -m pytest -q` - - 결과: `135 passed, 12 skipped` (PG/KIS 게이트만 skip; 실 RabbitMQ 통합 7건 모두 실행/통과). -- REQ-021 AC-1: `tests/adapters/messaging/test_rabbitmq_transport.py::test_declare_topology_creates_core_entities` 가 두 번 declare 후 멱등 + `TransportHealth.CONNECTED` 확인. -- REQ-021 AC-2: `tests/application/test_messaging_snapshot_consumer.py::test_snapshot_returns_all_slots_and_capacity` 응답 셰이프 확인 + `tests/adapters/messaging/test_rabbitmq_transport.py::test_rpc_roundtrip` 가 `correlation_id` 일치 확인. -- REQ-021 AC-3: `tests/application/test_messaging_intent_consumer.py::test_buy_admission_returns_admitted_and_transitions_slot` + `tests/application/test_messaging_event_publisher.py::test_fill_emits_slot_filled_event_with_routing_key_and_state` 의 조합으로 admission 동기 + fill 비동기 이벤트 분리 확인. -- REQ-021 AC-4: `tests/application/test_messaging_event_publisher.py::test_event_for_strategy_a_does_not_reach_strategy_b_queue` + `tests/adapters/messaging/test_rabbitmq_transport.py::test_event_publish_and_per_strategy_consume` (실 RabbitMQ). -- REQ-021 AC-5: `tests/adapters/messaging/test_topology.py::test_event_routing_key_format` + `tests/adapters/messaging/test_topology.py::test_strategy_queue_name_and_binding_pattern`. -- REQ-022 AC-1: `tests/adapters/messaging/test_in_memory_transport.py` 5종. -- REQ-022 AC-2: `tests/adapters/messaging/test_rabbitmq_transport.py` 5종 (모두 실 broker 실행 통과). -- REQ-022 AC-3: `tests/runtime/test_daemon_no_http_surface.py::test_daemon_module_has_no_fastapi_or_uvicorn_import` + `Daemon` 4 스레드 + close 코드 review. -- REQ-022 AC-4: `RabbitMQTransport._sleep_backoff` + `_mark_disconnected` 코드 + `tests/adapters/messaging/test_in_memory_transport.py::test_unavailable_transport_raises_on_publish_and_rpc` degrade 분기 회귀. -- REQ-022 AC-5: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` (`pika`/`starlette` 포함 금지 prefix 갱신). -- REQ-023 AC-1: `tests/application/test_messaging_intent_consumer.py::test_duplicate_correlation_id_dedups_via_intent_log` + `tests/adapters/messaging/test_rabbitmq_intent_e2e.py::test_intent_admission_rpc_dedups_duplicate_correlation_id_over_rabbitmq` (실 RabbitMQ). -- REQ-023 AC-2: 위 dedup 테스트가 Postgres durable commit 전제(intent_log) 위에서 통과 — ack-after-commit 순서 evidence. `Daemon._intent_rpc_loop` 가 manual ack-after-handler 사용. -- REQ-023 AC-3: `tests/adapters/messaging/test_rabbitmq_intent_e2e.py::test_intent_queue_topology_uses_dlq_pointer` 가 PRECONDITION_FAILED 로 큐 argument(durable + DLQ pointer) 일치 확인. -- REQ-023 AC-4: `topology.STRATEGY_QUEUE_ARGS` (`x-message-ttl=3.6e6`, `x-max-length=10000`, `x-overflow=drop-head`, `x-dead-letter-exchange`) + per-strategy filter 테스트가 broker 단 필터링 확인. -- REQ-023 AC-5: `tests/application/test_messaging_event_publisher.py::test_fill_emits_slot_filled_event_with_routing_key_and_state` 가 `sequence_at` 포함 확인. -- REQ-023 AC-6: `RabbitMQTransport.publish_event` 의 `UnroutableError` → `_publish_dlq` + `_on_message` 의 handler exception NACK(requeue=False) + `tests/adapters/messaging/test_in_memory_transport.py::test_publish_event_with_no_subscriber_routes_to_dlq` evidence. -- REQ-023 AC-7: `STRATEGY_QUEUE_ARGS["x-overflow"] == "drop-head"` (`tests/adapters/messaging/test_topology.py::test_strategy_queue_args_have_ttl_and_max_length`) + 스냅샷 reconcile 경로(snapshot RPC handler) 존재. -- REQ-024 AC-1: `tests/adapters/messaging/test_in_memory_transport.py::test_unavailable_transport_raises_on_publish_and_rpc` + `tests/application/test_messaging_event_publisher.py::test_publish_failure_does_not_break_execution_loop`. -- REQ-024 AC-2: `MessagingTransport` Protocol 에 outbox/enqueue 가 없음 + 어댑터 코드 review (publish 실패 → 즉시 raise/DLQ, 로컬 보관 0). -- REQ-024 AC-3: `TransportHealth` enum + `InMemoryTransport.set_available`/`RabbitMQTransport._mark_disconnected` 코드 + AC-1 테스트가 disconnected 상태 노출 검증. -- REQ-024 AC-4: REQ-024 본문 Notes + BAT-007 implementation/verification Remaining Risks 에 명시. -- REQ-024 AC-5: publisher 실패 시 슬롯 상태 유지 테스트(`test_publish_failure_does_not_break_execution_loop`). -- REQ-025 AC-1~4: `tests/runtime/test_daemon_no_http_surface.py` 5종 모두 통과. -- REQ-025 AC-5: `docs/discovery/dcy-002_…md` 헤더 supersede 메모 + `docs/discovery/dcy-005_…md` 가 이미 dcy-006 cross-reference 보유. -- REQ-025 AC-6: `Daemon.run` 의 signal handler + 포트 바인딩 0(`ss -ltn` 으로 데몬 기동 후 확인 가능; 본 batch 는 import 가드로 충분 — 실 environment 에는 별도 운영 절차). -- REQ-026 AC-1/AC-3: `docs/interface-contract.md` / `docs/runtime-flows.md` / `docs/project-structure.md` 모두에 messaging 섹션 + Change Log 2026-06-05 BAT-007 항목 추가됨. -- REQ-026 AC-2: 본 verification 의 Manual 섹션 + code-search evidence. -- REQ-026 AC-4: `grep "HTTP \`/v1\`" docs/interface-contract.md` → 제거 안내 1건만 잔존(invalidate notice). 트리에서 `adapters/api/` 제거됨. - -### Manual -- baseline ↔ 코드 정합 리뷰 (REQ-026 AC-1/AC-2): - - `docs/interface-contract.md` 의 `Interface: RabbitMQ messaging` 토폴로지/패턴/payload/Reliability/Degrade 표가 `trex_core/adapters/messaging/topology.py` 의 상수와 `trex_core/adapters/messaging/rabbitmq.py` 의 publish/consume 동작, `trex_core/application/messaging.py` 의 `MessagingTransport` Protocol 시그니처와 정합. - - `docs/runtime-flows.md` 의 `데몬 lifecycle`/`스냅샷 RPC`/`intent admission RPC`/`이벤트 발행·구독`/`broker degrade 분기` 흐름이 `trex_core/runtime/daemon.py:Daemon` 클래스(setup/start/shutdown + 4 worker) + `trex_core/application/execution.py:_emit_event` + `trex_core/application/messaging_handlers.py` 와 정합. - - `docs/project-structure.md` 디렉토리 트리에서 `trex_core/adapters/api/` 제거 + `trex_core/adapters/messaging/` 추가 + `trex_core/application/messaging.py`/`messaging_handlers.py` 추가 + daemon 의 4 스레드 구성 반영. 실제 디렉토리 트리(`ls trex_core/adapters/`, `ls tests/adapters/`) 와 일치. -- code-search (REQ-022 AC-5, REQ-025 AC-1): - - `grep -r "import httpx\|from httpx" trex_core/domain/` → 0건. - - `grep -r "import pika\|from pika" trex_core/domain/` → 0건. - - `grep -r "fastapi\|uvicorn\|starlette" trex_core/` → 0건. - - `find trex_core -path '*/adapters/api*'` → 0건. -- transport supersede cross-reference (REQ-025 AC-5): - - `docs/discovery/dcy-002_…md` 헤더에 dcy-006/BAT-007 supersede 메모 추가됨. - - `docs/discovery/dcy-005_…md` 는 본 batch 작업 전부터 dcy-006 cross-reference 보유 (FR-1, A-3, # 9 참조 목록). - -### Logs -- `.venv/bin/ruff check trex_core tests` → `All checks passed!`. -- RabbitMQ 통합 evidence: `127.0.0.1:5672` admin/admin 으로 7건 통합 테스트(`test_rabbitmq_transport.py` 5 + `test_rabbitmq_intent_e2e.py` 2) 모두 PASSED. 큐/이벤트는 매 테스트가 uuid prefix 로 격리되어 잔존 0. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. -- Notes: - - core 자율 안전 매도(A-5, dcy-006) 는 본 batch 범위 외 — release 전에 운영 절차 (broker 장기 outage 시 열린 포지션 처리) 문서화 또는 별도 Discovery 트래킹 권장. - - 전략 모듈 측 RabbitMQ 클라이언트 전환 + cut-over 순서(전략 모듈 → trex-core) 는 release 단계 의제. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md deleted file mode 100644 index 1845ef0..0000000 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/design.md +++ /dev/null @@ -1,62 +0,0 @@ -# BAT-008 Design - -- Status: draft -- Batch: BAT-008 - -## Architecture Summary -- 신규 `trex_core/adapters/portfolio/` read 계층을 추가해 portfolio-hub universe 를 가져온다. 스냅샷은 in-memory store 에 유지하되, pause overlay 만 Postgres 에 영속한다. warning 은 자동·임시 overlay 로 in-memory 에만 유지하고 다음 정합 성공 시 자동 해제한다. daemon 에 스케줄러 스레드를 추가해 일일 refresh 를 수행하고, BAT-007 의 snapshot RPC consumer 핸들러를 확장해 선별·마스킹·envelope 산출 로직을 연결한다. - -## Changed Areas -- Docs: docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md, docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- Modules: - - `trex_core/application/portfolio.py` (신규): `PortfolioHubReadPort` Protocol + 스냅샷 선별 서비스 + masking 합성 로직 - - `trex_core/adapters/portfolio/` (신규): `hub_read.py`(실 어댑터), `stub_read.py`(결정론 stub) - - `trex_core/adapters/portfolio/pause_store.py` (신규): Postgres `snapshot_pause` 테이블 CRUD - - `trex_core/adapters/messaging/snapshot_handler.py` (확장): snapshot RPC 핸들러에 선별/envelope/degraded 로직 연결 - - `trex_core/runtime/daemon.py` (확장): `_snapshot_scheduler` 스레드 추가, pause/resume RPC 제어 핸들러 연결 - - `trex_core/runtime/config.py` (확장): `TREX_SNAPSHOT_SCHEDULE_KST` env, daemon_id 토큰 도출·캐시 - - `trex_core/adapters/broker/` (확장): `BrokerPort.get_balance()` 메서드 추가 (KIS + paper stub) -- Interfaces: - - RabbitMQ `trex.snapshot.req` RPC: `{strategy_key?: str, view?: "all"|"buyable"|"held"}` → `{assets: [], view: str, available_slots: int, degraded: bool, reason: str|null}` - - CLI: `trex-core pause ` / `trex-core resume ` - - RPC 제어 메시지 (BAT-007 채널): `{action: "pause"|"resume", portfolio_id: str}` 핸들러 - -## Key Decisions - -1. **스냅샷 store 분리**: in-memory `SnapshotStore`(universe·현금·포지션·warning) + Postgres `snapshot_pause`(pause). warning 은 process 재시작 시 초기화(일시적·자동), pause 는 Postgres 에서 복원(영속·수동). 이 분리가 REQ-031/032 의 "자동 warning vs 수동 pause" 시맨틱을 명확히 구현한다. - -2. **masking 합성 순서**: `pending(slot 상태) OR warning(정합 실패) OR pause(수동)` → 셋 중 하나라도 true 면 마스킹. 적용은 view 집합 산출 후 균일 적용. 우선순위: 슬롯 overlay > portfolio-hub 보유 여부. - -3. **daemon_id 도출**: `PortfolioHubReadPort` 초기화 시 portfolio-hub `GET /v1/user_info` 1회 호출 → 응답 `daemon_id` 추출·캐시. `TREX_PORTFOLIO_HUB_TOKEN` 자격증명 재사용(REQ-027 A-1). (정정: REQ-027 CHG-04 — `/v1/auth/whoami` 부재로 `/v1/user_info` 로 변경) - -4. **strategy_key 필터**: 요청 payload 의 `strategy_key`(str) 를 자산의 `active_strategy_id` 와 **대소문자 구분 정확 일치**(`==`)로 필터링. 생략 시 필터 없음. - -5. **degraded 분기**: - - `portfolio_hub_unavailable`: 스케줄러 fetch 또는 RPC handler 시점에 portfolio-hub 에 연결 불가. - - `snapshot_fetch_failed`: fetch 시도 자체는 성공했으나 응답 파싱/검증 실패. - - `cash_position_stale`: 옵션 D 갱신 실패(ledger write 응답에 `*_after` 없거나 stale 감지). - - 각 경우 sanitized message 포함, raw exception/credentials 노출 금지. - -6. **스케줄러 스레드 snapshot 교체**: 일일 fetch 완료 시 `SnapshotStore` 를 **immutable swap**(새 dict 할당 후 atomic reference 교체) 으로 교체. snapshot RPC handler 가 구버전·신버전 혼합 읽기 없도록 보장. warning/pause overlay 는 swap 후 재적용. - -7. **KIS D-2 0수량 잔고 처리**: `hldg_qty == 0` 인 잔고 항목은 **미보유**로 취급(잔고 없음). 트리거: 당일 전량매도 후 KIS 잔고가 D-2 까지 0으로 남는 케이스. 이 항목이 symbol 집계 결과 0수량이면 portfolio-hub 포지션 0과 일치(warning 없음). - -8. **BrokerPort 잔고조회 확장**: `get_balance() → list[BalanceItem]` 메서드를 `BrokerPort` Protocol 에 추가. KIS 어댑터: `TTTC8434R`(실전)/`VTTC8434R`(모의) 분기. paper stub: 결정론 고정 잔고 반환. 기존 REQ-011/012 어댑터에 메서드 추가(인터페이스 확장, 기존 동작 불변). - -9. **옵션 D 연결점**: ledger write 응답(BAT-006 `LedgerAdapter`) 이 `cash_balance_after`/`asset_quantity_after` 를 반환하면 `SnapshotStore.update_cash_position(asset_id, cash, qty)` 호출. portfolio-hub#8 배포 전 응답에 해당 필드가 없으면 갱신 skip(graceful). - -## Edge Cases -- portfolio-hub 불가 **at startup**: `SnapshotStore` 비어 있음 + `degraded=true`. 전략 요청에 `assets: [], degraded: true, reason: "portfolio_hub_unavailable"` 반환. 신규 매매 보류(전략 모듈 책임). -- `strategy_key` 필터 결과가 빈 배열: `assets: [], degraded: false`. 필터 미매치는 degraded 가 아님. -- `available_slots=0` + `view=buyable`: `assets: [], available_slots: 0, degraded: false`. -- 같은 자산에 warning + pause 동시: 둘 다 마스킹 적용(OR), 어느 쪽이 먼저 해제되든 나머지 하나가 남아있으면 여전히 마스킹. -- pause 중 일일 refresh: universe·포지션 갱신 후 pause overlay 재적용(pause 불변). -- holding 슬롯 자산의 수량 표기: snapshot baseline 값 사용. holding 중 ledger flush 지연으로 실 포지션과 갭이 있을 수 있음 — 옵션 D 갱신이 완료되면 write 응답으로 즉시 보정. - -## Architecture Impact -- both (project-structure: `adapters/portfolio/` 신규 계층, runtime-flows: 스케줄러 스레드 + 일일 fetch + masking 합성 흐름 + 옵션 D ledger hook) - -## Reference Doc Update Plan -- project-structure.md: `adapters/portfolio/`(read 어댑터·stub·pause store) 신규 섹션, `application/portfolio.py` 포트·선별 서비스, 스케줄러 스레드 추가. -- runtime-flows.md: 일일 스냅샷 fetch 흐름, 정합 검증 흐름, pause/resume 흐름, snapshot RPC 응답 산출 흐름(masking 합성 포함), 옵션 D ledger write hook 흐름. -- interface-contract.md: `trex.snapshot.req` RPC 요청/응답 envelope 계약, pause/resume 제어 메시지 계약, `PortfolioHubReadPort` 포트 시그니처. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/implementation.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/implementation.md deleted file mode 100644 index bc73875..0000000 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/implementation.md +++ /dev/null @@ -1,69 +0,0 @@ -# BAT-008 Implementation - -- Status: complete (M1-M8; M9 옵션 D 는 portfolio-hub#8 배포 후) -- Batch: BAT-008 - -## Plan Summary -- M1-M8 마일스톤 모두 완료. M9(REQ-030 옵션 D `*_after` 갱신)는 portfolio-hub#8 배포 후 별도 실행. `SnapshotStore.update_cash_position` wiring point 가 이미 노출되어 있어 배포 후 LedgerWriter 호출 1줄 추가로 활성화 가능. - -## Changed Files -- 신규 - - `trex_core/application/portfolio.py` — `PortfolioHubReadPort` Protocol, `PortfolioItem`, `PortfolioHubUnavailableError`, `View`/`DegradedReason` enum, `SnapshotStore`(swap/warning/option-D/degraded), `PauseStore` Protocol + `InMemoryPauseStore`, `SnapshotEnvelope` + `build_snapshot_envelope`. - - `trex_core/application/reconciliation.py` — `reconcile_positions(store, broker)` symbol-aggregate 비교 + warning 자동 set/clear. - - `trex_core/application/snapshot_scheduler.py` — `SnapshotScheduler.run_once()` (fetch + swap + reconcile) + `run_forever(stop, tick_sec)` (boot 즉시 1회 + KST 시각 도래 시 1회). - - `trex_core/adapters/portfolio/__init__.py`, `hub_read.py`(`PortfolioHubReadAdapter` httpx — whoami + list_portfolios), `stub_read.py`(`StubPortfolioRead` 결정론), `pause_store.py`(`PostgreSQLPauseStore` upsert/delete). - - `trex_core/adapters/persistence/sql/003_create_snapshot_pause.sql` — `snapshot_pause(portfolio_id PK, actor, created_at, updated_at)`. - - `trex_core/runtime/cli.py` + `trex-core` console_script — `pause/resume/paused` 서브커맨드. - - 테스트: `tests/adapters/portfolio/{test_stub_read,test_hub_read,test_postgresql_pause_store}.py`, `tests/application/{test_snapshot_selection,test_snapshot_handler,test_reconciliation,test_snapshot_scheduler,test_pause_handler}.py`. -- 수정 - - `trex_core/application/broker_port.py` — `BalanceItem` 데이터클래스 + `BrokerPort.get_balance() -> list[BalanceItem]` 메서드 추가. - - `trex_core/adapters/broker/paper.py` — `get_balance()`: fill aggregate (buy − sell) per symbol, 0수량 drop. - - `trex_core/adapters/broker/kis.py` — `get_balance()`: `TTTC8434R`/`VTTC8434R` `inquire-balance` 호출, `output1[*].pdno + hldg_qty`, 0수량 drop (D-2 leftover 처리, Decision 7). - - `trex_core/adapters/messaging/topology.py` — `PAUSE_REQ_QUEUE = "trex.pause.req"` 추가. - - `trex_core/adapters/messaging/rabbitmq.py` — `declare_topology()` 가 `PAUSE_REQ_QUEUE` 도 선언. - - `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler` 에 `snapshot_store`/`pause_store`/`default_total_slots` 인자 추가 (BAT-008 envelope path). 인자 미제공 시 BAT-007 stub-shape 폴백. 신규 `make_pause_rpc_handler(pause_store)`. - - `trex_core/runtime/config.py` — `SnapshotScheduleConfig` + `TREX_SNAPSHOT_SCHEDULE_KST`/`TREX_SNAPSHOT_TICK_SEC` env 로딩, `DaemonConfig.snapshot_schedule` 필드. - - `trex_core/runtime/daemon.py` — `_build_portfolio_read(config)`, `_parse_kst_time`. `Daemon` 에 `snapshot_store`/`pause_store`/`portfolio_read`/`snapshot_scheduler` 인스턴스 추가. 신규 worker thread `_pause_rpc_loop`, `_snapshot_scheduler_loop`. `_snapshot_rpc_loop` 가 BAT-008 envelope handler 사용. `start()` 의 worker 목록을 6개로 확장. - - `pyproject.toml` — `trex-core = "trex_core.runtime.cli:main"` console_script 추가. - - `.env.example` — `TREX_SNAPSHOT_SCHEDULE_KST=07:30`, `TREX_SNAPSHOT_TICK_SEC=60` 추가. - - `tests/conftest.py` — `_PG_TABLES` 에 `snapshot_pause` 추가. - - `tests/adapters/persistence/test_migration_and_drift.py` — `_EXPECTED_TABLES` 에 `snapshot_pause` 추가. - - baseline 문서 갱신 — `docs/runtime-flows.md`/`docs/interface-contract.md`/`docs/project-structure.md` 에 BAT-008 섹션 + Change Log 추가. - -## Execution Log -1. baseline 테스트 147 passed (PG + RabbitMQ 게이트 모두 가용) 확인. -2. M1 (REQ-027): `application/portfolio.py` 의 read 포트/DTO/store + `adapters/portfolio/{hub_read,stub_read}` + `pause_store.py` 작성. 단위테스트 + httpx MockTransport 기반 어댑터 contract 테스트 작성. -3. M2 (REQ-030 baseline): `snapshot_scheduler.py` 의 `SnapshotScheduler` + `run_once()` 작성. `runtime/config.py` 에 `SnapshotScheduleConfig` 추가, `runtime/daemon.py` 에 `_snapshot_scheduler_loop` 스레드 추가. -4. M3 (REQ-031): `BrokerPort.get_balance()` 확장 + paper/KIS 구현 + `application/reconciliation.py` 의 `reconcile_positions` 작성 + 단위테스트. -5. M4 (REQ-032): `003_create_snapshot_pause.sql` 마이그레이션 + `PostgreSQLPauseStore` + `make_pause_rpc_handler` + `runtime/cli.py` 작성. RPC handler / PG persistence / CLI smoke 모두 테스트. -6. M5 (REQ-029): `build_snapshot_envelope` 의 base(view) 산출 + masking OR 합성 + strategy_key 필터 작성 + 10개 시나리오 테스트. -7. M6 (REQ-028): `make_snapshot_rpc_handler` 를 envelope path 로 확장 (legacy stub-shape 폴백 유지 → BAT-007 회귀 0). degraded 신호 sanitization 포함. -8. M7 통합 검증: stub `SnapshotStore` + slot 도메인 + warning + pause 합성 시퀀스 테스트 + 도메인 import 스윕 회귀 통과. -9. M8 (REQ-033): baseline 3 문서(runtime-flows / interface-contract / project-structure) 에 BAT-008 섹션 + Change Log 추가. -10. End-to-end 운영 환경 검증: 실 PostgreSQL + 실 RabbitMQ 위에서 `trex-core-daemon` 기동 → snapshot RPC (view=all/buyable/held + strategy_key 필터) → pause/resume RPC → snapshot 재요청까지 1회 통과. portfolio-hub 서버 미가동 환경에서 `degraded=true reason=portfolio_hub_unavailable` 정상 반환 확인. -11. 최종 검증: `TREX_RABBITMQ_TEST_URL` + `TREX_POSTGRESQL_TEST_DSN` 게이트 모두 가용 상태에서 `pytest -q` → **185 passed / 1 skipped** (KIS sandbox 토큰 rate-limit 1건만 skip). `ruff check trex_core tests` → All checks passed. - -## Validation -- 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" TREX_POSTGRESQL_TEST_DSN=$(grep TREX_POSTGRESQL_DSN .env | cut -d= -f2-) .venv/bin/python -m pytest -q` - - 결과: `185 passed, 1 skipped` (KIS sandbox 토큰 rate-limit). -- 명령: `.venv/bin/ruff check trex_core tests` - - 결과: `All checks passed!`. -- 추가 테스트 ~39 종: - - `tests/adapters/portfolio/test_stub_read.py` — 3 (REQ-027 AC-2) - - `tests/adapters/portfolio/test_hub_read.py` — 4 (REQ-027 AC-1/AC-4) - - `tests/adapters/portfolio/test_postgresql_pause_store.py` — 4 (REQ-032 AC-2) - - `tests/application/test_snapshot_selection.py` — 10 (REQ-029 AC-1~5, REQ-028 AC-1/AC-2/AC-3) - - `tests/application/test_snapshot_handler.py` — 4 (REQ-028) - - `tests/application/test_reconciliation.py` — 4 (REQ-031 AC-1/AC-2/AC-3) - - `tests/application/test_snapshot_scheduler.py` — 4 (REQ-030 AC-1, 옵션 D wiring point) - - `tests/application/test_pause_handler.py` — 5 (REQ-032 AC-1/AC-3/AC-4) -- 운영 환경 검증 (실 PG + RabbitMQ + `trex-core-daemon`): - - snapshot RPC envelope 모든 키 + no `price` 필드 + `degraded` 신호 확인. - - pause RPC → snapshot 마스킹 → resume RPC → snapshot 복귀 확인. - - view 3 모드 + strategy_key 필터가 envelope 응답에 반영됨. - -## Remaining Risks -- **REQ-030 옵션 D (AC-2/AC-3)**: portfolio-hub#8 배포 대기. `SnapshotStore.update_cash_position` wiring point 는 이미 노출됨 — 배포 확인 후 LedgerWriter 의 ledger write 응답 소비 경로에서 `cash_balance_after`/`asset_quantity_after` 가 있을 때 호출 1줄 추가로 활성. 본 batch verification 에서는 deferred residual risk 로 기록. -- **KIS D-2 0수량 잔고 운영 검증**: 설계 Decision 7(0수량 drop) 은 paper broker 테스트로 회귀 가드되어 있으나, 실 KIS 환경에서 D-2 leftover 케이스를 한 번 관찰해 운영 노트화 권장. -- **scheduler 실 운영 검증**: 본 batch 의 검증은 `run_once()` 단위테스트 + boot 즉시 fire 운영 확인까지. `TREX_SNAPSHOT_SCHEDULE_KST` 시각 fire 는 실 데몬 운영 후 첫 07:30 KST 도래 시 관찰 필요. -- **portfolio-hub HTTP 통합 evidence**: `httpx.MockTransport` contract 테스트 + 실 데몬이 portfolio-hub 부재 시 `degraded` 정상 노출까지 검증. portfolio-hub 가용 환경에서 실 응답 round-trip 은 운영 단계 추가 evidence 로 보강 권장. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/index.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/index.md deleted file mode 100644 index d4de38a..0000000 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/index.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-008: portfolio-hub 스냅샷 read 레이어 - -- Status: released -- Profile: standard -- Source Discovery: dcy-005 -- Included REQ: REQ-027, REQ-028, REQ-029, REQ-030, REQ-031, REQ-032, REQ-033 -- Excluded REQ: 없음 - -## Summary -- dcy-005 스냅샷 read 슬라이스 전체를 하나의 delivery 단위로 묶는다. portfolio-hub read 포트/어댑터로 universe 를 가져오고(REQ-027), 일일 스케줄러로 스냅샷을 갱신하며(REQ-030), KIS 잔고조회로 포지션 정합을 검증해 warning 마스킹을 적용하고(REQ-031), 수동 pause/resume 제어를 Postgres 에 영속한다(REQ-032). 이 모든 overlay 를 합성해 slot 상태 기반 자산 선별·view·strategy_key 필터링으로 응답 집합을 산출하고(REQ-029), BAT-007 의 RabbitMQ snapshot RPC 채널로 전달하는 메시지 계약과 degraded 신호를 정의한다(REQ-028). 구현 완료 후 baseline 3문서를 정합한다(REQ-033). - -## Scope -- In Scope: - - REQ-027: `PortfolioHubReadPort.get_portfolios(daemon_id)` 포트 + portfolio-hub read 어댑터(`managed_by_daemon_id` 스코프, `list_portfolios_v1`) + 결정론 stub. `daemon_id` 는 portfolio-hub 인증 토큰에서 도출·캐시(별도 `TREX_DAEMON_ID` 불필요). 각 자산: symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque). 도메인 순수성 import 스윕(NFR-1). - - REQ-028: snapshot RPC(`trex.snapshot.req`) 메시지 계약 — 요청(선택적 strategy_key·view), envelope 응답(`assets[]`·view·available_slots·degraded·reason). portfolio-hub 불가 시 `degraded=true` + machine-readable reason code + sanitized message(raw 예외 노출 금지). - - REQ-029: 응답 base 집합은 view(`all`/`buyable`/`held`)로 선택 후 마스킹(pending·warning·pause) 균일 적용. strategy_key 직교 필터. 최종 합성: `base(view) − mask(pending·warning·pause) − strategy_key 필터`. 슬롯 overlay 가 portfolio-hub 보유 여부보다 우선. - - REQ-030: 설정 가능한 시각(기본 07:30 KST)에 스케줄러가 universe 를 페치해 스냅샷(멤버십·전략파라미터·baseline 현금/포지션) 갱신. 체결 후 ledger write 응답의 `cash_balance_after`/`asset_quantity_after`(옵션 D)로 현금/포지션 갱신 — portfolio-hub#8 배포 후 착수. - - REQ-031: 스냅샷 페치 시 KIS 주식잔고조회(`TTTC8434R`/`VTTC8434R`) 수량 정확 일치 검증(symbol 집계 vs broker `hldg_qty`). 불일치 자산 warning 마스킹 + 다음 정합 성공 시 자동 해제. - - REQ-032: pause 단위 portfolio_id, CLI + dcy-006 RPC 제어 메시지 양방향. Postgres 영속(재시작/refresh 후에도 유지). 전역 마스킹, resume 시 다음 요청부터 즉시 복귀. - - REQ-033: `docs/interface-contract.md`/`docs/runtime-flows.md`/`docs/project-structure.md` 를 read 포트·스냅샷 계약·스케줄러·정합 검증 흐름에 맞게 갱신. -- Out of Scope: - - portfolio-hub write(ledger) 연동 — BAT-006(REQ-017~019) 소관; 본 batch 는 read 만. - - RabbitMQ transport 토폴로지/delivery 신뢰성 — BAT-007(REQ-021~026) 소관. - - 전략 모듈 측 RabbitMQ 클라이언트 구현(외부). - - REQ-030 옵션 D 현금/포지션 갱신 구현 — portfolio-hub#8 배포 전 대기. - - 강제 청산(kill-switch) — trex-core#3 별도 Discovery. - - 전략 taxonomy(declarative intent) — trex-core#1 별도 Discovery. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source Discovery dcy-005 단일. 핵심 확정 결정: Option D(ledger write 응답 `*_after`, dcy-005 OQ-2) + degraded 신호(portfolio-hub 불가 → degraded=true + reason code, OQ-5) + view 3모드(all/buyable/held, OQ-11/12) + strategy_key(사람-읽기 안정 slug, OQ-9) + pause(portfolio_id 단위·Postgres 영속·즉시 복귀, OQ-10) + position 정합(수량 정확 일치·symbol 집계·KIS 잔고조회, OQ-8). -- **의존**: REQ-030 옵션 D 구현은 portfolio-hub#8 배포 대기. baseline 스케줄러(universe fetch)는 선구현 가능. -- **검증 순서 제약**: REQ-029 AC-5(warning·pause 마스킹) 검증은 REQ-031(warning 상태)과 REQ-032(pause 상태) 구현 완료 후 가능. -- **리스크 D-2**: KIS 당일 전량매도 후 잔고가 `hldg_qty=0` 으로 D-2 까지 남을 수 있음 → 0수량 잔고 처리 규칙 설계 단계 확정. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/planning.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/planning.md deleted file mode 100644 index ebe257a..0000000 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/planning.md +++ /dev/null @@ -1,71 +0,0 @@ -# BAT-008 Planning - -- Status: draft -- Batch: BAT-008 -- Profile: standard - -## Included REQ -- REQ-027 (Integration): portfolio-hub read 포트 + 어댑터 + stub -- REQ-028 (Interface): 종목 스냅샷 read 메시지 계약 + envelope + degraded 신호 -- REQ-029 (Interface): 자산 선별(slot overlay) + view 모드 + strategy_key 필터 -- REQ-030 (Integration): 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신 -- REQ-031 (Exception): 포지션 정합 검증 + warning 마스킹 -- REQ-032 (Interface): 런타임 pause/resume 제어 -- REQ-033 (Documentation): 스냅샷 read 레이어 baseline 문서 정합 - -## Delivery Plan -- 의존 그래프 순서대로 구현한다. - - 1. **REQ-027** — `PortfolioHubReadPort` 포트(`trex_core/application/`) + portfolio-hub read 어댑터(`trex_core/adapters/portfolio/hub_read.py`) + 결정론 stub(`trex_core/adapters/portfolio/stub_read.py`). `runtime/config.py` 에 `daemon_id` 토큰 도출·캐시 로직 추가. 도메인 import 스윕 가드 확장. - - 2. **REQ-030 (baseline)** — 신규 스케줄러 스레드를 sync+thread daemon 에 추가(`_snapshot_scheduler` 스레드). `TREX_SNAPSHOT_SCHEDULE_KST`(기본 `07:30`) env 파라미터. universe fetch → 스냅샷 store 갱신. **옵션 D(`cash_balance_after`/`asset_quantity_after`) 갱신은 portfolio-hub#8 배포 후 별도 구현.** - - 3. **REQ-031** — BrokerPort 에 `get_balance() → list[BalanceItem(pdno, hldg_qty)]` 잔고조회 확장(KIS `TTTC8434R`/`VTTC8434R`, paper stub 결정론). 정합 검증 서비스: symbol 집계 vs broker per-symbol `hldg_qty` 수량 정확 일치, 불일치 자산 warning flag, 다음 정합 성공 시 자동 해제. 0수량 잔고 처리 규칙은 설계 단계 확정. - - 4. **REQ-032** — Postgres `snapshot_pause` 테이블(portfolio_id PK). CLI `trex-core pause ` / `resume`. BAT-007 RPC 채널에 pause/resume 제어 메시지 핸들러 추가. daemon 재시작/일일 refresh 후에도 pause 영속. - - 5. **REQ-029** — 스냅샷 선별 서비스: `base(view)` 집합 산출 후 `mask(pending · warning · pause)` OR 합성 마스킹 후 `strategy_key` 직교 필터. SlotRepository(BAT-002) 재사용. - - 6. **REQ-028** — BAT-007 의 snapshot RPC consumer(`trex.snapshot.req`) 핸들러에 envelope 산출 로직 연결. degraded 분기: portfolio-hub 불가 탐지 → `degraded=true` + reason code + sanitized message. null 금지(`assets: []`). - - 7. **REQ-033** — REQ-027~032 구현 안정화 후 `docs/interface-contract.md`/`docs/runtime-flows.md`/`docs/project-structure.md` 정합. - - 8. **REQ-030 (옵션 D)** — portfolio-hub#8 배포 확인 후, ledger write 응답 소비 경로에 `cash_balance_after`/`asset_quantity_after` → 스냅샷 store 갱신 로직 추가. idempotent 갱신(duplicate write 처리). - -## Dependencies -- Internal: - - REQ-027(read port) 선행 → REQ-028, REQ-029, REQ-030, REQ-031 모두. - - REQ-031(warning 상태), REQ-032(pause 상태) 선행 → REQ-029(마스킹 합성) 전체 검증 가능. - - REQ-029(선별 로직) 선행 → REQ-028(RPC handler 응답 산출). - - REQ-033 후행 → REQ-027~032 모두. - - BAT-002 `SlotRepository`(slot 상태) — REQ-029 slot overlay 재사용. - - BAT-004 `BrokerPort`(REQ-011/012) — REQ-031 잔고조회 확장. - - BAT-006 `LedgerPort`(REQ-017~019) — REQ-030 옵션 D ledger write hook. - - BAT-007 RabbitMQ snapshot RPC consumer(REQ-021/022) — REQ-028 핸들러 연결점. -- External: - - portfolio-hub `GET /v1/portfolios?managed_by_daemon_id=` (`list_portfolios_v1`) — A-1 전제. - - **portfolio-hub#8** (`POST /v1/portfolios/{id}/ledger/trades` 응답에 `cash_balance_after`/`asset_quantity_after`) — REQ-030 옵션 D 의존. 배포 전까지 구현 대기. - - KIS 주식잔고조회(`TTTC8434R`/`VTTC8434R`, `GET /uapi/domestic-stock/v1/trading/inquire-balance`) — REQ-031. KIS 미가용 시 paper stub 결정론 fallback. - - RabbitMQ broker — BAT-007 transport 재사용(추가 provision 불필요). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 `adapters/portfolio` 계층 도입(read port 추상화 + 어댑터 + stub 구조 결정), 스냅샷 store 설계(in-memory snapshot + Postgres pause overlay 분리), warning state 머신(자동 임시 vs pause 영속 구분), KIS D-2 0수량 잔고 처리 규칙, masking 합성 순서(pending·warning·pause OR 적용 방식), degraded 신호 분기 결정(portfolio_hub_unavailable / snapshot_fetch_failed / cash_position_stale), daemon_id 토큰 도출·캐시 방식, 스케줄러 스레드 통합 방식을 구현 전에 design.md 에 고정해야 한다. - -## Milestones -1. M1 — REQ-027: `PortfolioHubReadPort` 포트 + read 어댑터 + stub + daemon_id 토큰 도출 + import 스윕. -2. M2 — REQ-030 (baseline): 스케줄러 스레드 + universe fetch + 스냅샷 store 갱신(옵션 D 제외). -3. M3 — REQ-031: `BrokerPort.get_balance()` 확장(KIS/paper) + 정합 검증 + warning 상태 자동 해제. -4. M4 — REQ-032: `snapshot_pause` Postgres 테이블 + CLI + RPC 제어 핸들러 + 영속 검증. -5. M5 — REQ-029: 스냅샷 선별 서비스(base집합 + masking OR합성 + strategy_key 필터). -6. M6 — REQ-028: snapshot RPC 핸들러 + envelope 산출 + degraded 분기. -7. M7 — 통합 verification: end-to-end snapshot RPC 흐름, masking 합성(pending·warning·pause), strategy_key 필터, degraded 주입, pause 영속, 도메인 import 스윕. -8. M8 — REQ-033: baseline 3문서 정합. -9. M9(후속) — REQ-030 옵션 D: portfolio-hub#8 배포 확인 후 ledger write 응답 `*_after` 소비 + idempotent 갱신. - -## Risks -- R-1 (Medium): **portfolio-hub#8 배포 일정 불확실** — REQ-030 옵션 D 구현 대기. 대응: baseline 스케줄러(fetch만)를 먼저 deliver 하고 옵션 D 는 M9 로 분리. 배포 확인 전 스냅샷 현금/포지션은 일일 fetch baseline 으로만 유지. -- R-2 (Medium): **KIS D-2 0수량 잔고** — 당일 전량매도 후 잔고가 `hldg_qty=0` 으로 D-2 까지 남음(KIS 015 문서). 0수량을 보유로 볼지 미보유로 볼지 처리 규칙 미확정. 대응: design 단계에서 처리 규칙 확정(예: `hldg_qty=0` 인 항목은 잔고 없음으로 취급). -- R-3 (Medium): **masking 합성 경계 케이스** — 같은 자산에 warning·pause 동시 적용, pending 상태에서 resume 호출, holding 슬롯과 portfolio-hub 보유 수량 불일치(holding→ledger flush 갭). 대응: AC 별 테스트 시퀀스를 verification.md 에 명시. -- R-4 (Low): **portfolio-hub read API 인터페이스** — `list_portfolios_v1` 의 `managed_by_daemon_id` 스코프 지원 여부, `PortfolioItem` 응답 스키마 확정 필요(A-1 전제). 대응: stub 어댑터로 선검증, 실 통합은 게이트 분리. -- R-5 (Low): **스케줄러 스레드 충돌** — 스케줄러 fetch 중 snapshot RPC 요청이 도착하면 partial 상태 읽기 위험. 대응: design 단계에서 snapshot store 락 또는 immutable swap 전략 확정. diff --git a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md b/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md deleted file mode 100644 index 0040d63..0000000 --- a/docs/batches/bat-008_20260606_portfolio-hub-snapshot-read-layer/verification.md +++ /dev/null @@ -1,120 +0,0 @@ -# BAT-008 Verification - -- Status: approved -- Batch: BAT-008 - -## Acceptance Mapping - -- REQ-027 (계약 정정 CHG-04 반영): - - AC-1: HTTP MockTransport 통합테스트에서 `{portfolios:[…]}` envelope 파싱 + `managed_by_daemon_id` 클라이언트측 필터 + `PortfolioItem` 항목 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 필드 + `price` 부재 확인. 실 list 응답에 전략 필드 없으면 None/`{}` 기본값. - - AC-2: 결정론 stub 가 네트워크 없이 고정 universe 반환 확인. - - AC-3: 도메인 import 스윕(`tests/domain/test_domain_import_sweep.py`)이 `pika`/`httpx`/`adapters.*` 0 import 회귀 가드. - - AC-4: HTTP MockTransport 가 `/v1/user_info` 호출 + Bearer 토큰 헤더 + `daemon_id` 도출 확인. 스킴 없는 base URL → `http://` 기본 적용. `TREX_DAEMON_ID` env 1차 출처 없음(코드 review). - -- REQ-028: - - AC-1: envelope 응답이 정확히 `{assets, view, available_slots, degraded, reason, message}` 키 집합 + `assets` 가 항상 list(null 금지) 확인. - - AC-2: `SnapshotStore.set_degraded(...)` 후 envelope 가 `degraded=true reason=portfolio_hub_unavailable`. 실 운영 검증 — portfolio-hub 부재 환경에서 daemon 기동 후 snapshot RPC 가 같은 응답 반환. - - AC-3: 응답 항목에 `symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params` 만 존재, `price` 키 없음. - -- REQ-029: - - AC-1: 같은 symbol holding + 다른 portfolio_id pending → `view=all` 에서 holding 자산만 포함, pending portfolio_id 마스킹. - - AC-2: `available_slots=0` + `view=buyable` → `assets=[]`. - - AC-3: `view=held` → holding 슬롯이 가리키는 portfolio_id 만 응답. - - AC-4: `strategy_key="target-value-v1"` → 일치 자산만 / 생략 시 마스킹 후 전체. - - AC-5: warning(`set_warning`) 또는 pause(`pause_store.pause`) 자산은 임의 view 에서 균일 마스킹. - -- REQ-030: - - AC-1: `SnapshotScheduler.run_once()` 가 universe fetch + `SnapshotStore.swap` + reconcile 1회 수행 확인. 실 daemon boot 직후 동일 동작. - - AC-2 (옵션 D): **deferred — portfolio-hub#8 배포 후 evidence 수집**. `SnapshotStore.update_cash_position(pid, cash, qty)` wiring point 는 노출되어 있어 배포 후 LedgerWriter 의 ledger write 응답 소비 경로에서 호출 1줄 추가로 활성. 본 batch 에서는 wiring 단위테스트만 통과. - - AC-3 (옵션 D, duplicate idempotent): **deferred — portfolio-hub#8 배포 후 evidence 수집**. - -- REQ-031: - - AC-1: hub symbol 집계 수량 ≠ broker `hldg_qty` → `SnapshotStore.set_warning(pid, True)` + envelope 에서 마스킹 확인. - - AC-2: 2 라운드 reconcile 시퀀스(불일치→일치)에서 warning 자동 해제 + 응답 복귀 확인. - - AC-3: paper broker `get_balance()` 가 fill aggregate 기반 결정론 잔고 반환 확인. 0수량 row drop 가드 포함. - -- REQ-032: - - AC-1: `pause(pid)` 후 임의 view/strategy_key 요청에서 해당 portfolio_id 응답 마스킹. - - AC-2: PostgreSQL pause store 재인스턴스화(restart 시뮬레이션) 후에도 paused row 유지. - - AC-3: `resume(pid)` 후 다음 요청 envelope 에 복귀. - - AC-4: 동일 `PostgreSQLPauseStore` 를 CLI 와 RPC handler 가 공유 → CLI pause → RPC `action=list` 에 노출. 실 운영 검증 — daemon RPC pause → 같은 store 의 paused 목록 확인. - -- REQ-033: - - AC-1: `docs/runtime-flows.md`/`docs/interface-contract.md`/`docs/project-structure.md` 모두에 BAT-008 섹션 + Change Log 항목 추가. - - AC-2: 베이스라인 ↔ 코드 정합 manual review — 본 verification 의 Manual 섹션 참조. - -## Evidence - -### Test -- 명령: `TREX_RABBITMQ_TEST_URL="amqp://admin:admin@127.0.0.1:5672/" TREX_POSTGRESQL_TEST_DSN=$(grep TREX_POSTGRESQL_DSN .env | cut -d= -f2-) .venv/bin/python -m pytest -q` - - 결과: `185 passed, 1 skipped` (KIS sandbox token rate-limit 1건만 skip; 그 외 PG/RabbitMQ 게이트 모두 통과). -- REQ-027 AC-1: `tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_parses_envelope_and_filters_by_daemon` + `::test_get_portfolios_includes_items_without_daemon_field` + `test_stub_read.py::test_stub_returns_fixed_universe_without_network`. -- REQ-027 AC-2: `tests/adapters/portfolio/test_stub_read.py::test_stub_returns_fixed_universe_without_network` + `test_stub_items_are_mutable_for_tests`. -- REQ-027 AC-3: `tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports` (BAT-007 부터 유지, `httpx`/`pika`/`adapters.*` 금지 prefix 회귀 가드). -- REQ-027 AC-4: `tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id` (`/v1/user_info` URL/Bearer 헤더/응답 디코드 + daemon_id 추출) + `::test_base_url_without_scheme_defaults_to_http`. -- REQ-028 AC-1: `tests/application/test_snapshot_handler.py::test_envelope_shape_returned` + `tests/application/test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null`. -- REQ-028 AC-2: `tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials` + `tests/application/test_snapshot_selection.py::test_degraded_signal_in_envelope`. 실 daemon 운영 검증에서 portfolio-hub 부재 환경에서 같은 응답 발생 확인. -- REQ-028 AC-3: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`. -- REQ-029 AC-1: `tests/application/test_snapshot_selection.py::test_view_all_includes_holding_excludes_pending_for_same_symbol`. -- REQ-029 AC-2: `tests/application/test_snapshot_selection.py::test_view_buyable_with_zero_available_slots_returns_empty`. -- REQ-029 AC-3: `tests/application/test_snapshot_selection.py::test_view_held_returns_only_holding_slot_assets`. -- REQ-029 AC-4: `tests/application/test_snapshot_selection.py::{test_strategy_key_orthogonal_filter,test_strategy_key_missing_includes_everything_unmasked}`. -- REQ-029 AC-5: `tests/application/test_snapshot_selection.py::{test_warning_overlay_masks_asset_uniformly_across_views,test_pause_overlay_masks_asset}`. -- REQ-030 AC-1: `tests/application/test_snapshot_scheduler.py::test_run_once_fetches_universe_and_swaps_store` + `::test_swap_preserves_warning_for_carried_over_portfolio`. 실 daemon boot 후 첫 fetch 동작 확인. -- REQ-030 AC-2/AC-3: **deferred — portfolio-hub#8 배포 대기**. wiring point 테스트(`test_snapshot_scheduler.py::test_option_d_update_cash_position_overrides_baseline`) 가 `SnapshotStore.update_cash_position` 의 동작 보장. -- REQ-031 AC-1: `tests/application/test_reconciliation.py::test_mismatch_raises_warning_and_masks_asset`. -- REQ-031 AC-2: `tests/application/test_reconciliation.py::test_subsequent_match_auto_clears_warning`. -- REQ-031 AC-3: `tests/application/test_reconciliation.py::test_paper_broker_returns_deterministic_balance` (paper fill aggregate + 0수량 drop 시나리오). -- REQ-032 AC-1: `tests/application/test_pause_handler.py::test_pause_then_list` + `tests/application/test_snapshot_selection.py::test_pause_overlay_masks_asset`. -- REQ-032 AC-2: `tests/adapters/portfolio/test_postgresql_pause_store.py::test_durability_simulated_via_fresh_store_instance` (PG 게이트). -- REQ-032 AC-3: `tests/application/test_pause_handler.py::test_resume_clears_pause`. -- REQ-032 AC-4: `tests/application/test_pause_handler.py::test_cli_pause_visible_to_rpc_list_via_shared_store`. 실 daemon 운영 검증 — RPC pause → 같은 store list 노출 확인. - -### Manual -- baseline ↔ 코드 정합 리뷰 (REQ-033 AC-1/AC-2): - - `docs/runtime-flows.md` 의 신규 5 섹션(`일일 스냅샷 refresh`, `포지션 정합 검증`, `pause/resume 제어`, `스냅샷 RPC 응답 산출`, `broker degrade 분기` 인접 위치) 가 실제 코드와 정합: - - `SnapshotScheduler.run_once()` 의 boot+tick 시퀀스 → `trex_core/application/snapshot_scheduler.py:run_forever` - - `reconcile_positions` symbol 집계 + warning set/clear → `trex_core/application/reconciliation.py` - - pause/resume CLI ↔ RPC ↔ `snapshot_pause` table → `trex_core/runtime/cli.py` + `trex_core/adapters/portfolio/pause_store.py` + `trex_core/application/messaging_handlers.py::make_pause_rpc_handler` - - envelope builder 의 base/mask/strategy_key 합성 → `trex_core/application/portfolio.py::build_snapshot_envelope` - - `docs/interface-contract.md` 의 `trex.snapshot.req` envelope 스키마 + `trex.pause.req` control RPC + `PortfolioHubReadPort` Protocol + `SnapshotStore`/`PauseStore`/`SnapshotScheduler` + `BrokerPort.get_balance()` 확장 섹션이 실제 시그니처와 정합. - - `docs/project-structure.md` 디렉토리 트리에서 `trex_core/adapters/portfolio/`, `trex_core/application/{portfolio,reconciliation,snapshot_scheduler}.py`, `trex_core/runtime/cli.py`, `003_create_snapshot_pause.sql`, daemon 의 6-스레드 구성, `trex-core` CLI console_script 모두 실제 트리(`ls -R trex_core/`)와 일치. -- code-search: - - `grep -r "from fastapi\|from uvicorn" trex_core/` → 0 (BAT-007 회귀 유지). - - `grep -r "import pika\|import httpx" trex_core/domain/` → 0 (NFR-1). - - `find trex_core/adapters/portfolio -type f -name '*.py'` → `__init__/hub_read/stub_read/pause_store.py` 4 파일 존재. - - `grep "PAUSE_REQ_QUEUE" trex_core/adapters/messaging/` → topology 상수 + rabbitmq declare 두 곳 노출. - -### Logs -- `.venv/bin/ruff check trex_core tests` → `All checks passed!`. -- 실 RabbitMQ + PostgreSQL evidence: `127.0.0.1:5672` admin/admin + 로컬 PG 로 daemon 기동 후 snapshot RPC (view=all/buyable/held + strategy_key), pause/resume RPC, 응답 envelope 키/필드 검증 모두 통과. portfolio-hub 부재 환경에서 `degraded=true reason=portfolio_hub_unavailable` 정상 노출. - -### Re-verification Addendum (2026-06-06, REQ-027 CHG-04 — release 선행 정리) -- 사유: release-candidate 확정 이후 실 portfolio-hub API 실측으로 read 계약 불일치 발견 → 어댑터/테스트 정정(commit 43bad24). 본 batch verification 을 정정 계약 기준으로 재확인. -- 정정 요지: whoami `/v1/auth/whoami`→`/v1/user_info`; list envelope `{items}`→`{portfolios,total}`; 토큰 스코프 → 클라이언트측 `managed_by_daemon_id` 필터; base URL 스킴 누락 시 `http://` 보정; 실 `PortfolioItem` 전략 필드 부재 → None/`{}`(이월). -- 재실행 결과: - - `.venv/bin/python -m pytest tests/adapters/portfolio/test_hub_read.py -q` → `6 passed`. - - `.venv/bin/python -m pytest -q` (infra env 미설정 invocation) → `165 passed, 23 skipped` (PG/RabbitMQ/KIS 게이트 skip). `ruff check trex_core tests` → `All checks passed!`. - - 실 portfolio-hub e2e (portfolio-hub#9 user_info 500 수정 반영 후): `PortfolioHubReadAdapter.whoami()`→`daemon_id=b5084542-…`, `get_portfolios()`→삼성전자(005930) 1건, `SnapshotScheduler.run_once()`→`degraded=None`, universe 1건, warning 없음. -- 영향 결론: 현재 코드가 실 계약과 일치하고 모든 AC evidence 가 정정 테스트명으로 재연결됨. REQ-027 Implemented 유지, BAT-008 release-candidate 유지. Ready for Release 변동 없음. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. - -## Human Approval Memo -- 승인자: Justin Ko (운영자 위임 — dcy-005 confirmed-by + REQ-030 Approved follow-up "portfolio-hub#8 배포 추적 후 옵션 D 구현 착수" 정책 적용) -- 승인 시각: 2026-06-06 (KST) -- 수용 범위 / 스킵 항목: - - **REQ-030 AC-2 / AC-3 (옵션 D — ledger write `*_after` 갱신)** : portfolio-hub#8 (`POST /v1/portfolios/{id}/ledger/trades` 응답 본문에 `cash_balance_after`/`asset_quantity_after`) 배포 전제. portfolio-hub 측 별도 배포(REL-003 등) 완료 전까지 trex-core 구현은 대기 — dcy-005 A-7 명시. 본 batch 의 wiring point(`SnapshotStore.update_cash_position`)는 이미 노출되어 있어 배포 후 LedgerWriter 의 ledger write 응답 소비 경로에서 호출 1줄 추가로 활성화 가능. -- 잔여 리스크: - - 옵션 D 활성 후 first ledger write 응답에서 실제 `*_after` 값을 사용해 idempotent 갱신이 동작하는지 통합 검증 필요(별도 후속 작업). - - KIS D-2 0수량 잔고 처리 규칙(설계 Decision 7)은 paper broker 회귀 가드만 있고 실 KIS 환경 관찰 evidence 부재 — 운영 단계에서 한 번 관찰 후 운영 노트화 권장. - - 스케줄러 daily fire 동작(boot 즉시 fire 외) 은 실 데몬 운영 후 첫 07:30 KST 도래 시 관찰 권장. -- 승인 근거: - - dcy-005 confirm-discovery 단계 `Handoff Decision: 승인` + REQ-030 confirm-req 단계 `Follow-up Action: portfolio-hub#8 배포 추적 후 옵션 D 구현 착수` 비차단 정책. - - 본 batch 의 M1-M8 / REQ-027~029, REQ-031~033 8개 acceptance criteria 모두 evidence 연결 완료. - - REQ-030 AC-1 (baseline 스케줄러)는 evidence 연결 완료. AC-2/AC-3 은 외부 의존(portfolio-hub#8) 대기 항목이며 본 batch wiring point 가 이미 준비됨. -- 후속 트래킹: - - portfolio-hub#8 배포 완료 알림 수신 시 별도 minor-change batch 로 옵션 D 구현 + REQ-030 재verify → REQ-030 status Approved → Implemented 전환. diff --git a/docs/batches/bat-009_20260606_paper-broker-env-tuning/implementation.md b/docs/batches/bat-009_20260606_paper-broker-env-tuning/implementation.md deleted file mode 100644 index f0704bd..0000000 --- a/docs/batches/bat-009_20260606_paper-broker-env-tuning/implementation.md +++ /dev/null @@ -1,28 +0,0 @@ -# BAT-009 Implementation - -- Status: completed -- Batch: BAT-009 - -## Plan Summary -- REQ-010 CHG-20260606-01 / AC-5: paper broker 튜닝 knob(REQ-011 AC-2 지연/부분/거부)을 데몬 env 로 노출. 미설정=기존 `PaperBroker()` 동작 byte-for-byte(회귀 없음). - -## Changed Files -- trex_core/runtime/config.py: `PaperBrokerConfig` frozen dataclass(`fill_after_polls:int=0`, `qty_per_poll:Optional[int]=None`, `reject_symbols:frozenset[str]=frozenset()`). `DaemonConfig.paper` 필드(broker=='paper' 일 때만 참조). `from_env()` 에서 `TREX_PAPER_FILL_AFTER_POLLS`(int, 기본 0), `TREX_PAPER_QTY_PER_POLL`(빈 문자열→None), `TREX_PAPER_REJECT_SYMBOLS`(콤마 분리, 공백/빈 토큰 제거→frozenset) 파싱. -- trex_core/runtime/daemon.py: `_build_broker` paper 분기를 `PaperBroker(fill_after_polls=config.paper.fill_after_polls, qty_per_poll=config.paper.qty_per_poll, reject_symbols=set(config.paper.reject_symbols))` 로 교체. -- .env.example: `TREX_BROKER=paper` 아래 paper 튜닝 키 3개 주석 문서화(미설정=즉시 전량 체결·거부 없음). -- tests/runtime/test_config_dotenv.py: `_KEYS` 에 3개 키 추가, `test_paper_broker_defaults`(미설정=0/None/frozenset()), `test_paper_broker_env_tuning`(FILL_AFTER_POLLS=2, QTY_PER_POLL=4, REJECT_SYMBOLS="005930, 000660 ," → 2/4/{005930,000660}). - -## Execution Log -1. `PaperBrokerConfig` dataclass + `DaemonConfig.paper` 필드 추가(기본값 = 기존 `PaperBroker()` 동작). -2. `from_env()` env 파싱(빈 문자열/공백 토큰 정규화). -3. `_build_broker` paper 분기 주입 + `.env.example` 문서화. -4. 단위테스트 2개 추가, 좁은 테스트 → 전체 회귀 → ruff 순서로 검증. - -## Validation -- `pytest tests/runtime/test_config_dotenv.py`: **6 passed**(기존 4 + 신규 2). -- 전체 `pytest --deselect ...::test_sandbox_token_and_submit`(네트워크 의존 KIS sandbox 제외): **167 passed, 23 skipped**. -- `ruff check trex_core tests`: All checks passed. -- e2e(별도, verification.md 에 상세): 데몬 env 재기동으로 거부 롤백 `idle→pending_buy→idle`, 지연 체결 `+3.7s`, 부분 체결 qty 6 누적 `+2.5s` 재현. - -## Remaining Risks -- 없음(저위험). broker=='paper' 일 때만 참조(kis 경로 무시), 미설정=기존 동작. 빈/공백 토큰 edge 는 단위테스트로 고정. diff --git a/docs/batches/bat-009_20260606_paper-broker-env-tuning/index.md b/docs/batches/bat-009_20260606_paper-broker-env-tuning/index.md deleted file mode 100644 index 741edc6..0000000 --- a/docs/batches/bat-009_20260606_paper-broker-env-tuning/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-009: paper broker 튜닝 env 노출 (minor-change) - -- Status: released -- Profile: batch-lite -- Source Discovery: dcy-002 (REQ-010 데몬 설정 편의) — 동기는 dcy-003/REQ-011 e2e 시나리오 재현(Notes 참조) -- Included REQ: REQ-010 (CHG-20260606-01, AC-5) -- Excluded REQ: 없음 - -## Summary -- REQ-010 CHG-20260606-01 로 추가된 AC-5(paper broker 튜닝 env 노출)를 delivery 하는 minor-change batch-lite. REQ-011 AC-2 가 이미 요구하는 paper 더블의 결정적 시나리오(지연/부분/거부)를 데몬 재기동만으로 재현할 수 있도록, 기존 `PaperBroker` 생성자 knob 을 `.env`/OS env(`TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS`)로 노출한다. 미설정이면 기존 `PaperBroker()` 동작과 byte-for-byte 동일(회귀 없음). - -## Scope -- In Scope: - - `runtime/config.py`: `PaperBrokerConfig` frozen dataclass + `DaemonConfig.from_env()` 파싱(빈 문자열 → None/frozenset 정규화). - - `runtime/daemon.py`: `_build_broker` paper 분기에서 `config.paper` 주입. - - `.env.example`: paper 튜닝 키 3개 문서화(미설정=production-like 기본). - - `tests/runtime/test_config_dotenv.py`: 기본값(test_paper_broker_defaults) + env 파싱(test_paper_broker_env_tuning). -- Out of Scope: - - PaperBroker 내부 체결 로직 변경(REQ-011 에서 이미 구현·검증). - - kis broker 경로(`broker=kis` 시 paper config 무시). - - 부분 매도(수량 일부) 등 신규 능력(별도 Discovery/REQ). - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite with no architecture impact) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- minor-change: 기존 PaperBroker 생성자 인자를 config 표면으로 노출하는 additive 변경. BrokerPort 계약·슬롯 런타임 흐름 불변, design 불요. -- 동기 출처: REQ-011(dcy-003) e2e 시나리오 재현 필요. REQ-010(dcy-002) 의 설정 로딩 메커니즘 위에 키를 얹는 형태라 REQ-010 change-req 로 귀속. -- 구현 코드는 본 batch 생성 시점에 이미 working tree 에 존재(미커밋). verification stage 에서 단위테스트(166 pass) + e2e evidence(거부 롤백/지연/부분)를 verification.md 에 정리한다. diff --git a/docs/batches/bat-009_20260606_paper-broker-env-tuning/planning.md b/docs/batches/bat-009_20260606_paper-broker-env-tuning/planning.md deleted file mode 100644 index 01e37d0..0000000 --- a/docs/batches/bat-009_20260606_paper-broker-env-tuning/planning.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-009 Planning - -- Status: confirmed -- Batch: BAT-009 -- Profile: batch-lite - -## Included REQ -- REQ-010 (Configuration): 데몬 설정 .env 로딩 — CHG-20260606-01 / AC-5 (paper 튜닝 env 노출) - -## Delivery Plan -1. `runtime/config.py`: `PaperBrokerConfig(fill_after_polls:int=0, qty_per_poll:Optional[int]=None, reject_symbols:frozenset[str]=frozenset())` 추가. `DaemonConfig` 에 `paper: PaperBrokerConfig` 필드(broker=='paper' 일 때만 참조). `from_env()` 에서 `TREX_PAPER_FILL_AFTER_POLLS`(int, 기본 0), `TREX_PAPER_QTY_PER_POLL`(빈 문자열→None), `TREX_PAPER_REJECT_SYMBOLS`(콤마 분리, 공백/빈 토큰 제거→frozenset) 파싱. -2. `runtime/daemon.py`: `_build_broker` paper 분기를 `PaperBroker(fill_after_polls=..., qty_per_poll=..., reject_symbols=set(...))` 로 교체. -3. `.env.example`: `TREX_BROKER=paper` 아래 paper 튜닝 키 3개 주석 문서화(미설정=즉시 전량 체결·거부 없음). -4. `tests/runtime/test_config_dotenv.py`: `_KEYS` 에 3개 키 추가, `test_paper_broker_defaults`(미설정=0/None/frozenset()), `test_paper_broker_env_tuning`(FILL_AFTER_POLLS=2, QTY_PER_POLL=4, REJECT_SYMBOLS="005930, 000660 ," → 2/4/{005930,000660}). - -## Dependencies -- Internal: REQ-011 PaperBroker(생성자 knob 이미 존재), REQ-010 dotenv 로딩 메커니즘. -- External: 없음. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 기존 PaperBroker 생성자 인자를 config/env 표면으로 노출하는 additive wiring. 외부 인터페이스(BrokerPort)·슬롯 런타임 흐름 변경 없음. 미설정 시 기존 동작과 byte-for-byte 동일. batch-lite + design 생략(minor-change). - -## Milestones -1. M1 — PaperBrokerConfig + from_env 파싱. -2. M2 — _build_broker 주입 + .env.example 문서화. -3. M3 — 단위테스트 green(기본/파싱) + ruff clean + 전체 회귀(166 pass). -4. M4 — e2e 시나리오 evidence(거부 롤백 `idle→pending_buy→idle`, 지연 `+3.7s`, 부분 qty 누적 `+2.5s`). - -## Risks -- R-1 (Low): env 오설정이 production 데몬 동작을 바꿈. 대응: broker=='paper' 일 때만 참조(kis 경로 무시), 미설정=기본 동작, `.env.example` 에 "e2e 전용" 명시. -- R-2 (Low): 빈 문자열/공백 토큰 파싱 edge. 대응: `qty_per_poll` 빈 문자열→None, `reject_symbols` 빈/공백 토큰 제거 — 단위테스트로 고정. diff --git a/docs/batches/bat-009_20260606_paper-broker-env-tuning/verification.md b/docs/batches/bat-009_20260606_paper-broker-env-tuning/verification.md deleted file mode 100644 index cab5bce..0000000 --- a/docs/batches/bat-009_20260606_paper-broker-env-tuning/verification.md +++ /dev/null @@ -1,27 +0,0 @@ -# BAT-009 Verification - -- Status: confirmed -- Batch: BAT-009 - -## Acceptance Mapping -- REQ-010 (CHG-20260606-01): - - AC1 `.env` 로드 → `test_loads_values_from_dotenv`. **PASS** (BAT-003 기존, 회귀 재확인) - - AC2 OS env 우선 → `test_os_env_takes_precedence_over_dotenv`. **PASS** (BAT-003 기존) - - AC3 `.env` 부재 회귀 → `test_no_dotenv_uses_os_env_only`. **PASS** (BAT-003 기존) - - AC4 `.gitignore` → `.env` 무시 / `.env.example` 추적. **PASS** (BAT-003 기존) - - **AC5 (본 batch)** paper 튜닝 env 파싱 + `_build_broker` 주입, 미설정=기본 동작 byte-for-byte: - - 단위: `test_paper_broker_defaults`(미설정 → fill_after_polls=0, qty_per_poll=None, reject_symbols=frozenset()). **PASS** - - 단위: `test_paper_broker_env_tuning`(FILL_AFTER_POLLS=2 / QTY_PER_POLL=4 / REJECT_SYMBOLS="005930, 000660 ," → 2 / 4 / {005930,000660}). **PASS** - - e2e(데몬 env 재기동): 거부(`TREX_PAPER_REJECT_SYMBOLS=999999`) → `idle→pending_buy→idle` 롤백; 지연(`TREX_PAPER_FILL_AFTER_POLLS=3`) → `pending_buy→holding +3.7s`; 부분(`TREX_PAPER_QTY_PER_POLL=2`, qty 6) → holding qty 6 누적 `+2.5s`. **PASS** - -## Evidence -- Test: `pytest tests/runtime/test_config_dotenv.py` 6 passed(기존 4 + 신규 2). 전체 `pytest --deselect ...::test_sandbox_token_and_submit` **167 passed, 23 skipped**. `ruff check trex_core tests` All checks passed. -- e2e: 실 PostgreSQL + RabbitMQ + portfolio-hub(dev) 대상 데몬 e2e. 슬롯 전이 trail(`slot_transition_log`)·timestamp gap 으로 시나리오별 결정성 확인. (REL-001 Feedback Handoff Observation Summary 와 동일 evidence) - -## no architecture impact confirmed -- batch-lite, planning Design Gate=no. 기존 `PaperBroker` 생성자 인자를 config/env 표면으로 노출하는 additive wiring 뿐 — BrokerPort 계약·슬롯 런타임 흐름 변경 없음. 미설정 시 기존 동작 byte-for-byte. baseline 문서(`docs/project-structure.md`/`docs/runtime-flows.md`) 영향 없음. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. e2e 는 paper broker 한정(운영 KIS 경로 무영향). REL-001 배포 scope 와 분리된 후속 release 로 배포. diff --git a/docs/batches/bat-010_20260607_safety-sell-foundation/design.md b/docs/batches/bat-010_20260607_safety-sell-foundation/design.md deleted file mode 100644 index dbd5f0d..0000000 --- a/docs/batches/bat-010_20260607_safety-sell-foundation/design.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-010 Design - -- Status: confirmed -- Batch: BAT-010 - -## Architecture Summary -BAT-010은 safety-sell 기능의 기반 레이어 세 가지를 추가한다. - -1. **WatchdogStore** (`application/watchdog.py` 신규): `dict[str, datetime]` in-memory store. snapshot RPC handler가 응답 슬롯의 `last_seen_at`을 갱신하고 BAT-011 트리거 루프가 만료 판정에 사용한다. -2. **도메인 필드 확장** (`models.py`, `postgresql.py`, SQL 마이그레이션): `stop_loss_price`, `take_profit_price` `Optional[Decimal]` additive 추가. BAT-011 KIS polling이 트리거 기준으로 사용한다. -3. **SlotService.sell()** (`domain/slot/service.py`): RabbitMQ 우회 내부 매도 경로. BAT-011의 모든 트리거 루프(forced_close, KIS polling, 장 마감)가 이 메서드를 호출한다. - -## Changed Areas -- Docs: - - 없음 (baseline 문서 갱신은 REQ-042 / BAT-011에서 처리) -- Modules: - - `trex_core/runtime/config.py` — `DaemonConfig`에 safety-sell env vars 5종 파싱 추가 - - `trex_core/domain/slot/models.py` — `stop_loss_price: Optional[Decimal]`, `take_profit_price: Optional[Decimal]` 추가 - - `trex_core/adapters/persistence/postgresql.py` — 두 필드 read/write 포함 - - `trex_core/adapters/persistence/sql/` — 마이그레이션 스크립트 신규 추가 - - `trex_core/application/intents.py` — `IntentIn`에 `stop_loss_price`, `take_profit_price` Optional 필드 추가 - - `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler` 확장(`last_seen_at` 갱신) + intent RPC payload 파싱 확장(price fields) - - `trex_core/application/watchdog.py` (신규) — `WatchdogStore` class - - `trex_core/domain/slot/service.py` — `SlotService.sell(slot_id, actor, reason)` 추가 -- Interfaces: - - `SlotService.sell(slot_id: str, actor: str, reason: str) -> None` (신규 내부 인터페이스) - - `WatchdogStore.update(slot_id: str) -> None` (신규) - - `WatchdogStore.is_expired(slot_id: str, timeout_s: int) -> bool` (신규 — BAT-011 트리거 루프 계약) - - `WatchdogStore.in_grace_period(grace_s: int) -> bool` (신규) - -## Key Decisions -1. **WatchdogStore를 `watchdog.py` 신규 모듈로 분리**: `portfolio.py`는 snapshot 조회·직렬화 책임이 있어 watchdog 관심사(타임스탬프 추적·만료 판정)를 추가하면 단일 책임 위반. 신규 모듈이 독립 테스트와 BAT-011 참조에 유리하다. -2. **REQ-041 env vars 5종 전체를 BAT-010에서 파싱 완성**: `TREX_FORCED_CLOSE_CHECK_INTERVAL`·`TREX_STOP_LOSS_POLL_INTERVAL`·`TREX_MARKET_CLOSE_LIQUIDATION_TIME`은 BAT-010에서 사용하지 않지만, REQ-041을 두 batch에 분리하면 REQ 단위 거버넌스 trace가 깨진다. 파싱만 완성하고 BAT-011이 config 값을 참조한다. -3. **WatchdogStore 동시성은 Python GIL에 의존**: `dict[str, datetime]` 단일 dict의 Python 레벨 read/write는 GIL 보장 하에 원자적. 별도 `threading.Lock` 불필요. grace period 판정: `datetime.utcnow() - self.started_at < timedelta(seconds=grace_s)` 단순 비교. -4. **SlotService.sell()은 transit_slot() 재사용**: 기존 `transit_slot(slot_id, from_state=HOLDING, to_state=PENDING_SELL, ...)` advisory lock + outbox 경로를 그대로 재사용. 신규 로직을 최소화해 BAT-004 검증 coverage를 그대로 이어받는다. - -## Edge Cases -- grace period 중 `is_expired()` 호출: `in_grace_period()` True이면 store에 없는 슬롯 포함 전체 만료 판정 유예. -- 데몬 재시작 시 watchdog store: in-memory 초기화 + `started_at` 재설정으로 grace period 자동 재시작. -- `sell()` 호출 시 이미 `pending_sell` 상태: `transit_slot` 호출 전 상태 조회 → silent return (중복 매도 intent 제출 없음). -- `sell()` 호출 시 `idle`·`cancelled` 등 비정상 상태: `SlotStateError`(또는 동등 예외) raise, 슬롯 상태 불변. -- 마이그레이션 기존 row: `ALTER TABLE slots ADD COLUMN IF NOT EXISTS stop_loss_price NUMERIC` 패턴으로 멱등성 보장. 기존 row `NULL` 유지. - -## Architecture Impact -- runtime-flows: snapshot RPC handler → watchdog `last_seen_at` 갱신 흐름 추가 (BAT-011 REQ-042에서 문서화) -- project-structure: `watchdog.py` 신규 모듈 추가 (BAT-011 REQ-042에서 문서화) - -## Reference Doc Update Plan -- project-structure.md: BAT-011 REQ-042에서 처리 (`watchdog.py` 신규 모듈 반영) -- runtime-flows.md: BAT-011 REQ-042에서 처리 (snapshot RPC → watchdog update + sell() 내부 경로 반영) diff --git a/docs/batches/bat-010_20260607_safety-sell-foundation/implementation.md b/docs/batches/bat-010_20260607_safety-sell-foundation/implementation.md deleted file mode 100644 index fae4968..0000000 --- a/docs/batches/bat-010_20260607_safety-sell-foundation/implementation.md +++ /dev/null @@ -1,43 +0,0 @@ -# BAT-010 Implementation - -- Status: completed -- Batch: BAT-010 - -## Plan Summary -- REQ-041 → REQ-035 → REQ-034 → REQ-036 순서로 구현. 각 단계 unit test green 확인 후 다음으로 진행. - -## Changed Files - -### 신규 파일 -- `trex_core/application/watchdog.py` — WatchdogStore class (REQ-034) -- `trex_core/adapters/persistence/sql/004_add_price_thresholds.sql` — additive DB migration (REQ-035) -- `tests/runtime/test_safety_sell_config.py` — REQ-041 AC 검증 4종 -- `tests/application/test_watchdog.py` — REQ-034 AC 검증 11종 -- `tests/application/test_snapshot_rpc_watchdog.py` — REQ-034 handler 통합 4종 -- `tests/domain/slot/test_service_sell.py` — REQ-036 AC 검증 6종 -- `tests/domain/slot/test_price_threshold_fields.py` — REQ-035 AC 검증 7종 - -### 기존 파일 수정 -- `trex_core/runtime/config.py` — SafetySellConfig dataclass 추가 + DaemonConfig 5종 env var 파싱 (REQ-041) -- `trex_core/domain/slot/models.py` — stop_loss_price / take_profit_price Optional[Decimal] 필드 추가 + EXPECTED_SLOT_FIELDS 갱신 (REQ-035) -- `trex_core/adapters/persistence/postgresql.py` — _SLOT_COLUMNS 갱신, update_slot SET절 확장, _row_to_slot 인덱스 조정 (REQ-035) -- `trex_core/application/intents.py` — BuyIntent에 price fields + Decimal import (REQ-035) -- `trex_core/domain/slot/service.py` — transit_slot에 stop_loss_price / take_profit_price 파라미터 추가, _apply_transition_fields 확장(reset + preserve), sell() 메서드 신설, Decimal import (REQ-035, REQ-036) -- `trex_core/application/messaging_handlers.py` — WatchdogStore import + SlotState import + make_snapshot_rpc_handler watchdog_store= 파라미터 + 홀딩 슬롯 update 로직 + buy intent payload _opt_decimal 파싱 (REQ-034, REQ-035) - -## Execution Log -1. M1 — REQ-041: SafetySellConfig + DaemonConfig.from_env() 5종 파싱. test_safety_sell_config.py 4종 green. -2. M2 — REQ-035: models.py 필드 추가 + EXPECTED_SLOT_FIELDS 갱신 → test_contract.py 통과. postgresql.py _SLOT_COLUMNS / update_slot / _row_to_slot 갱신. SQL 마이그레이션 004 생성. intents.py BuyIntent 확장. service.py transit_slot + _apply_transition_fields 확장. test_price_threshold_fields.py 7종 green. -3. M3 — REQ-034: watchdog.py 신규. messaging_handlers.py make_snapshot_rpc_handler 확장. test_watchdog.py 11종 green. test_snapshot_rpc_watchdog.py 4종 green. -4. M4 — REQ-036: service.py sell() 신설. test_service_sell.py 6종 green. -5. M5 — 전체 회귀 95 passed (httpx 미설치 환경 기인 test_daemon_no_http_surface 1건 기존 실패, BAT-010 무관). - -## Validation -- 신규 테스트: 32종 100% green -- 회귀 테스트: 95 passed (domain/ + runtime/ + application/ 대상, httpx 불필요 범위) -- test_contract.py: EXPECTED_SLOT_FIELDS ↔ dataclasses.fields(Slot) 계약 통과 (price fields 포함) -- `WatchdogStore.in_grace_period()` + `is_expired()` 조합 패턴 단위 테스트로 고정 - -## Remaining Risks -- R-2 (Medium → Low): DB 마이그레이션은 실 PostgreSQL 없이 검증 불가. 004 migration이 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` 패턴 사용 → 멱등 보장. 실 DB 검증은 BAT-011 환경 포함 후 수행. -- R-3 (Low): test_daemon_no_http_surface 기존 실패 — httpx 미설치 환경 이슈. BAT-010 변경 무관. 런타임 환경에서 httpx 설치 시 해소. diff --git a/docs/batches/bat-010_20260607_safety-sell-foundation/index.md b/docs/batches/bat-010_20260607_safety-sell-foundation/index.md deleted file mode 100644 index ed3aa19..0000000 --- a/docs/batches/bat-010_20260607_safety-sell-foundation/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-010: safety-sell 기반 인프라 (watchdog + 가격 임계값 필드 + SlotService.sell) - -- Status: released -- Profile: standard -- Source Discovery: dcy-007 -- Included REQ: REQ-034, REQ-035, REQ-036, REQ-041 -- Excluded REQ: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 (safety-sell 트리거 레이어 — BAT-011 예정) - -## Summary -- dcy-007(core 자율 안전 매도) 기반 인프라 레이어를 delivery한다. per-slot 워치독 store(REQ-034), 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션(REQ-035), 자율 매도 내부 경로 `SlotService.sell()`(REQ-036), safety-sell 환경 변수 전체 파싱(REQ-041)을 포함한다. 트리거 루프(forced_close/KIS polling/장 마감)와 `update_thresholds` RPC는 BAT-011에서 처리한다. - -## Scope -- In Scope: - - `WatchdogStore` 신규 모듈(`application/watchdog.py`) + `make_snapshot_rpc_handler` 확장 (REQ-034) - - `stop_loss_price`, `take_profit_price` Optional[Decimal] 도메인 필드 + PostgreSQL additive 마이그레이션 (REQ-035) - - `SlotService.sell(slot_id, actor, reason)` + idempotency (REQ-036) - - `DaemonConfig` env vars 5종 파싱: `TREX_WATCHDOG_TIMEOUT`, `TREX_WATCHDOG_GRACE_PERIOD`, `TREX_FORCED_CLOSE_CHECK_INTERVAL`, `TREX_STOP_LOSS_POLL_INTERVAL`, `TREX_MARKET_CLOSE_LIQUIDATION_TIME` (REQ-041) -- Out of Scope: - - forced_close_at 루프 — BAT-011 (REQ-037) - - KIS 현재가 polling — BAT-011 (REQ-038) - - 장 마감 청산 루프 — BAT-011 (REQ-039) - - update_thresholds intent RPC — BAT-011 (REQ-040) - - baseline 문서 정렬 — BAT-011 (REQ-042) - - 가격 임계값 논리 유효성 검증(take_profit > stop_loss 등) — REQ-035 Notes 명시 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- BAT-010은 외부 API(KIS) 호출이 없고 신규 daemon thread를 추가하지 않는다. 단위 테스트와 persistence 통합 테스트만으로 모든 AC를 검증 가능한 레이어다. -- REQ-041의 env vars 5종 중 BAT-010에서 실제 사용되는 것은 `TREX_WATCHDOG_TIMEOUT` + `TREX_WATCHDOG_GRACE_PERIOD` 2개다. 나머지 3종(`TREX_FORCED_CLOSE_CHECK_INTERVAL`, `TREX_STOP_LOSS_POLL_INTERVAL`, `TREX_MARKET_CLOSE_LIQUIDATION_TIME`)은 파싱만 완성하고 BAT-011에서 참조한다. REQ-041을 두 batch에 분리하지 않기 위한 결정(design.md Key Decision 2 참조). -- WatchdogStore 구현 위치: `trex_core/application/watchdog.py` 신규 모듈(design.md Key Decision 1 참조). diff --git a/docs/batches/bat-010_20260607_safety-sell-foundation/planning.md b/docs/batches/bat-010_20260607_safety-sell-foundation/planning.md deleted file mode 100644 index f334d53..0000000 --- a/docs/batches/bat-010_20260607_safety-sell-foundation/planning.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-010 Planning - -- Status: confirmed -- Batch: BAT-010 -- Profile: standard - -## Included REQ -- REQ-041 (Configuration): safety-sell 환경 변수 5종 파싱 — `TREX_WATCHDOG_TIMEOUT`(s, 기본 120), `TREX_WATCHDOG_GRACE_PERIOD`(s, 기본 60), `TREX_FORCED_CLOSE_CHECK_INTERVAL`(s, 기본 60), `TREX_STOP_LOSS_POLL_INTERVAL`(s, 기본 30), `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional) -- REQ-035 (Data): `stop_loss_price` · `take_profit_price` `Optional[Decimal]` 도메인 필드 + PostgreSQL additive 마이그레이션 -- REQ-034 (Interface): per-slot 워치독 — `WatchdogStore(dict[slot_id, datetime])` + snapshot RPC handler 확장 + grace period -- REQ-036 (Interface): `SlotService.sell(slot_id, actor, reason)` — `holding→pending_sell` 전이 + idempotency - -## Delivery Plan -1. REQ-041: `runtime/config.py`에 safety-sell env vars 5종 파싱 추가 (`DaemonConfig` 확장). 파싱 단위 테스트. -2. REQ-035: `domain/slot/models.py`에 두 필드 추가 → `adapters/persistence/postgresql.py` read/write 포함 → SQL 마이그레이션 스크립트 작성 → `application/intents.py` + `messaging_handlers.py` payload 파싱 추가. 필드 저장·조회 round-trip 테스트 + 마이그레이션 멱등성 테스트. -3. REQ-034: `application/watchdog.py` 신규 작성 (`WatchdogStore` class, grace period 판정) → `make_snapshot_rpc_handler` 확장 (응답 슬롯 `last_seen_at` 갱신). 워치독 판정 단위 테스트 (만료/비만료/grace period 시나리오). -4. REQ-036: `domain/slot/service.py`에 `sell(slot_id, actor, reason)` 추가 (`transit_slot` 기반, idempotency). 단위 테스트 3종(holding→pending_sell, pending_sell 중복 호출, idle 오류). -5. 전체 회귀 테스트 green + ruff lint clean. - -## Dependencies -- Internal: - - BAT-007/REQ-022: `make_snapshot_rpc_handler` 기존 구현 — 확장 대상 - - BAT-008/REQ-027: `SlotService` 기존 구조 + `transit_slot` 패턴 — `sell()` 기반 - - BAT-004/REQ-011~012: `PaperBroker` execution 경로 — `sell()` 통합 검증 대상 -- External: - - 없음 (KIS API 호출 없음, 신규 daemon thread 없음) - -## Design Gate -- Design Required Before Implementation: yes -- Reason: `WatchdogStore`는 신규 in-memory 컴포넌트로 snapshot RPC handler thread에서 write, BAT-011 트리거 루프 thread에서 read가 발생한다. 동시성 모델(GIL 의존 vs. explicit lock)과 모듈 배치(`watchdog.py` 분리 vs. `portfolio.py` 확장)를 설계 단계에서 확정해야 BAT-011 트리거 루프 설계 시 인터페이스 충돌이 없다. `SlotService.sell()`과 `transit_slot` advisory lock 통합 방식도 설계에서 명시한다. - -## Milestones -1. M1 — REQ-041: env vars 파싱 + unit test green -2. M2 — REQ-035: 도메인 필드 + 마이그레이션 + persistence round-trip 테스트 green -3. M3 — REQ-034: `WatchdogStore` + snapshot RPC handler 확장 + 워치독 판정 unit test green -4. M4 — REQ-036: `SlotService.sell()` + idempotency unit test green (3종) -5. M5 — 전체 회귀 + ruff lint clean - -## Risks -- R-1 (Medium): WatchdogStore 동시성 — BAT-011 트리거 루프가 watchdog store를 read하는 시점에 snapshot RPC handler가 동시 write할 수 있음. 대응: Python GIL 보장 하에 단순 `dict` 사용; 별도 `threading.Lock` 회피. 단위 테스트에서 판정 로직 고정. -- R-2 (Medium): DB 마이그레이션 기존 row 영향. 대응: `NULL` 허용 additive 컬럼 추가 전용(`ALTER TABLE … ADD COLUMN IF NOT EXISTS`). 기존 row `NULL` 유지. migration 멱등성 테스트. -- R-3 (Low): `SlotService.sell()` advisory lock 통합 — `transit_slot` 기존 advisory lock과 충돌 가능성. 대응: 기존 `transit_slot()` 코드 경로를 최대한 재사용하고 신규 로직을 최소화한다. diff --git a/docs/batches/bat-010_20260607_safety-sell-foundation/verification.md b/docs/batches/bat-010_20260607_safety-sell-foundation/verification.md deleted file mode 100644 index 24ef215..0000000 --- a/docs/batches/bat-010_20260607_safety-sell-foundation/verification.md +++ /dev/null @@ -1,78 +0,0 @@ -# BAT-010 Verification - -- Status: verified -- Batch: BAT-010 - -## Acceptance Mapping - -### REQ-041 (SafetySellConfig env vars) - -| AC | Evidence | -|----|---------| -| AC-1: 기본값 120/60/60/30/None | `test_safety_sell_config.py::test_safety_sell_config_defaults` — 5개 기본값 모두 assert | -| AC-2: TREX_WATCHDOG_TIMEOUT 파싱 | `test_safety_sell_config_env_tuning` — 180 파싱 확인 | -| AC-3: TREX_WATCHDOG_GRACE_PERIOD 파싱 | 동일 테스트 — 90 파싱 확인 | -| AC-4: TREX_FORCED_CLOSE_CHECK_INTERVAL 파싱 | 동일 테스트 — 45 파싱 확인 | -| AC-5: TREX_STOP_LOSS_POLL_INTERVAL 파싱 | 동일 테스트 — 15 파싱 확인 | -| TREX_MARKET_CLOSE_LIQUIDATION_TIME Optional | `test_safety_sell_config_env_tuning` "15:30" 파싱 + `test_market_close_liquidation_time_empty_string_becomes_none` None 변환 | - -### REQ-035 (stop_loss_price / take_profit_price 필드) - -| AC | Evidence | -|----|---------| -| AC-1: 기본값 None | `test_price_threshold_fields.py::test_price_fields_default_to_none` | -| AC-2: Decimal 저장·조회 round-trip | `test_price_fields_set_on_pending_buy` — Decimal("48000") round-trip via in-memory repo | -| AC-3: 마이그레이션 멱등성 | `004_add_price_thresholds.sql` `IF NOT EXISTS` 패턴. 실 PostgreSQL round-trip은 Remaining Risks R-2 참조 | -| AC-4: 매수 intent payload 포함 시 저장 | `test_price_fields_preserved_through_holding` — pending_buy→holding 전이 후 price fields 유지 | -| AC-5: 미포함 시 None | `test_no_price_fields_sets_none` | -| 계약 가드 | `test_contract.py::test_slot_fields_match_expected_contract` — EXPECTED_SLOT_FIELDS 포함 확인 | - -### REQ-034 (per-slot WatchdogStore) - -| AC | Evidence | -|----|---------| -| AC-1: snapshot RPC 처리 시 holding slot last_seen_at 갱신 | `test_snapshot_rpc_watchdog.py::test_holding_slot_watchdog_updated_on_snapshot_rpc` | -| AC-2: timeout 초과 슬롯 만료 판정 | `test_watchdog.py::TestIsExpired::test_old_update_is_expired` (200s > 120s timeout) | -| AC-3: timeout 미초과 슬롯 비만료 | `test_just_within_timeout_not_expired` (100s < 120s timeout) | -| AC-4: grace period 중 만료 유예 | `test_grace_period_defers_expiry_check` — in_grace_period+is_expired 조합 패턴 검증 | -| AC-5: 재시작 시 store 초기화 | `test_new_store_is_empty` | -| AC-6: 정상 poll 주기 슬롯 비만료 | `test_recently_updated_slot_not_expired` — update 직후 is_expired=False | -| holding만 갱신, idle은 제외 | `test_idle_slot_watchdog_not_updated` | -| 복수 holding 슬롯 모두 갱신 | `test_multiple_holding_slots_all_updated` | -| backward compat (watchdog_store=None) | `test_no_watchdog_store_does_not_raise` | - -### REQ-036 (SlotService.sell() + idempotency) - -| AC | Evidence | -|----|---------| -| AC-1: holding → pending_sell 전이 | `test_service_sell.py::test_sell_holding_transitions_to_pending_sell` | -| AC-2: pending_sell 중복 호출 → 무전이 | `test_sell_idempotent_on_pending_sell` — transition_log PENDING_SELL 항목 1건만 확인 | -| AC-3: idle 호출 → 오류, 상태 불변 | `test_sell_idle_slot_raises_transition_error` | -| AC-4: execution 경로 재사용 | `test_sell_records_transition_log` — transit_slot 기반으로 log append 확인 | -| AC-5: transport 독립 | `sell()` 구현이 RabbitMQ 의존 없는 service.transit_slot() 직접 호출 — 코드 검사로 확인 | -| SlotNotFoundError | `test_sell_unknown_slot_raises_not_found` | -| price fields 보존 | `test_sell_preserves_stop_loss_price` | - -## Evidence - -- Test: - - 신규 테스트 32종 all green (pytest 출력 확인) - - `tests/domain/ + tests/runtime/ + 대상 tests/application/` 회귀 95 passed - - `test_contract.py` 통과 — EXPECTED_SLOT_FIELDS 계약 유지 -- Manual: - - 없음 (단위 테스트로 모든 AC 커버) -- Logs: - - `32 passed in 0.09s` (신규 테스트 단독) - - `95 passed in 0.13s` (회귀 포함) - -## Architecture Impact Note - -- BAT-010은 project-structure.md / runtime-flows.md 업데이트를 BAT-011 REQ-042에 위임 -- design.md Reference Doc Update Plan과 일치: 현 batch는 구현 evidence 확보 범위 -- `test_daemon_no_http_surface` 기존 실패는 httpx 미설치 환경 이슈로 BAT-010 변경과 무관 - -## Result - -- Ready for Release: true -- Blocking Issues: - - R-2: 실 PostgreSQL migration round-trip 미검증 — `IF NOT EXISTS` 패턴으로 안전성 확보, BAT-011 환경에서 검증 예정. release gate에서는 수용(additive-only schema 변경, 기존 row 영향 없음). diff --git a/docs/batches/bat-011_20260607_safety-sell-triggers/design.md b/docs/batches/bat-011_20260607_safety-sell-triggers/design.md deleted file mode 100644 index 556fa62..0000000 --- a/docs/batches/bat-011_20260607_safety-sell-triggers/design.md +++ /dev/null @@ -1,55 +0,0 @@ -# BAT-011 Design - -- Status: confirmed -- Batch: BAT-011 - -## Architecture Summary -BAT-011은 BAT-010 기반 위에 자동 매도를 실제로 발동하는 트리거 레이어를 추가한다. - -1. **`trex-safety-sell` worker 스레드** (`runtime/daemon.py`): 기존 6 스레드(execution/ledger/intent-rpc/snapshot-rpc/pause-rpc/snapshot-scheduler)에 7번째로 추가. `_safety_sell_loop()`가 단일 통합 루프에서 forced_close_at 만기(REQ-037), stop-loss·take-profit 가격 체크(REQ-038), 장 마감 청산(REQ-039)을 순차 평가한다. -2. **KIS 현재가 polling 어댑터** (`adapters/broker/kis.py` 확장): `get_current_price(symbol) -> Decimal` — `TTTC8397R`(실전)/`VTTC8397R`(모의) 시세 endpoint 호출, `stck_prpr`(직전 체결가) 파싱. paper 환경 stub. -3. **`update_thresholds` intent RPC** (`application/messaging_handlers.py` + `domain/slot/service.py`): `make_intent_rpc_handler`에 action 라우팅 추가, `SlotService.update_thresholds()`로 부분 갱신 + 워치독 `last_seen_at` 리셋. -4. **baseline 문서 정합** (REQ-042): 구현 완료 후 3문서 갱신. - -## Changed Areas -- Docs: - - `docs/interface-contract.md` — `update_thresholds` action 계약 + intent payload `stop_loss_price`/`take_profit_price` 선택 필드 + snapshot RPC 워치독 갱신 신호 명시 (REQ-040, REQ-042) - - `docs/runtime-flows.md` — 워치독 갱신·forced_close 체크·가격 체크·장 마감 청산 흐름 추가 + `:114` "A-5 미구현" 주석 갱신 (REQ-042) - - `docs/project-structure.md` — `trex-safety-sell` 스레드 + env 5종 + KIS 현재가 polling/watchdog 모듈 반영 (REQ-042) -- Modules: - - `trex_core/runtime/daemon.py` — `trex-safety-sell` worker 등록 + `_safety_sell_loop()` 구현(forced_close/가격/장 마감 통합) - - `trex_core/adapters/broker/kis.py` — `get_current_price(symbol)` 추가(`TTTC8397R`/`VTTC8397R`, `stck_prpr`) - - `trex_core/application/broker_port.py` — (필요 시) `get_current_price` 포트 시그니처 추가; paper 더블 stub 구현 - - `trex_core/adapters/broker/paper.py` — `get_current_price` stub(고정/주입 가능 현재가) — 테스트·모의용 - - `trex_core/domain/slot/service.py` — `SlotService.update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)` 추가 + 트리거 조건 평가 헬퍼(또는 worker 인라인) - - `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler`에 `action: "update_thresholds"` 라우팅 + holding-only 응답 -- Interfaces: - - `Broker.get_current_price(symbol: str) -> Decimal | None` (신규 포트 메서드) - - `SlotService.update_thresholds(slot_id, stop_loss_price=..., take_profit_price=..., forced_close_at=...) -> str` (부분 갱신, sentinel로 "미지정" vs "null 해제" 구분) - - intent RPC `action: "update_thresholds"` payload/response 계약 (REQ-040) - -## Key Decisions (초안 — 구현 전 확정 대상) -1. **단일 통합 worker 루프 vs. 분리 스레드**: REQ-037 Notes 권고에 따라 `trex-safety-sell` 단일 스레드 통합 루프를 기본안으로 한다. 가격 polling 주기(`TREX_STOP_LOSS_POLL_INTERVAL`, 기본 30s)와 forced_close 주기(`TREX_FORCED_CLOSE_CHECK_INTERVAL`, 기본 60s)가 다르므로, 루프 base tick을 짧은 쪽(또는 GCD)으로 두고 각 체크에 last-run 타임스탬프로 sub-scheduling한다. (확정 필요) -2. **KIS 현재가 어댑터 배치**: `kis.py`에 `get_current_price` 추가(별도 `kis_price.py` 분리 대신) — 토큰/세션/`_account` 재사용 이점. paper는 `paper.py` stub. (확정 필요) -3. **WatchdogStore read 동시성**: BAT-010 Key Decision 3 계약 승계 — GIL 보장 단일 dict, lock 없음. worker는 `is_expired()`/`in_grace_period()` read만 수행. -4. **장 마감 1회 발동 가드**: in-memory `last_liquidation_date: date | None`. 재시작 시 초기화(REQ-039 Notes). 같은 거래일 재발동 방지. 발동 시각 도래 판정은 `now(KST).time() >= liquidation_time and last_liquidation_date != today`. -5. **`update_thresholds` 부분 갱신 sentinel**: payload에 키가 없으면 "유지", 키가 `null`이면 "해제(NULL)". `_UNSET` sentinel로 두 경우를 구분(REQ-040 AC-4/AC-5). -6. **루프 예외 격리**: tick 단위 try/except로 polling/sell 예외가 worker 스레드를 죽이지 않게 한다. 예외는 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합). - -## Edge Cases -- 가격 polling 중 KIS 거부/타임아웃: 해당 슬롯 skip + 경고 로그, 다음 tick 재시도(R-4). -- forced_close와 stop-loss가 동시 충족: 둘 다 `sell()` 호출하나 REQ-036 idempotency로 1회만 전이. -- 장 마감 청산 시각이 forced_close 체크 사이에 도래: 통합 루프 tick에서 장 마감 평가가 우선 실행되도록 순서 고정. -- `update_thresholds`로 `forced_close_at`만 갱신: 나머지 필드 유지(sentinel), `last_seen_at` 리셋(워치독 타이머 재시작, REQ-034). -- non-holding 슬롯에 `update_thresholds`: 필드 불변 + `{status: ignored, reason: "not_holding"}` (REQ-040 AC-6). -- 쿼터 상한 초과: 경고 로그만(본 batch 범위) — 동적 간격 조정은 out of scope. - -## Architecture Impact -- runtime-flows: safety-sell worker 4종 흐름(워치독 갱신은 snapshot RPC 측) 추가 — REQ-042에서 문서화. -- project-structure: 7번째 스레드 `trex-safety-sell` + KIS 현재가 polling 어댑터 메서드 — REQ-042에서 문서화. -- broker_port 계약 확장(`get_current_price`)이 paper/kis 양 더블에 반영되어야 한다. - -## Reference Doc Update Plan -- `interface-contract.md`: `update_thresholds` action + price 선택 필드 + snapshot RPC 워치독 신호 (REQ-042 AC-1/AC-2) -- `runtime-flows.md`: 4종 safety-sell 흐름 + `:114` A-5 주석 갱신 (REQ-042 AC-3/AC-4) -- `project-structure.md`: `trex-safety-sell` 스레드 + env 5종 + 신규 모듈 (REQ-042 AC-5) diff --git a/docs/batches/bat-011_20260607_safety-sell-triggers/implementation.md b/docs/batches/bat-011_20260607_safety-sell-triggers/implementation.md deleted file mode 100644 index e258aab..0000000 --- a/docs/batches/bat-011_20260607_safety-sell-triggers/implementation.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-011 Implementation - -- Status: completed -- Batch: BAT-011 - -## Plan Summary -- 구현 순서: REQ-038 KIS 현재가 어댑터 → `SafetySellService`(REQ-037/038/039) + `trex-safety-sell` worker 배선 → REQ-040 `update_thresholds` RPC → REQ-042 baseline 문서 정합. 각 단계 unit test green 확인 후 진행. - -## Changed Files - -### 신규 파일 -- `trex_core/application/safety_sell.py` — `SafetySellService`: forced_close(REQ-037) / stop-loss·take-profit(REQ-038) / 장 마감 청산(REQ-039) + run_once 캐던스. watchdog read-only, sell() 통해 매도, tick 예외 격리. -- `tests/adapters/broker/test_kis_price.py` — KIS 현재가 어댑터 7종(tr_id 분기, stck_prpr 파싱, 에러→None, paper stub). -- `tests/application/test_safety_sell_worker.py` — 트리거 서비스 23종(forced_close 6 / 가격 10 / 장 마감 6 / run_once 2). -- `tests/application/test_update_thresholds_handler.py` — update_thresholds RPC 8종(부분 갱신/null 해제/non-holding/last_seen 리셋/미존재). - -### 기존 파일 수정 -- `trex_core/application/broker_port.py` — `BrokerPort.get_current_price(symbol) -> Optional[Decimal]` 포트 추가 (REQ-038). -- `trex_core/adapters/broker/kis.py` — `_tr_price()`(TTTC8397R/VTTC8397R) + `get_current_price()`(`inquire-price`, `stck_prpr` 파싱, 에러→None) (REQ-038). -- `trex_core/adapters/broker/paper.py` — `set_current_price()`/`get_current_price()` 프로그래머블 stub (REQ-038). -- `trex_core/domain/slot/service.py` — `SlotService.update_thresholds()` + `_UNSET` sentinel(유지 vs 해제 구분) (REQ-040). -- `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler` 에 `slot_service`/`watchdog_store` 인자 + `update_thresholds` action 라우팅(`_handle_update_thresholds`) + `last_seen_at` 리셋 (REQ-040). -- `trex_core/runtime/daemon.py` — `WatchdogStore` 인스턴스 + `SafetySellService` 빌드 + `trex-safety-sell` worker(7번째 스레드) + snapshot/intent RPC handler 에 watchdog 배선 + docstring 갱신 (REQ-034/037/038/039/040). -- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` — baseline 정합 (REQ-042). -- `tests/domain/slot/test_price_threshold_fields.py` — 중복 import 정리(ruff clean). - -## Execution Log -1. broker_port + kis + paper 현재가 구현 → `test_kis_price.py` 7 passed. -2. `safety_sell.py` 구현 → `test_safety_sell_worker.py` 23 passed. -3. daemon 배선(watchdog + worker + handler) → import 회귀(test_daemon_no_http_surface) green. -4. `update_thresholds` 구현 → `test_update_thresholds_handler.py` 8 passed. -5. baseline 3문서 정합(REQ-042). -6. 전체 회귀: 단위 236 passed / 23 skipped, `ruff check` All passed. -7. 인프라 게이트 포함(PG+RabbitMQ): 259 passed (마이그레이션 001~004 적용 + PG repo round-trip 포함). -8. **배포 전 실 KIS LIVE 시세 검증(읽기 전용)** → REQ-038 시세 tr_id 결함 발견(`TTTC8397R` = "없는 서비스 코드", rt_cd=1). change-req REQ-038 CHG-04/05 로 `FHKST01010100`(실전/모의 공용) 정정 → `kis.py _tr_price` 수정 + `test_kis_price` AC-7 정정 → LIVE 재검증 `get_current_price('005930')=329000`(rt_cd=0). 단위 236 passed 재확인. - -## Validation -- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 236 passed / 23 skipped. -- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) pytest -q` → 259 passed. -- lint: `ruff check trex_core tests` → All checks passed. - -## Remaining Risks -- R-1 (운영 실증 이월): stop-loss·take-profit·forced_close·장 마감의 **실 KIS 라이브 트리거**는 휴장/운영 환경 제약으로 단위(mock)·정적 검증까지 수행. 차기 거래일 운영 관찰 필요(verification Carried-Over). -- R-5 해소: BAT-010 R-2(실 PostgreSQL `004_add_price_thresholds.sql` round-trip)는 인프라 게이트 259 passed 로 적용 확인(마이그레이션 idempotency + PG repo 테스트 green). diff --git a/docs/batches/bat-011_20260607_safety-sell-triggers/index.md b/docs/batches/bat-011_20260607_safety-sell-triggers/index.md deleted file mode 100644 index 790e77d..0000000 --- a/docs/batches/bat-011_20260607_safety-sell-triggers/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-011: safety-sell 트리거 레이어 (forced_close·KIS polling·장 마감 청산 + update_thresholds RPC + baseline 정합) - -- Status: released -- Profile: standard -- Source Discovery: dcy-007 -- Included REQ: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 -- Excluded REQ: REQ-034, REQ-035, REQ-036, REQ-041 (safety-sell 기반 인프라 — BAT-010에서 delivery 완료) - -## Summary -- dcy-007(core 자율 안전 매도)의 트리거 레이어를 delivery한다. BAT-010이 제공한 기반(WatchdogStore, `stop_loss_price`/`take_profit_price` 도메인 필드, `SlotService.sell()`, safety-sell env vars 5종 파싱) 위에 실제 자동 매도를 발동하는 백그라운드 루프와 임계값 갱신 RPC를 추가한다. 구성: `forced_close_at` 만기 루프(REQ-037), stop-loss·take-profit KIS 현재가 polling + 트리거(REQ-038), 장 마감 강제 청산(REQ-039), `update_thresholds` intent RPC 액션(REQ-040), 그리고 dcy-007 전체(REQ-034~041)의 baseline 문서 정합(REQ-042). - -## Scope -- In Scope: - - 신규 `trex-safety-sell` 데몬 worker 스레드 — 기존 6 스레드에 1개 추가. forced_close_at 체크(REQ-037), stop-loss·take-profit 가격 체크(REQ-038), 장 마감 청산(REQ-039)을 통합 루프로 처리. - - forced_close_at 만기 감지: `holding` + 워치독 만료 + `forced_close_at <= now(KST)` → `SlotService.sell()` (REQ-037) - - KIS 현재가 polling 어댑터(`TTTC8397R` 실전 / `VTTC8397R` 모의, `stck_prpr` 파싱) + 손절/익절 조건 평가 + 쿼터 상한 경고 (REQ-038) - - 장 마감 강제 청산: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 도래 시 워치독 무관 전체 `holding` 청산 + 거래일 1회 발동 가드 (REQ-039) - - `update_thresholds` intent RPC 액션: `stop_loss_price`/`take_profit_price`/`forced_close_at` 부분 갱신 + 워치독 `last_seen_at` 리셋 + holding-only (REQ-040) - - baseline 3문서(`interface-contract.md`, `runtime-flows.md`, `project-structure.md`) 정합 — dcy-007 REQ-034~041 반영 (REQ-042) -- Out of Scope: - - BAT-010이 완료한 기반 인프라(REQ-034 WatchdogStore, REQ-035 도메인 필드/마이그레이션, REQ-036 `SlotService.sell()`, REQ-041 env 파싱) 재구현 — 본 batch는 호출/참조만 한다. - - 가격 임계값 논리 유효성 검증(take_profit > stop_loss 등) — dcy-007/REQ-035 범위 밖. - - KIS 쿼터 초과 시 polling 간격 자동 조정의 동적 튜닝 정책 — 본 batch는 상한 산정 + 경고 로그까지(REQ-038 AC-8). 동적 조정은 후속 후보. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery는 단일(dcy-007). BAT-010과 같은 Discovery의 후속 batch이며, BAT-010 index의 "BAT-011 예정" 분할 계획과 정확히 일치한다(REQ-037/038/039/040/042). -- 본 batch는 BAT-010과 달리 **외부 API(KIS 현재가 polling) 호출과 신규 daemon thread**를 도입하므로 standard profile + design 선행이 필요하다(planning.md Design Gate 참조). -- BAT-010 verification의 잔여 R-2(실 PostgreSQL migration `004_add_price_thresholds.sql` round-trip 미검증)를 본 batch 환경에서 함께 검증한다(verification에 evidence 기록 예정). -- REQ-042(문서 정합)는 REQ-037~040 구현 완료 후 verification 단계에서 최신 구현 상태를 반영해 수행한다(REQ-042 Notes). diff --git a/docs/batches/bat-011_20260607_safety-sell-triggers/planning.md b/docs/batches/bat-011_20260607_safety-sell-triggers/planning.md deleted file mode 100644 index 9a0fa66..0000000 --- a/docs/batches/bat-011_20260607_safety-sell-triggers/planning.md +++ /dev/null @@ -1,54 +0,0 @@ -# BAT-011 Planning - -- Status: confirmed -- Batch: BAT-011 -- Profile: standard - -## Included REQ -- REQ-037 (Integration): `forced_close_at` 만기 자동 매도 루프 — `holding` + 워치독 만료 + `forced_close_at <= now(KST)` 슬롯에 `SlotService.sell()`. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 주기. KIS 호출 없음(DB + in-memory watchdog 비교). -- REQ-038 (Integration): stop-loss·take-profit KIS 현재가 polling + 트리거 — `TTTC8397R`(실전)/`VTTC8397R`(모의) `stck_prpr` 기준, `현재가 <= stop_loss_price`(손절)/`현재가 >= take_profit_price`(익절) 시 `sell()`. `TREX_STOP_LOSS_POLL_INTERVAL` 주기. 쿼터 상한 `N × (3600/interval)` 경고. -- REQ-039 (Integration): 장 마감 강제 청산 — `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST) 도래 시 워치독 무관 전체 `holding` 청산. 거래일 1회 발동 가드(`last_liquidation_date`). -- REQ-040 (Interface): `update_thresholds` intent RPC 액션 — `{action, slot_id, stop_loss_price?, take_profit_price?, forced_close_at?}` 부분 갱신 + 워치독 `last_seen_at` 리셋 + holding-only. `reply_to` 시 `{status: updated|ignored, slot_id}` 응답. -- REQ-042 (Documentation): baseline 3문서 정합 — `interface-contract.md`(update_thresholds + price 필드), `runtime-flows.md`(워치독 갱신·forced_close·가격 체크·장 마감 흐름 + `:114` A-5 주석 갱신), `project-structure.md`(`trex-safety-sell` 스레드 + env 5종 + KIS polling/watchdog 모듈). - -## Delivery Plan -1. REQ-038 KIS 현재가 polling 어댑터: `get_current_price(symbol)` (`TTTC8397R`/`VTTC8397R`, `stck_prpr` 파싱, 실전/모의 분기). paper 환경 stub. 어댑터 단위 테스트. -2. REQ-037 + REQ-038 + REQ-039 통합 worker: `daemon.py`에 `trex-safety-sell` 스레드 + `_safety_sell_loop()` 추가. 루프 1 tick에서 (a) forced_close_at 만기, (b) stop-loss/take-profit 가격 체크, (c) 장 마감 청산 시각 평가를 순차 수행. 각 경로 `SlotService.sell()` 호출. mock 기반 단위 테스트(만기/손절/익절/미충족/워치독 보호/idempotency/장 마감 전체·중복가드). -3. REQ-040 `update_thresholds`: `SlotService.update_thresholds(...)` + `make_intent_rpc_handler` action 라우팅 + `last_seen_at` 리셋 + holding-only 응답. handler 단위 테스트(부분 갱신/null 해제/non-holding ignored/last_seen_at 리셋). -4. REQ-039 config: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 활용 확인(REQ-041에서 파싱 완료 — 본 batch는 소비). -5. REQ-042 baseline 문서 3종 정합 — 구현 완료 상태 반영. -6. 전체 회귀 green + ruff lint clean + BAT-010 잔여 R-2(실 PG 마이그레이션 round-trip) 검증. - -## Dependencies -- Internal: - - BAT-010/REQ-034: `WatchdogStore.is_expired()` / `in_grace_period()` — 트리거 루프의 워치독 만료 판정 계약(read 측). - - BAT-010/REQ-035: `stop_loss_price`/`take_profit_price` 도메인 필드 — 가격 체크 기준값. - - BAT-010/REQ-036: `SlotService.sell(slot_id, actor, reason)` — 모든 트리거의 매도 제출 경로. - - BAT-010/REQ-041: safety-sell env vars 5종 파싱(`DaemonConfig`) — 본 batch가 값을 소비. - - BAT-007/REQ-022: `make_intent_rpc_handler` — `update_thresholds` action 확장 대상. - - BAT-004/REQ-012: `KISBroker`(`kis.py`) — 현재가 polling 어댑터 추가/확장 대상. -- External: - - KIS Open API 현재가 시세 endpoint(`TTTC8397R`/`VTTC8397R`). 실 호출은 운영(`broker=kis`) 환경; 테스트는 mock/stub. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 본 batch는 (a) **신규 daemon worker 스레드(`trex-safety-sell`)**를 추가하고 BAT-010의 WatchdogStore를 새로운 스레드에서 read하므로 동시성 계약(GIL 의존 read vs. snapshot RPC handler write)을 명시해야 한다. (b) **신규 외부 I/O(KIS 현재가 polling)** 어댑터의 배치(`kis.py` 확장 vs. 신규 `kis_price.py`), 실전/모의 endpoint 분기, paper stub 전략, 쿼터 상한 산정을 설계에서 확정해야 한다. (c) forced_close·가격 체크·장 마감을 **단일 통합 루프**로 묶는 구조와 장 마감 1회 발동 가드(`last_liquidation_date`) 상태 모델을 설계에서 고정해야 구현·검증 drift가 없다. → `design.md`를 동일 턴에 scaffold한다. - -## Milestones -1. M1 — REQ-038 KIS 현재가 polling 어댑터 + 단위 테스트 green (실전/모의 분기, `stck_prpr` 파싱) -2. M2 — `trex-safety-sell` worker + forced_close_at 만기 루프(REQ-037) + 단위 테스트 green -3. M3 — stop-loss·take-profit 가격 체크(REQ-038) worker 통합 + mock 단위 테스트 green(손절/익절/미충족/워치독 보호/쿼터 산정) -4. M4 — 장 마감 강제 청산(REQ-039) + 거래일 1회 가드 + 단위 테스트 green -5. M5 — `update_thresholds` RPC(REQ-040) + handler 단위 테스트 green -6. M6 — baseline 3문서 정합(REQ-042) + 전체 회귀 + ruff clean + BAT-010 R-2 검증 - -## Risks -- R-1 (Medium): KIS 현재가 polling 쿼터 — 워치독 만료 슬롯 N개 × (3600/`TREX_STOP_LOSS_POLL_INTERVAL`) 호출이 KIS 분당/일일 쿼터를 초과할 수 있음. 대응: REQ-038 AC-8 상한 산정 + 임계 초과 시 경고 로그. 폴링 대상은 "워치독 만료" 슬롯으로 한정해 평시 호출 최소화. -- R-2 (Medium): WatchdogStore cross-thread read — safety-sell worker가 snapshot RPC handler thread의 write와 동시 접근. 대응: BAT-010 Key Decision 3(GIL 보장 단일 dict, lock 없음) 계약을 그대로 따르고 read-only 판정만 수행. 단위 테스트로 만료 판정 고정. -- R-3 (Medium): 장 마감 청산 발동 정밀도/중복 — worker 루프 주기에 의존하는 시각 평가 + 재시작 시 in-memory `last_liquidation_date` 초기화로 동일 거래일 재발동 위험. 대응: 발동 가드 + (필요 시) 발동 시각 경계 처리 설계에서 확정. -- R-4 (Low): KIS 거부/네트워크 예외 시 루프 안정성 — polling 또는 sell 제출 중 예외가 worker 스레드를 죽이면 안 됨. 대응: tick 단위 try/except + 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합). -- R-5 (Low, 이월): BAT-010 잔여 R-2 — `004_add_price_thresholds.sql` 실 PostgreSQL round-trip 미검증. 대응: 본 batch verification에서 실 PG 적용 + round-trip 확인. - -## Notes -- safety-sell worker는 REQ-037 Notes의 권고(forced_close·stop-loss/take-profit·장 마감을 하나의 통합 루프로 처리)를 따른다. 루프 주기와 가격 체크 주기가 다를 경우(`TREX_FORCED_CLOSE_CHECK_INTERVAL` vs `TREX_STOP_LOSS_POLL_INTERVAL`) tick 내 sub-scheduling 방식은 design에서 확정한다. -- REQ-040 OQ-6(non-holding 슬롯 처리)는 REQ-040 AC-6에서 `{status: ignored, reason: "not_holding"}` 응답으로 이미 결정됨 — 구현은 이를 따른다. diff --git a/docs/batches/bat-011_20260607_safety-sell-triggers/verification.md b/docs/batches/bat-011_20260607_safety-sell-triggers/verification.md deleted file mode 100644 index fa7d7c3..0000000 --- a/docs/batches/bat-011_20260607_safety-sell-triggers/verification.md +++ /dev/null @@ -1,82 +0,0 @@ -# BAT-011 Verification - -- Status: verified -- Batch: BAT-011 - -## Acceptance Mapping - -### REQ-037 (forced_close_at 만기 자동 매도 루프) -| AC | Evidence | -|----|---------| -| AC-1: 만기+워치독 만료 → sell() | `test_safety_sell_worker.py::TestForcedClose::test_expired_past_due_sells` | -| AC-2: 미만기(now+1h) → 미호출 | `test_future_not_due_no_sell` | -| AC-3: 워치독 활성 → 미호출(보호) | `test_watchdog_active_protected` | -| AC-4: forced_close_at None → 제외 | `test_no_forced_close_excluded` | -| AC-5: pending_sell 중복 미호출 | `test_idempotent_on_pending_sell` (transition_log PENDING_SELL 1건) | -| AC-6: interval 주기 | `TestRunOnce::test_base_tick_is_finest_interval` + `safety_sell.base_tick_sec`/`run_once` `_due` 캐던스(코드) | -| grace 보호 | `test_grace_period_defers` | - -### REQ-038 (stop-loss·take-profit KIS polling + 트리거) -| AC | Evidence | -|----|---------| -| AC-1: stck_prpr ≤ stop_loss → 손절 | `TestPriceTriggers::test_stop_loss_triggers` | -| AC-2: stck_prpr ≥ take_profit → 익절 | `test_take_profit_triggers` | -| AC-3: 두 조건 미충족 → 미호출 | `test_neither_condition_no_sell` | -| AC-4: 워치독 활성 → KIS 호출 0 | `test_watchdog_active_no_kis_call` (broker.calls == []) | -| AC-5: 임계값 None → 제외 | `test_no_thresholds_excluded` | -| AC-6: pending_sell 중복 미호출 | `test_idempotent_on_pending_sell` | -| AC-7 (CHG-04): 시세 tr `FHKST01010100`(실전/모의 공용) + stck_prpr | `test_kis_price.py::test_tr_price_is_quotations_tr` + `test_get_current_price_parses_stck_prpr`; **실 KIS LIVE 검증**: `get_current_price('005930')=Decimal('329000')`(rt_cd=0). 구 `TTTC8397R` 는 LIVE rt_cd=1 "없는 서비스 코드" 로 폐기 | -| AC-8: 시간당 호출 ≤ N×(3600/interval) | `test_quota_estimate`(N=3,interval=30→360) + `test_only_eligible_slots_polled`(워치독 만료+임계 슬롯만 poll) | -| 가격 미수신 → skip | `test_price_unavailable_skips` | - -### REQ-039 (장 마감 강제 청산) -| AC | Evidence | -|----|---------| -| AC-1: 시각 도래 → 전체 holding sell() | `TestMarketClose::test_time_reached_liquidates_all` | -| AC-2: 워치독 활성 포함 청산 | `test_watchdog_active_included` | -| AC-3: 미설정 → 미동작 | `test_not_configured_no_sell` | -| AC-4: 거래일 1회 발동 가드 | `test_once_per_day_guard` | -| AC-5: pending_sell 중복 미호출 | REQ-036 idempotency(`SlotService.sell`) — `test_service_sell.py::test_sell_idempotent_on_pending_sell` 승계, 청산 경로 동일 sell() 사용 | -| AC-6: grace period 무관 청산 | `test_grace_period_does_not_block` | -| 발동 시각 이전 → 미동작 | `test_before_time_no_sell` | - -### REQ-040 (update_thresholds intent RPC) -| AC | Evidence | -|----|---------| -| AC-1: stop_loss_price 갱신 | `test_update_thresholds_handler.py::test_updates_stop_loss` | -| AC-2: take_profit + forced_close 갱신 | `test_updates_take_profit_and_forced_close` | -| AC-3: last_seen_at 리셋 | `test_resets_watchdog_last_seen` | -| AC-4: 부분 갱신(미포함 유지) | `test_partial_update_keeps_other_fields` | -| AC-5: null → 해제 | `test_explicit_null_clears` | -| AC-6: non-holding → ignored/not_holding | `test_non_holding_ignored` | -| AC-7: reply_to → updated 응답 | `test_reply_updated_status` | -| 미존재 슬롯 → ignored/slot_not_found | `test_unknown_slot_ignored` | - -### REQ-042 (baseline 문서 정합) -| AC | Evidence | -|----|---------| -| AC-1: interface-contract update_thresholds 계약 | `docs/interface-contract.md` `trex.intent.req update_thresholds 액션` 블록 + Patterns 행 | -| AC-2: interface-contract price 선택 필드 | buy payload 에 `stop_loss_price`/`take_profit_price` 추가 | -| AC-3: runtime-flows 4종 흐름 | `docs/runtime-flows.md` 워치독 갱신/forced_close/가격 체크/장 마감 흐름 4종 추가 | -| AC-4: runtime-flows A-5 주석 갱신 | broker degrade 흐름의 "A-5 미구현" → dcy-007 REQ-037~041 해소로 갱신 | -| AC-5: project-structure trex-safety-sell 스레드 | `docs/project-structure.md` 7 스레드 목록 + safety_sell.py/watchdog.py 모듈 + 변경 로그 | - -## Evidence -- Test: - - 신규 테스트 38종 green (test_kis_price 7 + test_safety_sell_worker 23 + test_update_thresholds_handler 8). - - 단위 전체 회귀: `236 passed / 23 skipped`(`::test_sandbox_token_and_submit` deselect — `.env=LIVE` 산물). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `259 passed`(마이그레이션 001~004 적용 + PG repo round-trip 포함). - - `ruff check trex_core tests` → All checks passed. -- Manual: - - 없음(트리거 로직은 결정적 단위 테스트로 커버; 실 KIS 라이브 트리거는 운영 실증 이월). -- Logs: - - `236 passed, 23 skipped, 1 deselected in ~0.8s` / `259 passed ... in ~11s`. - -## Carried-Over Items -- BAT-010 R-2 (실 PostgreSQL `004_add_price_thresholds.sql` round-trip): **해소** — 인프라 게이트 259 passed(마이그레이션 적용 + PG repo 테스트 green)로 확인. -- 운영 실증 이월(차기 거래일): stop-loss·take-profit·forced_close·장 마감의 실 KIS 라이브 트리거 1건 관찰. 휴장/운영 제약으로 본 batch 는 mock·정적 검증까지. release feedback 으로 추적. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 잔여(실 KIS 라이브 트리거 운영 실증)는 blocker 아님 — paper/mock 결정적 검증 + KIS 어댑터 tr_id/파싱 단위 검증으로 release gate 충족. 차기 거래일 운영 관찰로 보강(REL feedback). diff --git a/docs/batches/bat-012_20260607_kill-switch-control/design.md b/docs/batches/bat-012_20260607_kill-switch-control/design.md deleted file mode 100644 index 195304e..0000000 --- a/docs/batches/bat-012_20260607_kill-switch-control/design.md +++ /dev/null @@ -1,56 +0,0 @@ -# BAT-012 Design - -- Status: confirmed -- Batch: BAT-012 - -## Architecture Summary -BAT-012 는 운영자 비상 정지(kill-switch)를 기존 인프라 위에 추가한다. - -1. **제어 채널 일원화** (`trex.control.req`): 기존 `trex.pause.req` 단일 목적 큐를 `action` discriminator 기반 제어 채널로 확장. `make_control_rpc_handler` 가 pause/resume/list(REQ-032 위임) + kill_switch(REQ-045) + resume_kill_switch(REQ-046)를 분기. daemon 의 pause consumer 스레드를 control consumer 로 교체(스레드 수 7 불변). -2. **day-scoped 제외 store** (`killswitch_exclusion` + `PostgreSQLKillSwitchStore`): `(portfolio_id, trading_date)` 키. `snapshot_pause`(영구 pause)와 분리. `list_excluded(today)` 가 당일 항목만 반환 → day-scoped 자동 복귀. -3. **kill_switch 실행**: 범위별 holding 조회 → 순차 `SlotService.sell()` → 당일 제외 표시 → 이력 로깅. dcy-007 safety-sell 과 독립(REQ-036 idempotency 로 충돌 무해). -4. **masking 합성 + resume**: snapshot envelope masking 에 `killswitch_today` 추가. `resume_kill_switch` 가 day-scoped 제외 row 삭제(청산된 슬롯은 비가역). - -## Changed Areas -- Docs: - - `docs/interface-contract.md` — 제어 채널 `trex.control.req` + action 목록 + kill_switch/resume_kill_switch payload/응답 + masking 소스 (REQ-047) - - `docs/runtime-flows.md` — kill-switch 발동/masking/자동복귀/resume 흐름 (REQ-047) - - `docs/project-structure.md` — control consumer + killswitch_exclusion 테이블/store + 005 sql (REQ-047) -- Modules: - - `trex_core/adapters/messaging/topology.py` — `CONTROL_REQ_QUEUE = "trex.control.req"` (PAUSE_REQ_QUEUE 대체/별칭) - - `trex_core/application/messaging_handlers.py` — `make_control_rpc_handler`(action dispatch) + kill_switch / resume_kill_switch handler - - `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (신규) - - `trex_core/adapters/portfolio/killswitch_store.py` — `PostgreSQLKillSwitchStore` - - `trex_core/application/portfolio.py`(envelope builder) — masking 소스에 `list_excluded(today)` 추가 - - `trex_core/application/killswitch.py`(신규, 후보) 또는 `domain/slot/service.py` — 범위별 holding 순차 sell() 진입점 - - `trex_core/runtime/daemon.py` — pause consumer → control consumer 교체 + killswitch_store 배선 + snapshot/control handler 주입 -- Interfaces: - - `trex.control.req` action 계약: `pause|resume|list|kill_switch|resume_kill_switch` - - kill_switch payload `{action, scope: portfolio|global, portfolio_id?, actor?}` → `{status, scope, portfolio_id?, liquidated}` - - resume_kill_switch payload `{action, scope, portfolio_id?, actor?}` → `{status, scope, portfolio_id?}` - - `KillSwitchStore.exclude/clear/list_excluded(trading_date)` - -## Key Decisions (초안 — 구현 전 확정 대상) -1. **제어 큐 전환 방식**: `trex.control.req` 단일 채널로 일원화. 기존 `trex.pause.req` 하위호환은 (a) 별칭 큐 동시 선언 유지 vs (b) control.req 단일화 + 클라이언트 갱신 중 택1 — release rollout 영향이라 확정 필요(R-1). 기본안: control.req 단일화(RPC pause 사용처는 전략/운영 한정, CLI 무영향). -2. **전역 제외 표현**: `killswitch_exclusion.portfolio_id = '*'` sentinel(기본안) vs scope 컬럼 분리. masking 소비처(REQ-046)와 계약만 일치하면 됨. 기본안: `'*'` sentinel + `list_excluded` 가 sentinel 포함 시 전체 마스킹 신호로 반환. -3. **kill_switch 실행 진입점**: 신규 `application/killswitch.py`(범위별 holding 조회 + 순차 sell() + store exclude + 로깅)로 분리(기본안). domain service 는 단일 슬롯 책임 유지, 복수 슬롯 오케스트레이션은 application 계층. -4. **trading_date 기준**: 데몬 기존 관례(UTC date) 사용 — REQ-045 표시·REQ-046 조회·자동복귀 모두 동일 키. (safety-sell `_trading_date(now)` 와 일치) -5. **dcy-007 독립성**: kill_switch 는 safety-sell worker 스레드와 상태/락 공유 없음. 동일 슬롯 동시 sell() 은 REQ-036 PENDING_SELL idempotency 로 1회만 전이. - -## Edge Cases -- kill_switch scope=portfolio + portfolio_id 누락 → `{status:error, reason:portfolio_id_required}`. -- 대상 중 이미 pending_sell → sell() 건너뜀(idempotent), liquidated 카운트에서 제외 여부는 구현 결정(기본: 신규 전이만 카운트). -- resume_kill_switch 후 이미 청산된 슬롯 → 상태 불변(매도 비가역). resume 은 제외 row 만 삭제. -- 다음 거래일 → `list_excluded(D+1)` 에 전일 row 미포함 → 자동 복귀(별도 동작 불필요). -- 데몬 재시작(같은 거래일) → store 영속 → masking 유지(NFR-3). -- unknown action → `{status:error, reason:unknown_action:}`. - -## Architecture Impact -- runtime-flows: 제어 채널 + kill-switch 4흐름 추가 — REQ-047 에서 문서화. -- project-structure: control consumer 전환 + 신규 store/테이블/sql — REQ-047 에서 문서화. -- interface-contract: pause→control 채널 계약 갱신 — REQ-047. - -## Reference Doc Update Plan -- `interface-contract.md`: 제어 채널 action 목록 + kill_switch/resume_kill_switch 계약 + masking 소스 (REQ-047 AC-1/2) -- `runtime-flows.md`: 발동/masking/자동복귀/resume 흐름 (REQ-047 AC-3) -- `project-structure.md`: control consumer + killswitch_exclusion/store + 005 sql (REQ-047 AC-4) diff --git a/docs/batches/bat-012_20260607_kill-switch-control/implementation.md b/docs/batches/bat-012_20260607_kill-switch-control/implementation.md deleted file mode 100644 index 4ab7a4d..0000000 --- a/docs/batches/bat-012_20260607_kill-switch-control/implementation.md +++ /dev/null @@ -1,51 +0,0 @@ -# BAT-012 Implementation - -- Status: completed -- Batch: BAT-012 - -## Plan Summary -- 구현 순서(planning Delivery Plan): REQ-044 store/마이그레이션 → REQ-043 제어 채널/dispatch → REQ-045 kill_switch 실행 → REQ-046 masking/resume → REQ-047 baseline 정합. 각 단계 unit test green 후 진행. - -## Changed Files -- (구현 시 기록) - -### 신규 파일 (예정) -- `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (REQ-044) -- `trex_core/adapters/portfolio/killswitch_store.py` — `PostgreSQLKillSwitchStore` (REQ-044) -- `trex_core/application/killswitch.py` — 범위별 holding 순차 sell() 진입점 (REQ-045) -- `tests/adapters/portfolio/test_killswitch_store.py` (REQ-044) -- `tests/application/test_control_dispatch.py` (REQ-043) -- `tests/application/test_killswitch_execution.py` (REQ-045) -- `tests/application/test_killswitch_masking_resume.py` (REQ-046) - -### 기존 파일 수정 (예정) -- `trex_core/adapters/messaging/topology.py` — CONTROL_REQ_QUEUE (REQ-043) -- `trex_core/application/messaging_handlers.py` — make_control_rpc_handler + kill_switch/resume_kill_switch (REQ-043/045/046) -- `trex_core/application/portfolio.py` — masking 소스 확장 (REQ-046) -- `trex_core/runtime/daemon.py` — control consumer 전환 + killswitch_store 배선 (REQ-043/045/046) -- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` (REQ-047) - -## Changed Files (실제) -- 신규: `sql/005_create_killswitch_exclusion.sql`, `adapters/portfolio/killswitch_store.py`(PostgreSQLKillSwitchStore), `application/killswitch.py`(KillSwitchService), 테스트 4개(test_killswitch_store / test_control_dispatch / test_killswitch_execution / test_killswitch_masking_resume). -- 수정: `adapters/messaging/topology.py`(CONTROL_REQ_QUEUE), `adapters/messaging/rabbitmq.py`(control 큐 declare), `application/portfolio.py`(GLOBAL_KILLSWITCH + KillSwitchStore Protocol + InMemoryKillSwitchStore + envelope masking), `application/messaging_handlers.py`(make_control_rpc_handler + kill_switch/resume_kill_switch + snapshot handler killswitch_store), `runtime/daemon.py`(control consumer 전환 + KillSwitchService + killswitch_store 배선), `tests/conftest.py`(killswitch_exclusion truncate), baseline 3문서(REQ-047). - -## Execution Log -1. REQ-044 store + 005 마이그레이션 → 단위 5 passed(PG 5 gated). -2. REQ-045 KillSwitchService → 단위 6 passed. -3. REQ-043 control dispatch(topology+handler+daemon) → 단위 9 passed, import/회귀 green. -4. REQ-046 masking + resume → 단위 6 passed. -5. REQ-047 baseline 3문서 정합. -6. 전체 회귀: 단위 262 passed / 28 skipped, ruff clean. -7. 인프라 게이트(PG+RabbitMQ): 290 passed(마이그레이션 001~005 + killswitch PG round-trip 포함). -8. paper 데몬 e2e(control RPC): buy 2 → `kill_switch global`(liquidated=2) → 양쪽 `holding→pending_sell(emergency_stop)→closed`, `killswitch_exclusion=[('*',2026-06-07)]` → `resume_kill_switch global` → exclusion 0. PASS. - -## Validation -- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 262 passed / 28 skipped. -- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) pytest -q` → 290 passed. -- lint: `ruff check trex_core tests` → All passed. -- e2e: 위 8 (paper 데몬 control RPC kill_switch/resume). - -## Remaining Risks -- R-1(제어 큐 rename 하위호환): control.req 단일화 구현. 기존 `trex.pause.req` 사용 RPC 클라이언트 이전은 release rollout 결정(REQ-043 Notes). CLI 무영향. -- R-5 해소: `005` 실 PG round-trip = 인프라 게이트 290 passed(killswitch PG 테스트 green). -- (이월) 실 KIS LIVE kill_switch 발동→실매도 제출→에러 관찰은 차기 거래일 운영 실증(safety-sell LIVE 선례). paper/mock 으로 충분 검증. diff --git a/docs/batches/bat-012_20260607_kill-switch-control/index.md b/docs/batches/bat-012_20260607_kill-switch-control/index.md deleted file mode 100644 index b2d25ce..0000000 --- a/docs/batches/bat-012_20260607_kill-switch-control/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-012: 비상 정지/kill-switch — 제어 채널 + 범위별 청산 + 당일 제외 masking/resume - -- Status: released -- Profile: standard -- Source Discovery: dcy-008 -- Included REQ: REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 -- Excluded REQ: REQ-030 (BAT-008 released 포함, 옵션 D 는 portfolio-hub#8 후 별도 change-req) - -## Summary -- dcy-008(비상 정지/강제 청산 kill-switch) 전체를 단일 standard batch 로 delivery 한다. 운영자가 `trex.control.req` 제어 채널로 portfolio/전역 단위 holding 슬롯을 즉시 청산하고 당일 전략 실행에서 제외하며(다음 거래일 자동 복귀 + 수동 resume), 기존 `SlotService.sell()`(REQ-036)·outbox·execution 인프라를 재사용한다. 구성: 제어 채널 + action dispatch(REQ-043), day-scoped 제외 store + 마이그레이션(REQ-044), kill_switch 실행(REQ-045), 당일 제외 masking + 자동복귀 + resume_kill_switch(REQ-046), baseline 3문서 정합(REQ-047). - -## Scope -- In Scope: - - `trex.pause.req` → `trex.control.req` 제어 채널 확장 + `action` dispatch(pause/resume/list 유지 + kill_switch/resume_kill_switch 추가) (REQ-043) - - 신규 `killswitch_exclusion` 테이블 + `005_*.sql` additive 마이그레이션 + 전용 store 어댑터 (REQ-044) - - kill_switch 실행: 범위별(portfolio/전역) holding 순차 `SlotService.sell()` + REQ-036 idempotency + 발동 이력 로깅 (REQ-045) - - 당일 제외 masking(snapshot envelope `mask = pending|warning|pause|killswitch_today`) + 다음 거래일 자동 복귀 + `resume_kill_switch` 수동 해제 (REQ-046) - - baseline 3문서(interface-contract/runtime-flows/project-structure) 정합 (REQ-047) -- Out of Scope: - - pending_buy 슬롯 롤백 (dcy-008 OQ-3 Deferred — 기본 구현 HOLDING 한정) - - 부분 수량 청산 (GitHub #5 별도 Discovery) - - kill-switch 발동 후 자율 안전 매도(dcy-007/REL-003) 비활성화 — 두 경로 독립 - - 제어 큐 rename 의 기존 클라이언트 호환 정책 확정 — release 단계 결정(REQ-043 Notes) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-008). OQ-1(제어 채널)·OQ-2(별도 테이블)·OQ-4(수동 resume) Answered, OQ-3(pending_buy) REQ Deferred. -- NFR-1(청산 지연)·NFR-2(dcy-007 독립)는 REQ-045 AC-7/8, NFR-3(영속 masking)은 REQ-046 AC-5 로 흡수(별도 NFR REQ 없음). -- 기존 자산 재사용: `SlotService.sell()`(REQ-036/BAT-010), outbox·execution worker, pause masking 패턴(`portfolio.py`). 호출 진입점·제어 채널·day-scoped store 만 신규. -- REQ-047(문서 정합)은 REQ-043~046 구현 완료 후 verification 단계 수행(REQ-042 선례). diff --git a/docs/batches/bat-012_20260607_kill-switch-control/planning.md b/docs/batches/bat-012_20260607_kill-switch-control/planning.md deleted file mode 100644 index 497e5cb..0000000 --- a/docs/batches/bat-012_20260607_kill-switch-control/planning.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-012 Planning - -- Status: confirmed -- Batch: BAT-012 -- Profile: standard - -## Included REQ -- REQ-043 (Interface): `trex.pause.req` → `trex.control.req` 제어 채널 + `action` dispatch. pause/resume/list 회귀 보존 + kill_switch/resume_kill_switch 라우팅. unknown action error. 워커 스레드 수 불변(pause→control 교체). -- REQ-044 (Data): `killswitch_exclusion(portfolio_id, trading_date, actor, created_at)` 테이블 + `005_*.sql` additive 마이그레이션 + `PostgreSQLKillSwitchStore`(exclude/clear/list_excluded(day)). 전역 sentinel. -- REQ-045 (Interface): kill_switch handler — payload `{action, scope, portfolio_id?}` → 범위별 holding 순차 `SlotService.sell(actor="kill-switch", reason="emergency_stop")` + REQ-036 idempotency + 당일 제외 표시(044) + 발동 이력 로깅 + 응답 `{liquidated:N}`. -- REQ-046 (Interface): snapshot masking 소스에 `list_excluded(today)` 추가(portfolio/전역) + day-scoped 자동 복귀 + `resume_kill_switch` handler(`clear`) — 청산된 슬롯은 비가역. -- REQ-047 (Documentation): interface-contract(제어 채널+action+payload), runtime-flows(발동/masking/복귀/resume 흐름), project-structure(control consumer + killswitch_exclusion + store + 005 sql) 정합. - -## Delivery Plan -1. REQ-044: 마이그레이션 `005_create_killswitch_exclusion.sql` + `PostgreSQLKillSwitchStore` + 단위/PG 게이트 테스트(day-scoped/멱등/전역 sentinel). -2. REQ-043: `topology.CONTROL_REQ_QUEUE` + `make_control_rpc_handler`(action dispatch, pause/resume/list 위임 + kill_switch/resume_kill_switch 라우팅) + `daemon.py` pause consumer→control consumer 교체. dispatch 단위 테스트(회귀 + 라우팅 + unknown). -3. REQ-045: kill_switch handler + 범위별 holding 조회·순차 sell() 진입점(application service 또는 service 헬퍼) + 044 store exclude 호출 + 이력 로깅. 단위 테스트(전역/portfolio/idempotency/portfolio_id 누락/이력/독립성/지연). -4. REQ-046: snapshot envelope masking 확장(`killswitch_today`) + resume_kill_switch handler(clear) + day-scoped 자동복귀. 단위 테스트(masking/추가매수거부/자동복귀(날짜 mock)/재시작 영속/resume). -5. REQ-047: baseline 3문서 정합(구현 완료 상태 반영). -6. 전체 회귀 + ruff + 인프라 게이트(PG: 005 round-trip, RabbitMQ: control RPC) + e2e(발동→청산→masking→resume). - -## Dependencies -- Internal: - - REQ-036/BAT-010 `SlotService.sell()` — kill_switch 청산 경로(재사용). - - REQ-032/BAT-008 `PostgreSQLPauseStore` + pause masking 패턴(`portfolio.py`) — masking 합성 참조(분리 유지). - - REQ-022/BAT-007 `make_*_rpc_handler` + consume_rpc_forever — control consumer 전환 대상. - - BAT-008 snapshot envelope builder — masking 소스 확장 지점. -- External: - - 없음 (kill_switch 청산은 기존 broker 경로 사용; 신규 외부 API 없음). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) **제어 채널 전환**(`trex.pause.req`→`trex.control.req`) 은 인터페이스·daemon consumer 구조 변경이며 기존 pause 클라이언트 하위호환(별칭 vs 단일화) 결정이 design 에서 필요하다. (b) **day-scoped store 의 전역 sentinel 표현**과 masking 합성 계약(REQ-044↔046) 을 설계에서 고정해야 구현 drift 가 없다. (c) **kill_switch 실행 진입점 배치**(domain service vs application service)와 발동 이력/순차 처리 모델, dcy-007 safety-sell 과의 독립성 보장을 설계에서 명시해야 한다. → `design.md` 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-044 store + 마이그레이션 + 단위/PG 테스트 green -2. M2 — REQ-043 control 채널 + dispatch + pause/resume/list 회귀 green -3. M3 — REQ-045 kill_switch 실행 + idempotency + 이력 단위 green -4. M4 — REQ-046 masking + 자동복귀 + resume 단위 green -5. M5 — REQ-047 baseline 3문서 정합 -6. M6 — 전체 회귀 + ruff + 인프라 게이트 + e2e(발동→청산→masking→resume) - -## Risks -- R-1 (Medium): 제어 큐 rename(`pause.req`→`control.req`) 하위호환 — 기존 RPC pause 클라이언트 영향. 대응: design 에서 별칭 유지 vs 단일화 결정, release rollout 에 명시. CLI pause/resume 는 store 직접 호출이라 무영향. -- R-2 (Medium): kill_switch 다수 슬롯 순차 sell() 큐 부하(dcy-008 R-1). 대응: sell()=PENDING_SELL 전이(DB write 1회) 빠름, N<10 가정(NFR-1). -- R-3 (Low): day-scoped 전역 sentinel 표현 불일치(044 store ↔ 046 masking). 대응: design 에서 계약 고정 + 단위 테스트. -- R-4 (Low): kill_switch ↔ 자율 안전 매도(dcy-007) 동시 sell() 중복. 대응: REQ-036 idempotency(PENDING_SELL 중복 무시). -- R-5 (Low): `005_*.sql` 실 PG round-trip(BAT-010 R-2 선례). 대응: verification 인프라 게이트에서 확인. - -## Notes -- pending_buy 슬롯 처리(OQ-3)는 본 batch 범위 밖(HOLDING 한정). 필요 시 후속 REQ/change-req. -- 제어 큐 호환 최종 결정은 release(REL) 단계 rollout 에서 확정(REQ-043 Notes). diff --git a/docs/batches/bat-012_20260607_kill-switch-control/verification.md b/docs/batches/bat-012_20260607_kill-switch-control/verification.md deleted file mode 100644 index 0f385fa..0000000 --- a/docs/batches/bat-012_20260607_kill-switch-control/verification.md +++ /dev/null @@ -1,80 +0,0 @@ -# BAT-012 Verification - -- Status: verified -- Batch: BAT-012 - -## Acceptance Mapping -> 구현 완료. evidence: 신규 단위 26 + PG 게이트(killswitch store 5) + e2e. 파일: test_control_dispatch.py(9), test_killswitch_execution.py(6), test_killswitch_masking_resume.py(6+1), test_killswitch_store.py(in-mem 5 + PG 5). - -### REQ-043 (제어 채널 + action dispatch) -| AC | Evidence (예정) | -|----|---------| -| AC-1: pause 회귀 | control dispatch 단위(pause) | -| AC-2: resume/list 회귀 | 동일 | -| AC-3: kill_switch 라우팅 | 동일 | -| AC-4: resume_kill_switch 라우팅 | 동일 | -| AC-5: unknown action error | 동일 | -| AC-6: 워커 스레드 7 불변 | daemon 구조 검사/test_daemon | - -### REQ-044 (day-scoped 제외 store + 마이그레이션) -| AC | Evidence (예정) | -|----|---------| -| AC-1: 테이블 스키마 | 마이그레이션 + PG 게이트 | -| AC-2: 마이그레이션 멱등 | 005 IF NOT EXISTS + 재적용 | -| AC-3: exclude→list_excluded | store 단위 | -| AC-4: day-scoped 자동 제외 | list_excluded(D+1) | -| AC-5: 멱등 exclude | store 단위 | -| AC-6: clear | store 단위 | -| AC-7: 전역 sentinel | store 단위 | - -### REQ-045 (kill_switch 실행) -| AC | Evidence (예정) | -|----|---------| -| AC-1: 전역 전체 청산 | killswitch 실행 단위 | -| AC-2: portfolio 범위 한정 | 동일 | -| AC-3: pending_sell idempotency | 동일 | -| AC-4: portfolio_id 누락 error | 동일 | -| AC-5: 당일 제외 표시 | 044 store 연동 | -| AC-6: 이력 로깅 | 로그 캡처 | -| AC-7: dcy-007 독립 | 동일 | -| AC-8: 지연 N×100ms+5s | 측정/코드 검사 | - -### REQ-046 (masking + 자동복귀 + resume) -| AC | Evidence (예정) | -|----|---------| -| AC-1: portfolio masking | masking 단위 | -| AC-2: 전역 masking | 동일 | -| AC-3: 추가매수 거부 | buyable masking | -| AC-4: 다음 거래일 자동복귀 | 날짜 mock | -| AC-5: 재시작 영속 masking | 통합/날짜 mock | -| AC-6: resume_kill_switch | resume 단위 | -| AC-7: portfolio_id 누락 error | 동일 | -| AC-8: 청산 슬롯 비가역 | 상태 불변 확인 | - -### REQ-047 (baseline 정합) -| AC | Evidence (예정) | -|----|---------| -| AC-1: interface-contract 제어 채널+action | 문서 diff | -| AC-2: interface-contract kill/resume payload | 문서 diff | -| AC-3: runtime-flows 4흐름 | 문서 diff | -| AC-4: project-structure control consumer + store/sql | 문서 diff | - -## Evidence -- Test: - - 신규 단위 26 + PG 게이트 killswitch store 5 green. (control dispatch 9 / 실행 6 / masking·resume 7 / store in-mem 5 / store PG 5) - - 단위 전체 회귀: `262 passed / 28 skipped`(`::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `290 passed`(마이그레이션 001~005 적용 + killswitch PG round-trip 포함). - - `ruff check trex_core tests` → All passed. -- Manual / e2e: - - paper 데몬 control RPC e2e: buy 2(pf-A/pf-B) → `{action:kill_switch, scope:global}` → `{status:ok, liquidated:2}` → 양쪽 `holding→pending_sell(emergency_stop)→closed`, `killswitch_exclusion=[('*',2026-06-07)]` → `{action:resume_kill_switch, scope:global}` → exclusion 0. PASS. -- Logs: - - `262 passed, 28 skipped` / `290 passed ... ~13s` / e2e `RESULT: PASS`. - -## Carried-Over / e2e -- BAT-010 R-2 선례(실 PG 마이그레이션 round-trip): `005` 인프라 게이트 290 passed 로 확인. -- (이월, 비차단) 실 KIS LIVE kill_switch 발동 → 실매도 제출 → 상황별 에러(주말/휴장) 관찰 — safety-sell LIVE e2e 선례와 동일 접근, 차기 거래일 운영 실증. paper/mock + 290 passed 로 release gate 충족. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 제어 큐 rename 하위호환(R-1)은 release rollout 결정 항목(REQ-043 Notes), 실 KIS 라이브 발동은 운영 실증 이월 — 둘 다 blocker 아님. diff --git a/docs/batches/bat-013_20260607_multi-instance-topology/design.md b/docs/batches/bat-013_20260607_multi-instance-topology/design.md deleted file mode 100644 index e210b6a..0000000 --- a/docs/batches/bat-013_20260607_multi-instance-topology/design.md +++ /dev/null @@ -1,51 +0,0 @@ -# BAT-013 Design - -- Status: confirmed -- Batch: BAT-013 - -## Architecture Summary -다중 인스턴스가 동일 RabbitMQ 에서 split-brain 없이 운영되도록 토폴로지·설정을 인스턴스화한다. - -1. **동적 큐명**(`topology.py`): `intent_queue/snapshot_queue/control_queue(instance_key)`. `instance_key in (None,"default")` → 기존 리터럴(`trex.intent.req` 등), 그 외 → `{base}.{instance_key}`. 기존 상수는 default 값으로 보존(하위호환). -2. **exchange**(`topology.py`+`rabbitmq.py`): `trex.direct`(topic,durable) + `trex.broadcast`(fanout,durable). `declare_topology(instance_key)` 가 인스턴스 큐를 direct 에 `{instance_key}.{intent|control|snapshot}` 로 바인딩 + control 큐를 broadcast 에 dual 바인딩. -3. **설정**(`config.py`): `DaemonInstanceConfig(instance_key, portfolio_hub_token)` + `from_toml(path)`(tomllib) + `DaemonConfig.from_config(toml_path)` 가 `from_env()` 결과에 TOML 을 병합(`portfolio_hub_token` → PortfolioHubConfig.token). -4. **진입점**(`daemon.main`): argparse `--config` 필수. instance_key 를 declare/consume 에 일관 전달. - -## Changed Areas -- Docs: interface-contract.md(routing key)·runtime-flows.md(TOML 기동/라우팅)·project-structure.md(동적 큐명/설정/exchange) — REQ-051. -- Modules: - - `trex_core/adapters/messaging/topology.py` — 큐명 빌더 + DIRECT/BROADCAST exchange 상수 + `direct_routing_key` - - `trex_core/adapters/messaging/rabbitmq.py` — `declare_topology(instance_key="default")` + exchange 선언 + binding(direct per-instance + control dual) + (필요시) broadcast publish helper - - `trex_core/runtime/config.py` — `DaemonInstanceConfig` + `from_toml` + `from_config` - - `trex_core/runtime/daemon.py` — `--config` argparse(main) + instance_key 보관 + consumer 큐명에 instance_key 적용 + declare_topology(instance_key) - - `.gitignore` — 인스턴스 TOML 패턴 -- Interfaces: - - `intent_queue(instance_key) -> str` 등 (default 하위호환) - - `declare_topology(instance_key="default")` - - `direct_routing_key(instance_key, channel_type) -> "{instance_key}.{channel_type}"` - - `DaemonInstanceConfig.from_toml(path)`, `DaemonConfig.from_config(toml_path)` - -## Key Decisions -1. **default = 리터럴 큐명(특수 처리)**: FR-2(suffix)와 FR-5(default 무회귀)의 긴장을 해소 — named instance 만 suffix, default 는 기존 이름 유지. 기존 messaging 테스트/배포 무변경. (planning R-1) -2. **declare_topology 기본 인자 `instance_key="default"`**: 기존 호출부(`transport.declare_topology()`)가 인자 없이도 default 동작 → 호출부 광범위 수정 회피. -3. **exchange 는 항상 선언(default 포함)**: direct/broadcast 는 default 인스턴스에서도 선언·바인딩하되, 기존 직접 큐 reply-to RPC 경로는 그대로 동작(추가 바인딩은 비파괴적). 기존 RPC 회귀 없음(FR-5/AC-6). -4. **TOML 우선 병합**: `from_config` 는 `from_env()` 로 베이스 구성 후 TOML 의 `portfolio_hub_token` 을 PortfolioHubConfig.token 으로 override. `instance_key` 는 TOML 전용. -5. **`--config` 필수**: argparse required. 누락 시 argparse 가 usage 출력 + exit≠0(OQ-2). 단, `from_env()` 단독 호출(테스트/라이브러리)은 계속 가능. -6. **pika per-thread**: 바인딩 추가만, consumer 스레드/커넥션 수 불변(NFR-2). - -## Edge Cases -- default 큐명 충돌 없음: `*.default` 를 쓰지 않고 리터럴 사용. -- TOML 누락 필드/파일 부재 → 명확한 예외(FR-1/AC-4). -- broadcast 중복 control 전달 → 핸들러 idempotency(REQ-036/advisory lock, R-3). -- 기존 old 큐 잔류 → 자연 소멸(OQ-1), 코드 무관. -- snapshot/intent reply-to RPC: 클라이언트가 direct routing key 로 보내든 직접 큐로 보내든, 인스턴스 큐가 direct 바인딩 + 직접 publish 둘 다 수신 가능(하위호환). - -## Architecture Impact -- runtime-flows: TOML 기동 + directed/broadcast 라우팅 + split-brain 방지 — REQ-051. -- project-structure: topology 동적화 + 설정 계층 + exchange — REQ-051. -- interface-contract: routing key 계약 — REQ-051. - -## Reference Doc Update Plan -- interface-contract.md: routing key(`{instance_key}.{channel_type}`)+broadcast+큐명 규칙 (REQ-051 AC-1) -- runtime-flows.md: `--config` 기동 + directed/broadcast + split-brain (REQ-051 AC-2) -- project-structure.md: 동적 큐명/DaemonInstanceConfig/`--config`/exchange (REQ-051 AC-3) diff --git a/docs/batches/bat-013_20260607_multi-instance-topology/implementation.md b/docs/batches/bat-013_20260607_multi-instance-topology/implementation.md deleted file mode 100644 index a4c1b1e..0000000 --- a/docs/batches/bat-013_20260607_multi-instance-topology/implementation.md +++ /dev/null @@ -1,45 +0,0 @@ -# BAT-013 Implementation - -- Status: completed -- Batch: BAT-013 - -## Plan Summary -- 순서: REQ-049 동적 큐명/declare_topology → REQ-050 exchange/binding/routing → REQ-048 TOML 로더/--config → REQ-051 baseline. 각 단계 단위 green 후 진행. default 하위호환 회귀 우선. - -## Changed Files -- (구현 시 기록) - -### 신규 (예정) -- `tests/runtime/test_instance_config.py` (REQ-048) -- `tests/adapters/messaging/test_instance_topology.py` (REQ-049/050) -- 인스턴스 TOML 예시(`config/instance.example.toml`, 추적) (REQ-048) - -### 수정 (예정) -- `trex_core/adapters/messaging/topology.py` (동적 큐명 + DIRECT/BROADCAST + routing key) -- `trex_core/adapters/messaging/rabbitmq.py` (declare_topology(instance_key) + exchange/binding) -- `trex_core/runtime/config.py` (DaemonInstanceConfig + from_toml + from_config) -- `trex_core/runtime/daemon.py` (--config argparse + instance_key 큐 적용) -- `.gitignore` (인스턴스 TOML) -- baseline 3문서 (REQ-051) - -## Changed Files (실제) -- 신규: `config/instance.example.toml`(추적), `tests/runtime/test_instance_config.py`, `tests/adapters/messaging/test_instance_topology.py`. -- 수정: `topology.py`(동적 큐명 빌더 + DEFAULT_INSTANCE_KEY/DIRECT_EXCHANGE/BROADCAST_EXCHANGE + direct_routing_key), `rabbitmq.py`(declare_topology(instance_key) + exchange/binding/control dual + import 정리), `config.py`(DaemonInstanceConfig+from_toml+from_config, DaemonConfig.instance_key), `daemon.py`(--config argparse main + per-instance 큐 구독 + declare_topology(instance_key)), `.gitignore`(인스턴스 TOML), baseline 3문서(REQ-051). - -## Execution Log -1. REQ-049 동적 큐명(default 리터럴) + declare_topology(instance_key) + daemon per-instance consume → 빌더 단위 + messaging 회귀 green. -2. REQ-050 trex.direct/trex.broadcast exchange + binding + routing key → 인프라 게이트(directed 격리 + broadcast dual) green. -3. REQ-048 DaemonInstanceConfig.from_toml + from_config 병합 + --config argparse + .gitignore + example TOML → 로더 단위 + `--config` 누락 subprocess 종료 green. -4. REQ-051 baseline 3문서 정합. -5. 전체 회귀: 단위 271 passed / 30 skipped, ruff clean. -6. 인프라 게이트(PG+RabbitMQ): 301 passed (directed 라우팅 격리 S-2 + broadcast dual S-3 + default 무회귀 S-4 포함). - -## Validation -- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 271 passed / 30 skipped. -- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=... pytest -q` → 301 passed. -- lint: `ruff check trex_core tests` → All passed. - -## Remaining Risks -- R-1(declare_topology 변경 회귀): default=리터럴 큐명 하위호환으로 기존 messaging 테스트 무변경 — 301 passed 로 확인(FR-5/NFR-1). -- R-2(old 큐 잔류): 운영 자연 소멸(OQ-1), 코드 무관. -- (이월, 비차단) 실 2-인스턴스 동시 기동 manual e2e: directed 격리·broadcast 는 인프라 게이트 통합 테스트로 검증됨. 실 배포 시 다중 인스턴스 운영 시점 확인. diff --git a/docs/batches/bat-013_20260607_multi-instance-topology/index.md b/docs/batches/bat-013_20260607_multi-instance-topology/index.md deleted file mode 100644 index c5b367a..0000000 --- a/docs/batches/bat-013_20260607_multi-instance-topology/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-013: 다중 인스턴스 RabbitMQ 토폴로지 — TOML config + instance_key 큐 + direct/broadcast exchange - -- Status: released -- Profile: standard -- Source Discovery: dcy-009 -- Included REQ: REQ-048, REQ-049, REQ-050, REQ-051 - -## Summary -- dcy-009(다중 인스턴스 RabbitMQ 토폴로지)를 단일 standard batch 로 delivery 한다. per-instance TOML 설정 로더 + `--config` 필수(REQ-048), `instance_key` 기반 동적 큐명 + default 하위호환(REQ-049), `trex.direct`(topic)/`trex.broadcast`(fanout) exchange + 바인딩(REQ-050), baseline 3문서 정합(REQ-051). 다중 인스턴스를 동일 RabbitMQ 에 붙여도 인스턴스 전용 큐로 split-brain(WatchdogStore/SafetySellService 경쟁 소비)을 구조적으로 방지한다. - -## Scope -- In Scope: - - `DaemonInstanceConfig.from_toml` + `--config` 필수 진입점 + env 병합 + `.gitignore` TOML (REQ-048) - - `topology.py` 동적 큐명 빌더(`intent_queue/snapshot_queue/control_queue(instance_key)`, default=리터럴 하위호환) + `declare_topology(instance_key)` + daemon per-instance consume (REQ-049) - - `trex.direct`(topic) + `trex.broadcast`(fanout) exchange + per-instance/control dual binding + routing key 빌더 (REQ-050) - - baseline 3문서(interface-contract/runtime-flows/project-structure) (REQ-051) -- Out of Scope: - - strategy 모듈(orb-v1) publish 코드 변경 — 계약만 정의 - - WatchdogStore Redis 외부화 / leader election / failover - - 전체 env 의 TOML 일원화(KIS/DSN/RABBITMQ_URL 은 `.env` 유지) - - old 큐 자동 삭제 스크립트(운영 절차) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-009). OQ-1(old 큐 자연 소멸)·OQ-2(`--config` 필수) Answered. -- 하위호환 핵심(REQ-049): `instance_key="default"` → 리터럴 큐명 → 기존 테스트/배포 무회귀(FR-5/NFR-1). -- NFR-2(pika per-thread) 유지, NFR-3(split-brain) 인스턴스 전용 큐로 달성, NFR-4(민감값) TOML+gitignore. -- multi-instance 라우팅(directed/broadcast)·split-brain 격리는 인프라 게이트(RabbitMQ) 통합/e2e 로 검증. diff --git a/docs/batches/bat-013_20260607_multi-instance-topology/planning.md b/docs/batches/bat-013_20260607_multi-instance-topology/planning.md deleted file mode 100644 index ef02df5..0000000 --- a/docs/batches/bat-013_20260607_multi-instance-topology/planning.md +++ /dev/null @@ -1,48 +0,0 @@ -# BAT-013 Planning - -- Status: confirmed -- Batch: BAT-013 -- Profile: standard - -## Included REQ -- REQ-048 (Configuration): `DaemonInstanceConfig.from_toml` (tomllib) + `--config` 필수 진입점 + `from_config` env 병합(`portfolio_hub_token` 주입) + `.gitignore` TOML + 토큰 로그 미노출. -- REQ-049 (Interface): `topology.py` 동적 큐명 빌더(default=리터럴 하위호환) + `declare_topology(instance_key)` + daemon intent/snapshot/control consumer per-instance 큐 구독. -- REQ-050 (Integration): `trex.direct`(topic)/`trex.broadcast`(fanout) exchange + per-instance direct binding + control dual binding + `direct_routing_key` 빌더. -- REQ-051 (Documentation): interface-contract(routing key)/runtime-flows(TOML 기동·라우팅)/project-structure(동적 큐명·설정 계층·exchange) 정합. - -## Delivery Plan -1. REQ-049: `topology.py` 큐명 빌더(default 하위호환) → `rabbitmq.declare_topology(instance_key)` → daemon consumer per-instance. 큐명 단위 + 기존 회귀. -2. REQ-050: `trex.direct`/`trex.broadcast` exchange 선언 + binding(direct per-instance, control dual) + routing key 빌더. 토폴로지 단위 + 인프라 게이트(라우팅 격리/broadcast). -3. REQ-048: `DaemonInstanceConfig.from_toml` + `--config` argparse 진입점 + `from_config` 병합 + `.gitignore` + 토큰 미노출. 로더 단위. -4. REQ-051: baseline 3문서 정합. -5. 전체 회귀 + ruff + 인프라 게이트(default 무회귀 + 라우팅) + (가능 시) 2-인스턴스 e2e. - -## Dependencies -- Internal: - - dcy-006/BAT-007 messaging(topology/rabbitmq/daemon consumer) — 변경 대상 기반. - - BAT-012 control 채널(`trex.control.req`) — control 큐가 broadcast dual binding 대상. - - BAT-008 PortfolioHubConfig.token — TOML 주입 지점. -- External: - - RabbitMQ(topic/fanout exchange). stdlib `tomllib`(py3.12). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) `declare_topology` signature 변경 + 큐명 동적화는 **모든 messaging consumer/test 표면**에 영향 — default 하위호환 규칙(리터럴 vs suffix)을 설계에서 고정해야 회귀를 막는다. (b) topic/fanout exchange + per-instance/dual binding 의 정확한 binding 매트릭스와 routing key 계약을 설계에서 확정해야 strategy 계약과 일치한다. (c) TOML+env 병합 우선순위(`portfolio_hub_token` TOML 우선) + `--config` 진입점 구조를 설계에서 명시. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-049 동적 큐명 + declare_topology(instance_key) + default 회귀 green -2. M2 — REQ-050 exchange + binding + routing key + 인프라 게이트(라우팅) green -3. M3 — REQ-048 TOML 로더 + --config + 병합 + .gitignore 단위 green -4. M4 — REQ-051 baseline 3문서 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 + 2-인스턴스 e2e(가능 시) - -## Risks -- R-1 (Medium): `declare_topology`/큐명 변경이 기존 messaging 테스트 다수에 영향. 대응: default=리터럴 큐명 하위호환으로 기존 테스트 무변경 유지(REQ-049 핵심). 빌더에 default 특수 처리. -- R-2 (Medium): old durable 큐 잔류(dcy-009 R-2). 대응: 자연 소멸 허용(OQ-1), 배포 절차 외. 코드 무관. -- R-3 (Low): broadcast 중복 제어(dcy-009 R-3). 대응: kill-switch/pause idempotency(REQ-036/advisory lock). -- R-4 (Low): TOML 토큰 커밋 노출(dcy-009 R-4). 대응: `.gitignore` + 로그 미노출(REQ-048). -- R-5 (Low): pika BlockingConnection thread-safety. 대응: per-thread connection 유지(NFR-2), 바인딩만 추가. - -## Notes -- 하위호환 우선: default 인스턴스는 기존 리터럴 큐명·RPC 경로 그대로 → 기존 277+ 테스트 무회귀가 1차 게이트. -- 2-인스턴스 e2e(라우팅 격리/broadcast)는 RabbitMQ 인프라 게이트 또는 manual; 단일 인스턴스 기본 검증은 단위로 충분. diff --git a/docs/batches/bat-013_20260607_multi-instance-topology/verification.md b/docs/batches/bat-013_20260607_multi-instance-topology/verification.md deleted file mode 100644 index c306cf0..0000000 --- a/docs/batches/bat-013_20260607_multi-instance-topology/verification.md +++ /dev/null @@ -1,64 +0,0 @@ -# BAT-013 Verification - -- Status: verified -- Batch: BAT-013 - -## Acceptance Mapping -> 구현 완료 후 각 AC 에 evidence 매핑. 아래는 계획 골격. - -### REQ-048 (TOML 로더 + --config) -| AC | Evidence (예정) | -|----|---------| -| AC-1 from_toml 파싱 | 로더 단위 | -| AC-2 --config 누락 종료 | 진입점 단위/검사 | -| AC-3 token 주입 | 로더 단위(S-5) | -| AC-4 필수 필드 누락 오류 | 로더 단위 | -| AC-5 .gitignore TOML | gitignore 확인 | -| AC-6 token 로그 미노출 | 코드/로그 검사 | - -### REQ-049 (동적 큐명 + 하위호환) -| AC | Evidence (예정) | -|----|---------| -| AC-1 named suffix | 큐명 빌더 단위 | -| AC-2 default 리터럴 | 동일 | -| AC-3 declare named 큐 | 인프라 게이트 | -| AC-4 default 회귀 없음 | 전체 pytest | -| AC-5 per-thread/스레드 수 | daemon 검사 | -| AC-6 인스턴스 격리 | 인프라 게이트/e2e | - -### REQ-050 (exchange + binding) -| AC | Evidence (예정) | -|----|---------| -| AC-1 direct/broadcast 선언 | 인프라 게이트 | -| AC-2 direct binding | 인프라 게이트 | -| AC-3 routing key 빌더 | 단위 | -| AC-4 directed 격리 | 인프라 게이트(2큐) | -| AC-5 broadcast dual 전달 | 인프라 게이트(2큐) | -| AC-6 default 회귀 | 전체 pytest | - -### REQ-051 (baseline 정합) -| AC | Evidence (예정) | -|----|---------| -| AC-1 interface-contract routing key | 문서 diff | -| AC-2 runtime-flows 기동/라우팅 | 문서 diff | -| AC-3 project-structure 동적큐/설정/exchange | 문서 diff | - -## Evidence -- Test: - - REQ-048 로더 6 단위(from_toml/필수 누락/token 주입 S-5/default ik/`--config` 누락 subprocess 종료/gitignore) green. - - REQ-049/050 토폴로지 3 단위(builders default vs named/routing key) + 인프라 게이트 2(directed 격리 S-2 / broadcast dual S-3) green. - - 단위 전체 회귀: `271 passed / 30 skipped`(`::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `301 passed`(directed 라우팅 격리 + broadcast dual + default 무회귀 S-4 + 마이그레이션 round-trip 포함). - - `ruff check trex_core tests` → All passed. -- Logs: `271 passed, 30 skipped` / `301 passed ... ~15s`. - -## Carried-Over / e2e -- S-1(named 큐 선언)·S-2(directed 격리)·S-3(broadcast dual): RabbitMQ 인프라 게이트 통합 테스트로 확인. -- S-4(default 무회귀): 전체 pytest 301 passed. -- S-5(TOML token 주입): 로더 단위. -- (이월, 비차단) 실 2-인스턴스 동시 기동 manual e2e: 실 다중 인스턴스 운영 시점 확인. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 제어 큐 rename(BAT-012 R-1)·old 큐 자연 소멸(OQ-1)·strategy publish 변경(Out of Scope)·2-인스턴스 manual e2e 는 비차단(인프라 게이트 통합으로 핵심 라우팅 검증됨). diff --git a/docs/batches/bat-014_20260608_safety-sell-cooldown/implementation.md b/docs/batches/bat-014_20260608_safety-sell-cooldown/implementation.md deleted file mode 100644 index 97bc714..0000000 --- a/docs/batches/bat-014_20260608_safety-sell-cooldown/implementation.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-014 Implementation - -- Status: completed -- Batch: BAT-014 - -## Plan Summary -- REQ-053(config 2종) → REQ-052(쿨다운 로직) → REQ-054(runtime-flows). 인메모리만, DB/스레드/인터페이스 변경 없음. - -## Changed Files -- 수정: - - `trex_core/runtime/config.py` — `SafetySellConfig.min_retry_interval_sec`(기본 600)·`max_retry_warn_count`(기본 3) + `from_env` 파싱(`TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`/`TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`) (REQ-053) - - `trex_core/application/safety_sell.py` — `_last_attempt`/`_attempt_count` dict, `_in_cooldown`/`_attempt_sell`/`_prune_cooldown`, `check_forced_close`·`check_prices` 쿨다운 gate(skip→KIS poll skip 포함)·경고, `run_once` 시작 시 prune. market_close 는 미적용 (REQ-052) - - `docs/runtime-flows.md` — safety-sell 재시도 쿨다운 flow + 가격체크 tr_id(FHKST01010100) 정정 (REQ-054) - - `tests/runtime/test_safety_sell_config.py` — 쿨다운 env 기본값/파싱 2종 - - `tests/application/test_safety_sell_worker.py` — TestRetryCooldown 6종(skip/만료 재시도/경고/prune/market_close 제외/트리거 해소) - -## Execution Log -1. REQ-053 config 2종 + from_env → `test_safety_sell_config.py` green. -2. REQ-052 쿨다운(`_in_cooldown`/`_attempt_sell`/`_prune_cooldown` + forced_close/prices gate) → TestRetryCooldown 6 green (non-transitioning sell stub 으로 영속 거부 시뮬). -3. REQ-054 runtime-flows 쿨다운 flow 추가. -4. 전체 회귀: 단위 278 passed / 30 skipped, ruff clean. - -## Validation -- `pytest tests/application/test_safety_sell_worker.py tests/runtime/test_safety_sell_config.py` → 34 passed. -- 전체 `pytest -q --deselect ...::test_sandbox_token_and_submit` → 278 passed / 30 skipped. -- `ruff check trex_core tests` → All passed. - -## Remaining Risks -- R-2(재시작 시 쿨다운 초기화 → 1회 즉시 재시도): 허용(문서 명시). -- 실 운영 거부 시나리오(휴장/잔고)에서의 쿨다운 동작은 차기 거래일 실 관찰로 보강(단위는 stub 로 시뮬). 인메모리·additive 변경이라 release 위험 낮음. diff --git a/docs/batches/bat-014_20260608_safety-sell-cooldown/index.md b/docs/batches/bat-014_20260608_safety-sell-cooldown/index.md deleted file mode 100644 index 7992d2f..0000000 --- a/docs/batches/bat-014_20260608_safety-sell-cooldown/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# BAT-014: safety-sell 영속 거부 재시도 쿨다운 (per-slot cooldown + 경고 + env) - -- Status: released -- Profile: batch-lite -- Source Discovery: dcy-010 -- Included REQ: REQ-052, REQ-053, REQ-054 - -## Summary -- dcy-010(safety-sell 영속 거부 재시도 백오프)를 batch-lite 로 delivery 한다. `SafetySellService`에 per-slot 인메모리 쿨다운(마지막 시도 시각·시도 횟수)을 추가해 영속 거부(휴장/잔고/규제) 시 `min_retry_interval_sec`(기본 600s) 이내 재시도를 건너뛰고, `max_retry_warn_count`(기본 3) 도달 시 운영자 WARNING 을 발행한다. `SafetySellConfig` env 2종 + runtime-flows 정합. **DB/스레드/인터페이스 변경 없음**(인메모리만). - -## Scope -- In Scope: - - `SafetySellService` per-slot 쿨다운 추적(`_last_attempt`/`_attempt_count`) + skip/만료 재시도 + 경고 로그 + holding 이탈 정리, market_close 제외 (REQ-052) - - `SafetySellConfig` `min_retry_interval_sec`/`max_retry_warn_count` env 2종 (REQ-053) - - `docs/runtime-flows.md` safety-sell tick 쿨다운 단계 반영 (REQ-054) -- Out of Scope: - - ExecutionService→SafetySellService 거부 사유 피드백 채널, 거부 원인별 차등 쿨다운, 쿨다운 DB 영속, 수동 해제 API, market_close 별도 쿨다운. - -## Documents -- Planning: [planning.md](./planning.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Profile **batch-lite**: 소규모(단일 service + config 2종 + 문서), 인메모리만, 구조/인터페이스/DB 변경 없음, 설계가 dcy-010 §4 에 완비 → **Design Gate: no**(design.md 불요). implementation/verification 은 해당 stage 진입 시 생성. -- OQ-1(600s)/OQ-2(3, 경고만)/OQ-3(단일 per-slot) Answered. NFR-1(인메모리)/NFR-2(무락)/NFR-4(paper reject 시뮬 테스트) 는 REQ-052 AC 로 흡수. -- 발견 근거: REL-003 Feedback / 2026-06-07 실 KIS LIVE SL/TP e2e. diff --git a/docs/batches/bat-014_20260608_safety-sell-cooldown/planning.md b/docs/batches/bat-014_20260608_safety-sell-cooldown/planning.md deleted file mode 100644 index 449245b..0000000 --- a/docs/batches/bat-014_20260608_safety-sell-cooldown/planning.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-014 Planning - -- Status: confirmed -- Batch: BAT-014 -- Profile: batch-lite - -## Included REQ -- REQ-052 (Integration): `SafetySellService` per-slot 쿨다운(`_last_attempt`/`_attempt_count`) + `_safe_sell` 전 skip / 만료 재시도 + 경고 로그 + holding 이탈 정리. market_close 제외. -- REQ-053 (Configuration): `SafetySellConfig.min_retry_interval_sec`(env `TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, 기본 600) + `max_retry_warn_count`(env `TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`, 기본 3). -- REQ-054 (Documentation): `docs/runtime-flows.md` safety-sell tick 쿨다운 단계. - -## Delivery Plan -1. REQ-053: `SafetySellConfig` 필드 2종 + `from_env` 파싱 → `test_safety_sell_config.py` 기본값/파싱. -2. REQ-052: `SafetySellService.__init__` dict 2종 + `check_forced_close`/`check_prices` 쿨다운 gate(skip/update) + `attempt_count` 경고 + `run_once` holding 이탈 정리. SafetySellService 가 config 의 두 값을 사용. paper `reject_symbols` 시뮬 단위 테스트(skip/만료 재시도/경고/정리/market_close 제외/트리거 해소). -3. REQ-054: runtime-flows 갱신. -4. 전체 회귀 + ruff. - -## Dependencies -- Internal: dcy-007/BAT-011 `SafetySellService`(변경 대상) + `SafetySellConfig`(REQ-041). paper `reject_symbols`(REQ-011/BAT-009) — 거부 시뮬. -- External: 없음. - -## Design Gate -- Design Required Before Implementation: **no** -- Reason: 변경이 단일 service 인메모리 dict 추가 + config 2 필드 + 문서로 국소적이고, 구조/인터페이스/DB/스레드 영향이 없다(추가 스레드·스키마 없음, 단일 스레드 호출이라 무락). 쿨다운 알고리즘·기본값·트리거 적용 범위가 dcy-010 §4/§6(OQ-1/2/3 Answered)에 완비되어 설계 모호성이 없다 → batch-lite, design.md 생략. - -## Milestones -1. M1 — REQ-053 config 2종 + 단위 green -2. M2 — REQ-052 쿨다운 로직 + paper reject 시뮬 단위 green -3. M3 — REQ-054 runtime-flows -4. M4 — 전체 회귀 + ruff - -## Risks -- R-1 (Low): 쿨다운 중 트리거 해소 시 불필요 경고 — holding 이탈 정리(FR-4) + 조건 해소 시 `_safe_sell` 미호출로 완화. -- R-2 (Low): 재시작 시 쿨다운 초기화 → 1회 즉시 재시도 — 허용(문서 주의). -- R-3 (Low): paper 기본은 거부 안 함 → `reject_symbols` 시뮬로 테스트 커버. - -## Notes -- batch-lite: design 생략(Design Gate no). implementation.md/verification.md 는 구현/검증 stage 진입 시 생성. diff --git a/docs/batches/bat-014_20260608_safety-sell-cooldown/verification.md b/docs/batches/bat-014_20260608_safety-sell-cooldown/verification.md deleted file mode 100644 index 0a095cd..0000000 --- a/docs/batches/bat-014_20260608_safety-sell-cooldown/verification.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-014 Verification - -- Status: verified -- Batch: BAT-014 - -## Acceptance Mapping - -### REQ-052 (per-slot 쿨다운 + 경고) -| AC | Evidence | -|----|---------| -| AC-1: 쿨다운 이내 재시도 skip (S-1) | `test_safety_sell_worker.py::TestRetryCooldown::test_skips_retry_within_interval` (1회만) | -| AC-2: 쿨다운 만료 후 재시도 (S-2) | `test_retries_after_cooldown_expiry` (+601s → 2회) | -| AC-3: max_retry_warn_count 경고 (S-3) | `test_warning_after_max_count` (caplog WARNING + slot_id) | -| AC-4: holding 이탈 시 정리 (S-4) | `test_prune_on_non_holding` (`_last_attempt`/`_attempt_count` 제거) | -| AC-5: market_close 미적용 | `test_market_close_not_gated_by_cooldown` (쿨다운 중에도 청산) | -| AC-6: 트리거 해소 시 미증가 (R-1) | `test_trigger_resolved_no_increment` (sell 없음, count 없음) | - -### REQ-053 (쿨다운 env 2종) -| AC | Evidence | -|----|---------| -| AC-1: 기본값 600/3 | `test_safety_sell_config.py::test_safety_sell_config_defaults` | -| AC-2: env 파싱(300/5) | `test_retry_cooldown_env_tuning` | -| AC-3: 미설정 회귀 없음 | 전체 278 passed (기존 safety-sell 동작 불변) | - -### REQ-054 (runtime-flows 정합) -| AC | Evidence | -|----|---------| -| AC-1: 쿨다운 flow(skip/재시도/경고/정리) | `docs/runtime-flows.md` "safety-sell 재시도 쿨다운" flow | -| AC-2: market_close 제외 명시 | 동일 flow 첫 문단 + 장 마감 flow | - -## Evidence -- Test: 신규 8종(쿨다운 6 + config 2) green. 전체 단위 **278 passed / 30 skipped**, `ruff` All passed. -- Manual: 없음(인메모리 로직 단위 커버; 영속 거부는 non-transitioning sell stub 로 시뮬 — dcy-010 NFR-4 의도). -- Logs: `34 passed`(대상 파일) / `278 passed`(전체). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 인메모리·additive(DB/스레드/인터페이스 변경 없음), default 미설정 시 기존 동작 불변. 실 운영 거부 시나리오 관찰은 차기 거래일 보강(비차단). diff --git a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/design.md b/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/design.md deleted file mode 100644 index f5cf74a..0000000 --- a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/design.md +++ /dev/null @@ -1,63 +0,0 @@ -# BAT-015 Design - -- Status: confirmed -- Batch: BAT-015 -- Profile: standard - -## 설계 목표 -- buy intent 식별자 계약을 정식 식별자(portfolio_id=asset_id) 기준으로 hard cutover 하면서, symbol 입력 제거에 따른 다수 호출부 회귀를 통제한다. -- issue #11 의 ledger silent skip 을 무성 → 가시화로 전환하되 전이 동작과 outbox 스키마는 불변으로 유지한다(OQ-4). - -## 1. buy admission 계약 (REQ-007) - -### BuyIntent 구조 -- `portfolio_id: str` 를 필수 위치 인자로 승격(intent_id, trading_date 다음). `symbol: Optional[str] = None` 으로 강등 — 클라이언트 입력이 아니라 admission 이 universe 에서 resolve 해 슬롯에 싣는 값. -- 나머지 필드(qty/allocated_capital/entry_price/strategy_id/thresholds/extra)는 불변. - -### symbol resolve 주입 경계 -- 결정: `IntentService(resolve_symbol=callable|None)` 콜백 주입(SnapshotStore 직접 의존 대신). - - 근거: (a) application 계층이 portfolio.SnapshotStore 에 직접 결합되지 않아 테스트 더블 주입이 쉽다. (b) `resolve_symbol=None` 폴백을 두면 symbol resolve 가 필요 없는 기존 단위 테스트는 portfolio_id 추가만으로 무회귀(R-1 통제). 프로덕션 daemon 은 항상 `_resolve_symbol`(SnapshotStore.get) 주입. -- `_resolve_symbol(portfolio_id) -> Optional[str]`. 부재 시 falsy. - -### `_admit_buy` reject 분기 (FR-1/FR-2/FR-3) -1. `not intent.portfolio_id` → `reject(portfolio_id_required)` (FR-1, 근본 원인 제거). -2. resolver 주입됨: `symbol = resolve_symbol(portfolio_id)`; falsy 면 `reject(unknown_portfolio)` (FR-3, 단일 reject 정책 OQ-1 — symbol fallback 없음). -3. resolver 미주입 + `intent.symbol` 도 falsy → `reject(unknown_portfolio)` (호출 대상 미상). -4. resolve 성공 시 기존 idle-slot 배정·`transit_slot(IDLE→PENDING_BUY, symbol=symbol, portfolio_id=...)` 흐름 유지. extra 에도 resolved symbol 기록. - -## 2. buy 메시지 payload 계약 (REQ-021) -- buy 핸들러는 payload 에서 `symbol` 을 읽지 않는다. `portfolio_id = _opt_str(body.get("portfolio_id")) or ""` 로 매핑(빈 문자열 → admission 의 portfolio_id_required 로 일관 거부). -- symbol 은 핸들러에서 BuyIntent 에 싣지 않음(서버 resolve 가 admission 책임). - -## 3. daemon 배선 (REQ-007) -- `runtime/daemon.py::_resolve_symbol(self, portfolio_id)` → `asset.symbol if (asset := self.snapshot_store.get(portfolio_id)) else None`. -- `IntentService(..., resolve_symbol=self._resolve_symbol)` 주입. - -## 4. ledger skip 가시화 (REQ-018, issue #11) -- `_record_ledger` 두 조기 반환 경로 모두에서 `r.ledger_skipped += 1` 후 `_log.warning(...)`: - - portfolio_id 누락: `reason=missing_portfolio_id` (slot_id/symbol/side). - - execution_id 누락: `reason=missing_execution_id` (slot_id/portfolio_id/side, 브로커 계약 회귀 방어 케이스). -- 슬롯 전이는 그대로(관측만 추가, OQ-4). outbox 마커 행/스키마 변경 없음. -- `execution.py` 에 `import logging` + `_log = logging.getLogger(__name__)` 추가. - -## 5. 운영 점검 쿼리 (REQ-055, FR-5) -- `adapters/persistence/ops_checks.py::find_ledger_gap_slots(dsn, *, trading_date=None, psycopg_module=None)` — 읽기 전용. -- anti-join SQL: `daily_slot s WHERE s.status='closed' AND (td IS NULL OR trading_date=td) AND (portfolio_id IS NULL OR NOT EXISTS(SELECT 1 FROM ledger_outbox o WHERE o.asset_id=s.portfolio_id))`. -- 반환: `slot_id/trading_date/symbol/portfolio_id/broker_order_id` dict 리스트. `daily_slot` durable → outbox 마커 없이 영구 탐지(OQ-4 위임). -- 스키마 변경 없음. 알람/주기 실행 배선은 운영 절차(Out of Scope). - -## 책임 분리 -- 런타임 관측(REQ-018, WARN+카운터): "지금 skip 났다"를 즉시 노출. -- 사후 탐지(REQ-055, anti-join): durable 슬롯 기준으로 누락을 영구·집계 탐지. -- 둘 다 outbox 마커 행 없이 성립 → outbox 전송 큐 의미 오염 회피(OQ-4 근거). - -## 하위호환 / cutover (NFR-3) -- 호환 창구 없음(OQ-2). symbol 기반 호출은 portfolio_id 누락 시 명확히 reject. -- 호출부 전환 전략: - - 비-핸들러 intent/동시성/repository 테스트: BuyIntent 에 portfolio_id 추가(resolver None → intent.symbol 폴백). - - 핸들러/RabbitMQ e2e 테스트: resolve_symbol 람다 + payload portfolio_id 동시 전환. - - ledger skip 테스트: admission 우회(`transit_slot(portfolio_id=None)`)로 skip 전제 재구성 + caplog 검증. - -## 문서 (FR-6) -- `docs/interface-contract.md`: buy payload portfolio_id 필수 + symbol 제거/서버 resolve. -- `docs/runtime-flows.md`: buy admission step 에 portfolio_id 필수 + symbol resolve + unknown_portfolio + ledger skip 가시화 단락. diff --git a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/implementation.md b/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/implementation.md deleted file mode 100644 index 5d339a6..0000000 --- a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/implementation.md +++ /dev/null @@ -1,40 +0,0 @@ -# BAT-015 Implementation - -- Status: completed -- Batch: BAT-015 - -## 구현 요약 -- dcy-011 buy intent 계약 hard cutover + ledger skip 가시화 + 운영 점검 쿼리를 단일 standard batch 로 구현. issue #6/#11 흡수. - -## 변경 내역 - -### REQ-007 — buy admission portfolio_id 필수 + symbol resolve -- `trex_core/application/intents.py`: - - `BuyIntent`: `portfolio_id: str` 를 required 위치 인자로 승격, `symbol: Optional[str] = None` 로 강등(서버 resolve 결과를 담는 필드). - - `IntentService.__init__(..., resolve_symbol=None)` — `callable(portfolio_id) -> Optional[str]` 주입점. - - `_admit_buy`: ① `not portfolio_id` → `reject(portfolio_id_required)`; ② resolver 주입 시 `symbol = resolve_symbol(portfolio_id)`, falsy 면 `reject(unknown_portfolio)`; ③ resolver 미주입 + symbol falsy → `reject(unknown_portfolio)`; 이후 `transit_slot(symbol=symbol, portfolio_id=..., extra={..., "symbol": symbol})`. -- `trex_core/runtime/daemon.py`: `_resolve_symbol(self, portfolio_id)` → `asset.symbol if (asset := self.snapshot_store.get(portfolio_id)) else None`; `IntentService(..., resolve_symbol=self._resolve_symbol)` 배선. - -### REQ-021 — buy 메시지 payload symbol 제거 -- `trex_core/application/messaging_handlers.py`: buy 핸들러가 `symbol` 을 더 이상 읽지 않음; `portfolio_id=_opt_str(body.get("portfolio_id")) or ""` 매핑. - -### REQ-018 — ledger skip 가시화 (issue #11) -- `trex_core/application/execution.py`: `import logging` + `_log = logging.getLogger(__name__)`; `_record_ledger` 두 skip 경로(portfolio_id 누락 `reason=missing_portfolio_id`, execution_id 누락 `reason=missing_execution_id`)에서 `r.ledger_skipped += 1` + `_log.warning(...)`. 슬롯 전이 동작 불변, outbox 스키마 무변경(OQ-4). - -### REQ-055 — closed↔outbox anti-join 운영 점검 (신규) -- `trex_core/adapters/persistence/ops_checks.py`(신규): `find_ledger_gap_slots(dsn, *, trading_date=None, psycopg_module=None)` 읽기 전용. `status='closed'` 슬롯 중 `portfolio_id IS NULL` 또는 대응 `ledger_outbox` 행 부재(anti-join) 식별 → `slot_id/trading_date/symbol/portfolio_id/broker_order_id` dict 리스트. 스키마 변경 없음. - -### FR-6 — 문서 정합 -- `docs/interface-contract.md`: buy payload `portfolio_id` 필수 + `symbol` 제거(서버 resolve). Last Updated → BAT-015. -- `docs/runtime-flows.md`: buy admission step 에 portfolio_id 필수 + symbol resolve + unknown_portfolio + ledger skip 가시화 단락. Last Updated → BAT-015. - -## 테스트 마이그레이션 (hard cutover, NFR-3) -- 비-핸들러(resolver None → intent.symbol 폴백, portfolio_id 추가만): `test_execution.py`, `test_intent_concurrency.py`, `test_messaging_snapshot_consumer.py`, `test_postgresql_repository.py`. -- 핸들러/e2e(resolver 람다 + payload portfolio_id): `test_messaging_intent_consumer.py`(+ 신규 3: without_portfolio_id_rejected / unknown_portfolio_rejected / symbol_resolved_from_universe), `test_rabbitmq_intent_e2e.py`. -- ledger skip(admission 우회 + caplog): `test_execution_ledger.py`(skip 테스트 재작성 + 신규 `test_ledger_skip_emits_warning`). -- 신규 PG 게이트: `tests/adapters/persistence/test_ops_checks.py`(gap 탐지 / 정상 0건). - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `282 passed / 32 skipped`(`::test_sandbox_token_and_submit` 는 LIVE `.env`(sandbox URL 아님)로 deselect — 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `314 passed`(PG anti-join 점검 + buy intent RabbitMQ e2e + 마이그레이션 round-trip 포함). -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/index.md b/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/index.md deleted file mode 100644 index c042aad..0000000 --- a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/index.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-015: buy intent portfolio_id 필수 계약 + symbol 서버 resolve + ledger skip 가시화 + 누락 점검 쿼리 - -- Status: released -- Profile: standard -- Source Discovery: dcy-011 -- Included REQ: REQ-007 (CHG-20260608-01), REQ-018 (CHG-20260608-01), REQ-021 (CHG-20260608-01), REQ-055 - -## Summary -- dcy-011(buy intent — portfolio_id 필수 / symbol 서버 resolve + ledger silent skip 가시화)을 단일 standard batch 로 delivery 한다. issue #6(buy 식별자 정정)과 issue #11(portfolio_id 없는 체결의 ledger silent skip)을 한 주기로 흡수한다. -- buy intent 계약을 hard cutover: `portfolio_id` 필수화 + admission 시 `SnapshotStore` universe 에서 `symbol` resolve + universe 부재 시 `unknown_portfolio` 거부(REQ-007), buy 메시지 payload 에서 `symbol` 제거(REQ-021), `_record_ledger` skip 가시화(WARN 로그 + `ExecutionResult.ledger_skipped` 카운터, REQ-018), closed↔`ledger_outbox` anti-join 운영 점검 `find_ledger_gap_slots` 신규(REQ-055). -- 거버넌스: dcy-011 OQ-3 결정에 따라 기존 Implemented REQ(007/018/021)는 `change-req`, 기존 REQ 가 다루지 않는 신규 기능(FR-5 운영 점검 쿼리)만 신규 REQ-055. - -## Scope -- In Scope: - - `BuyIntent` portfolio_id 필수화 + symbol Optional(서버 resolve), `IntentService(resolve_symbol=...)` 주입, `_admit_buy` portfolio_id_required/unknown_portfolio reject + symbol resolve (REQ-007) - - buy 메시지 핸들러 payload 에서 symbol 제거·portfolio_id 매핑 (REQ-021) - - `runtime/daemon.py` `_resolve_symbol`(SnapshotStore.get) 배선 (REQ-007) - - `_record_ledger` 두 skip 경로(portfolio_id/execution_id 누락) WARN 로그 + 카운터 가시화 (REQ-018) - - `adapters/persistence/ops_checks.py::find_ledger_gap_slots` 읽기 전용 anti-join 점검 (REQ-055) - - `docs/interface-contract.md` + `docs/runtime-flows.md` 정합 (FR-6, REQ-007/021/018 본문에 흡수) -- Out of Scope: - - sell intent 계약 변경(이미 portfolio_id/slot 기반) - - universe refresh 주기/스케줄 재설계 - - symbol-only 레거시 호환 창구(OQ-2 hard cutover) - - `ledger_outbox` skip 마커 행 도입(OQ-4 — 로그+메트릭만, 영구 탐지는 REQ-055 anti-join 위임) - - 과거 누락분 데이터 복구(별도 운영 이슈) - - skip 점검 쿼리의 알람/주기 실행 배선(쿼리/함수가 산출물; 운영 배포 절차) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-011). OQ-1~OQ-5 모두 Answered. -- hard cutover(NFR-3): symbol 기반 호출부(테스트 다수) 동시 전환. `IntentService(resolve_symbol=None)` 는 `intent.symbol` 로 폴백 → 비-핸들러 테스트 churn 최소화, 프로덕션 daemon 은 항상 SnapshotStore resolve. -- ledger skip 가시화(REQ-018)는 전이 동작 불변, 관측만 추가(OQ-4). -- REQ-055 anti-join 은 `daily_slot` durable 특성에 의존(outbox 마커 없이 영구 탐지). diff --git a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/planning.md b/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/planning.md deleted file mode 100644 index 8953eb5..0000000 --- a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/planning.md +++ /dev/null @@ -1,53 +0,0 @@ -# BAT-015 Planning - -- Status: confirmed -- Batch: BAT-015 -- Profile: standard - -## Included REQ -- REQ-007 (Interface, change-req CHG-20260608-01): buy admission 계약 — `portfolio_id` 필수화, admission 시 `SnapshotStore` 에서 symbol resolve, universe 부재 시 `unknown_portfolio` 거부. symbol-필수/portfolio_id-optional 폐기(hard cutover). -- REQ-021 (Integration, change-req CHG-20260608-01): buy 메시지 payload 계약에서 `symbol` 제거, `portfolio_id` 필수. -- REQ-018 (Integration, change-req CHG-20260608-01): `_record_ledger` ledger skip 가시화(WARN 로그 + `ledger_skipped` 카운터, 두 skip 경로). -- REQ-055 (Exception, 신규): closed↔`ledger_outbox` anti-join 운영 점검 `find_ledger_gap_slots`(읽기 전용). - -## Delivery Plan -1. REQ-007: `BuyIntent`(portfolio_id 필수 + symbol Optional 서버 resolve), `IntentService(resolve_symbol=...)`, `_admit_buy`(portfolio_id_required → resolve → unknown_portfolio). intent 단위 + 동시성 회귀. -2. REQ-021: buy 핸들러 payload 파싱(symbol 제거, portfolio_id 매핑). 핸들러 단위 + RabbitMQ intent e2e. -3. REQ-007 배선: `runtime/daemon.py::_resolve_symbol`(SnapshotStore.get) + IntentService 주입. -4. REQ-018: `_record_ledger` 두 skip 경로 WARN 로그 + 카운터. caplog 단위. -5. REQ-055: `adapters/persistence/ops_checks.py::find_ledger_gap_slots` anti-join SQL. PG 게이트 단위. -6. FR-6 문서: interface-contract / runtime-flows 정합. -7. 전체 회귀 + ruff + 인프라 게이트(PG + RabbitMQ). - -## Dependencies -- Internal: - - dcy-002/BAT-002 intent 제출(REQ-007) — admission 변경 대상. - - dcy-006/BAT-007 messaging 핸들러(REQ-021) — buy payload 파싱 변경 대상. - - dcy-004/BAT-006 fill→ledger(REQ-018) — `_record_ledger` skip 가시화 대상. - - dcy-005/BAT-008 SnapshotStore(REQ-027/028) — portfolio_id→symbol resolve 소스. - - REQ-019 ledger outbox 스키마 — REQ-055 anti-join 대상(스키마 변경 없음). -- External: - - PostgreSQL(anti-join 점검). RabbitMQ(buy intent e2e). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) buy 계약 hard cutover 는 `BuyIntent` 구조 + admission + 메시지 핸들러 + daemon 배선 + 다수 테스트 표면에 동시 영향 — symbol 서버 resolve 의 주입 경계(`resolve_symbol` 콜백 vs SnapshotStore 직결)와 폴백 규칙을 설계에서 고정해야 회귀를 막는다. (b) reject 정책 분기(portfolio_id_required vs unknown_portfolio)와 universe 부재 판정 경로를 명확히 해야 admission 의미가 일관된다. (c) ledger skip 가시화(REQ-018)와 anti-join 점검(REQ-055)의 책임 분리(런타임 관측 vs 사후 탐지) + OQ-4 결정(outbox 마커 미도입)을 설계에서 못박아야 스키마 오염을 피한다. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-007 BuyIntent + IntentService resolve_symbol + _admit_buy reject/resolve, intent 단위 green -2. M2 — REQ-021 buy 핸들러 payload portfolio_id 기반, 핸들러 단위 + e2e green -3. M3 — REQ-007 daemon _resolve_symbol 배선 -4. M4 — REQ-018 _record_ledger 가시화, caplog 단위 green -5. M5 — REQ-055 find_ledger_gap_slots, PG 게이트 단위 green -6. M6 — FR-6 문서 정합 + 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High): hard cutover 가 symbol 기반 기존 테스트/호출부 다수를 깬다. 대응: `IntentService(resolve_symbol=None)` 는 `intent.symbol` 폴백 → 비-핸들러 테스트는 portfolio_id 추가만으로 무회귀; 핸들러/e2e 는 resolver + payload portfolio_id 동시 전환. 모든 호출부 식별·전환(NFR-3). -- R-2 (Medium): 매수 시점 universe 부재 시 정상 매수가 unknown_portfolio 로 거부. 대응: 단일 reject 정책(OQ-1) + 진단 reason/로그. 거래일 refresh 정합은 운영 점검(R-4와 연계). -- R-3 (Low, OQ-4): skip 마커 행 미도입 → 영구 탐지는 REQ-055 anti-join 으로 충족. outbox 스키마/dispatcher 무변경. -- R-4 (Medium, issue #11): 잔존/과거 누락 체결 탐지 지연. 대응: skip 가시화(REQ-018) + anti-join 점검(REQ-055). 과거분 복구는 범위 밖. - -## Notes -- hard cutover 1차 게이트: 전체 회귀가 green 이어야 호출부 전환 누락이 없음을 보장. -- REQ-055 anti-join 은 PG 게이트(실 PostgreSQL) 통합으로 검증; gap 탐지 + 정상 0건. -- buy intent e2e(RabbitMQ)는 payload portfolio_id + resolver 경로 확인. diff --git a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/verification.md b/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/verification.md deleted file mode 100644 index b060f18..0000000 --- a/docs/batches/bat-015_20260608_buy-intent-portfolio-id-contract/verification.md +++ /dev/null @@ -1,65 +0,0 @@ -# BAT-015 Verification - -- Status: verified -- Batch: BAT-015 - -## Acceptance Mapping - -### REQ-007 (buy admission portfolio_id 필수 + symbol resolve, change-req) -| AC | Evidence | -|----|----------| -| AC-1 idle→pending_buy admit (portfolio_id 기반) | tests/application/test_messaging_intent_consumer.py::test_buy_symbol_resolved_from_universe + test_intent_concurrency.py::test_concurrent_buys_admit_exactly_idle_count | -| AC-2 sell 전이 (회귀 불변) | tests/application/test_execution_ledger.py (sell fill) + 전체 회귀 | -| AC-3 멱등 재전송 | test_intent_concurrency.py::test_duplicate_intent_id_admits_once_under_concurrency | -| AC-4 동시성 단일 writer | test_intent_concurrency.py::test_concurrent_buys_admit_exactly_idle_count | -| AC-5 결정 재조회 | 전체 회귀(IntentService.lookup) | -| portfolio_id_required 거부 (CHG delta) | test_messaging_intent_consumer.py::test_buy_without_portfolio_id_rejected | -| unknown_portfolio 거부 (CHG delta) | test_messaging_intent_consumer.py::test_buy_unknown_portfolio_rejected | -| symbol 서버 resolve (CHG delta) | test_messaging_intent_consumer.py::test_buy_symbol_resolved_from_universe | - -### REQ-021 (buy payload symbol 제거, change-req) -| AC | Evidence | -|----|----------| -| buy payload portfolio_id 매핑(symbol 미사용) | test_messaging_intent_consumer.py (3 buy 테스트) | -| RabbitMQ buy intent e2e | tests/adapters/messaging/test_rabbitmq_intent_e2e.py (인프라 게이트) | - -### REQ-018 (ledger skip 가시화, change-req) -| AC | Evidence | -|----|----------| -| AC-1 buy fill → 원장 1건 + holding | tests/application/test_execution_ledger.py::test_buy_fill_enqueues_one_ledger_item_and_writer_records_it | -| AC-2 sell fill → 원장 1건 + closed | test_execution_ledger.py::test_sell_fill_enqueues_sell_ledger_item | -| AC-3 FillStatus execution_id/fee | 위 buy 테스트 | -| AC-4 skip 시 전이 정상 + **가시화(WARN+카운터)** | test_execution_ledger.py::test_portfolio_id_absent_skips_ledger_but_transitions_slot + ::test_ledger_skip_emits_warning (caplog) | -| AC-5 단일 기록 주체 | test_domain_import_sweep + execution.py 단일 enqueue 지점 | - -### REQ-055 (closed↔outbox anti-join 운영 점검, 신규) -| AC | Evidence | -|----|----------| -| AC-1 portfolio_id NULL closed 포함 (S-4) | tests/adapters/persistence/test_ops_checks.py::test_detects_only_gap_slots (PG 게이트) | -| AC-2 portfolio_id 있으나 outbox 부재 포함 | ::test_detects_only_gap_slots | -| AC-3 정상(outbox 일치) 제외 → 0건 | ::test_no_gaps_when_all_recorded | -| AC-4 비-closed 제외 | ::test_detects_only_gap_slots | - -## Evidence -- Test: - - 단위 전체 회귀: `282 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` sandbox URL 아님, 본 batch 무관). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `314 passed`(PG anti-join 점검 gap 탐지/정상 0건 + buy intent RabbitMQ e2e + 마이그레이션 round-trip 포함). - - `ruff check trex_core tests` → All checks passed. -- Logs: `282 passed, 32 skipped, 1 deselected` / `314 passed, 1 deselected ~15.5s`. - -## 성공 기준(dcy-011) 대응 -- S-1(portfolio_id 누락 거부 + portfolio_id 만으로 resolve admit): test_buy_without_portfolio_id_rejected + test_buy_symbol_resolved_from_universe. -- S-2(매수 슬롯 portfolio_id 귀속 + outbox/ledger 정합): buy fill 원장 테스트 + e2e. -- S-3(ledger skip 무성 금지, WARN 100%): test_ledger_skip_emits_warning(caplog). -- S-4(점검 쿼리 누락 정확 식별, 정상 0건): test_detects_only_gap_slots + test_no_gaps_when_all_recorded. -- S-5(코드-문서 정합): interface-contract / runtime-flows 갱신(FR-6). - -## Carried-Over / 비차단 -- 매수 시점 universe(SnapshotStore) portfolio_id 존재 보장은 운영 런타임 데이터(거래일 refresh 정합) — 부재는 `unknown_portfolio` reject 로 안전 처리(R-2). -- REQ-055 점검 쿼리의 알람/주기 실행 배선은 운영 배포 절차(Out of Scope) — 쿼리/함수가 산출물. -- 과거 누락분 데이터 복구는 본 주기 범위 밖(필요 시 별도 운영 이슈, R-4). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. hard cutover 호출부 전환 누락은 전체 회귀(314 passed) green 으로 부재 확인. LIVE sandbox 토큰 테스트 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/design.md b/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/design.md deleted file mode 100644 index 5875194..0000000 --- a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/design.md +++ /dev/null @@ -1,65 +0,0 @@ -# BAT-016 Design - -- Status: confirmed -- Batch: BAT-016 -- Profile: standard - -## 설계 목표 -- 매도 주문이 매수 지정가(`entry_price`)로 나가는 버그(issue #10)를 도메인 모델 차원에서 구조적으로 제거한다. -- released 집행 경로 변경·컬럼 rename 마이그레이션을 회귀 없이 cutover 한다. - -## 1. 슬롯 가격 필드 의미 통일 (REQ-002) - -### rename -- `Slot.entry_price` → `Slot.limit_price` (side 무관 "현재 pending 주문 지정가"). `EXPECTED_SLOT_FIELDS` 동시 반영(컬럼 계약 가드가 곧 검출). -- `transit_slot`/`_apply_transition_fields` 의 `entry_price` 파라미터 → `limit_price`. - -### limit_price 생애주기 (FR-1, 핵심) -- 결정: limit_price 는 **pending 상태에서만 유효**. `_apply_transition_fields` 의 generic 경로에서: - - `to_state ∈ {PENDING_BUY, PENDING_SELL}` → `limit_price = 입력값 if not None else slot.limit_price`(set on entry). - - `to_state ∈ {HOLDING, CLOSED}` → `limit_price = None`(clear on exit). - - `to_state == IDLE`(롤백/복구) → 기존 reset 분기에서 이미 None clear. -- 효과: PENDING_BUY→HOLDING 에서 매수 지정가가 clear 되므로, 이후 HOLDING→PENDING_SELL 에서 매도 가격 미전달이면 slot.limit_price 는 None → 시장가. 매수 지정가가 매도로 누수될 경로가 구조적으로 없음. -- 전이 매트릭스: - - IDLE→PENDING_BUY: set(매수 지정가) / PENDING_BUY→HOLDING: clear / HOLDING→PENDING_SELL: set(매도 지정가 또는 None) / PENDING_SELL→CLOSED: clear / *→IDLE: clear. - -### SlotService.sell (REQ-036) -- `sell(slot_id, actor, reason, *, limit_price=None)`. default None → 자율 매도(safety-sell/kill-switch)는 가격 없이 호출 → 시장가. transit_slot 에 limit_price 전달. - -## 2. dispatch 가격 결정 (REQ-013) -- `_dispatch`: side 분기 제거. `limit = int(slot.limit_price) if slot.limit_price else None`; `order_type = "00" if limit is not None else "01"`. 매수가 재사용 경로 삭제. - -## 3. sell intent 계약 (REQ-007) -- `SellIntent.limit_price: Optional[float] = None`. `_admit_sell` → `transit_slot(..., limit_price=intent.limit_price)`. -- `_admit_buy`: 기존 `entry_price=intent.entry_price`(BuyIntent 외부 필드 유지) → 슬롯 `limit_price=intent.entry_price` 로 매핑(매수 동작 동일). -- 단일 메커니즘(가격 유무 분기, OQ-2): reason 별 코드 차등 없음. - -## 4. 메시지 경계 (REQ-007, NFR-3) -- sell 핸들러: `limit_price=_opt_float(body.get("limit_price"))` 추가. -- buy 핸들러: 외부 payload 필드명 `entry_price` 유지(BuyIntent.entry_price). -- slot read(`_slot_to_dict`): 외부 read 키 `"entry_price"` 유지하되 내부 `s.limit_price` 로 매핑(계약 호환 — 외부 필드명 rename 은 본 주기 밖). - -## 5. persistence + 마이그레이션 (REQ-006) -- `006_rename_entry_price_to_limit_price.sql`: `ALTER TABLE daily_slot RENAME COLUMN entry_price TO limit_price` 를 idempotent DO-block(컬럼 존재 && 신컬럼 부재 시에만)으로. 글롭 재적용 모델에서 안전. 순수 rename → 기존 행 데이터 보존(S-3). -- `postgresql.py`: `_SLOT_COLUMNS`/update_slot/_row_to_slot 의 entry_price → limit_price. - -## 6. 컬럼 계약 가드 (REQ-003) -- `test_contract.py`(도메인 필드셋)·`test_migration_and_drift.py`(DB 컬럼 = EXPECTED_SLOT_FIELDS)는 상수 참조라 rename 자동 반영; PG 게이트에서 실 컬럼명 limit_price 검증. - -## 7. baseline 문서 (REQ-015) -- runtime-flows: dispatch 가격 결정 = slot.limit_price 단일 필드 + limit_price 생애주기. -- interface-contract: sell payload optional limit_price + ORD_DVSN 매핑. -- data-model: 슬롯 limit_price 컬럼 의미/생애주기. - -## 하위호환 / 회귀 통제 -- 외부 buy/sell payload·slot read 필드명 유지(NFR-3) → 전략 모듈 계약 무파괴. -- sell limit_price optional default None → 기존 재량 매도 호출 시장가로 안전 동작. -- 전체 회귀 green 이 rename 호출부 누락 부재의 1차 게이트. - -## 테스트 설계 -- 신규 `tests/application/test_sell_pricing.py`(recording paper broker 로 OrderRequest 직접 검증): - - (a) 지정가 매수 슬롯 + 가격 미전달 매도 → sell order_type "01"/limit None(entry_price 미사용 — 핵심 버그 검출). - - (b) 지정가 매수 슬롯 + 가격 전달 매도 → "00"/그 가격. - - (c) SlotService.sell(가격 없음) → 시장가 + closed(S-2). - - (d) 시장가 매수 슬롯 매도 → 시장가(회귀 안전). -- `test_service.py`: PENDING_BUY→HOLDING 시 limit_price clear 단언 추가. diff --git a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/implementation.md b/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/implementation.md deleted file mode 100644 index f58f4fb..0000000 --- a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/implementation.md +++ /dev/null @@ -1,48 +0,0 @@ -# BAT-016 Implementation - -- Status: completed -- Batch: BAT-016 - -## 구현 요약 -- dcy-012 매도 가격 버그(issue #10)를 슬롯 `entry_price`→`limit_price` rename + clear-on-exit 생애주기 + dispatch 단일 필드 결정으로 정정. 전부 change-req(신규 REQ 없음). - -## 변경 내역 - -### REQ-002 — 슬롯 도메인 rename + 생애주기 -- `trex_core/domain/slot/models.py`: `Slot.entry_price` → `limit_price`(주석: side 무관 pending 주문 지정가, clear-on-exit); `EXPECTED_SLOT_FIELDS` 의 entry_price → limit_price. -- `trex_core/domain/slot/service.py`: - - `transit_slot`/`_apply_transition_fields` 파라미터 `entry_price` → `limit_price`. - - IDLE reset 분기: `entry_price=None` → `limit_price=None`. - - generic 경로: `to_state ∈ {PENDING_BUY, PENDING_SELL}` 면 set(입력 or 유지), 아니면(HOLDING/CLOSED) `new_limit_price=None` clear. - - `SlotService.sell(..., *, limit_price=None)` 추가 → transit_slot 에 전달. - -### REQ-013 — dispatch 단일 필드 -- `trex_core/application/execution.py::_dispatch`: `limit = int(slot.limit_price) if slot.limit_price else None`(side 분기/entry_price 재사용 제거). order_type 은 limit 유무로 "00"/"01". - -### REQ-007 — sell intent 가격 -- `trex_core/application/intents.py`: `SellIntent.limit_price: Optional[float] = None`; `_admit_sell` → `transit_slot(..., limit_price=intent.limit_price)`; `_admit_buy` → `limit_price=intent.entry_price`(BuyIntent.entry_price 외부 필드 유지). -- `trex_core/application/messaging_handlers.py`: sell 핸들러 `limit_price=_opt_float(body.get("limit_price"))`; buy payload `entry_price` 유지; `_slot_to_dict` 외부 키 `"entry_price"` 유지하되 `s.limit_price` 매핑(NFR-3). - -### REQ-036 — 자동 매도 시장가 -- `SlotService.sell` optional `limit_price`(default None) → safety-sell/kill-switch 자율 매도는 가격 미전달 → 시장가. - -### REQ-006 — 마이그레이션 + persistence -- `trex_core/adapters/persistence/sql/006_rename_entry_price_to_limit_price.sql`(신규): idempotent DO-block rename(컬럼 존재 && 신컬럼 부재 시에만). 순수 rename → 데이터 보존. -- `trex_core/adapters/persistence/postgresql.py`: `_SLOT_COLUMNS`/update_slot SQL+바인딩/_row_to_slot 의 entry_price → limit_price. - -### REQ-003 — 컬럼 계약 가드 -- `EXPECTED_SLOT_FIELDS` rename 으로 `test_contract.py`/`test_migration_and_drift.py` 자동 반영(상수 참조). PG 게이트에서 실 컬럼 limit_price 검증. - -### REQ-015 — baseline 문서 -- `docs/runtime-flows.md`: dispatch 가격 결정(slot.limit_price 단일 필드) + limit_price 생애주기 set/clear. Last Updated → BAT-016. -- `docs/interface-contract.md`: sell payload optional limit_price + ORD_DVSN 매핑. Last Updated → BAT-016. -- `docs/data-model.md`: 슬롯 limit_price 컬럼 의미/생애주기. Last Updated → BAT-016. - -## 테스트 -- 신규 `tests/application/test_sell_pricing.py`(recording paper broker): 4건 — (a) 지정가 매수+시장가 매도(entry_price 미사용), (b) 지정가 매수+지정가 매도, (c) SlotService.sell 시장가+closed, (d) 시장가 매수 매도 회귀. -- `tests/domain/slot/test_service.py`: PENDING_BUY→HOLDING 시 `limit_price is None` 단언 추가; 두 →HOLDING 전이의 obsolete `entry_price=` 인자 제거. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `286 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `318 passed`(006 rename 마이그레이션 적용 + 컬럼 계약 limit_price + 마이그레이션 idempotency 포함). -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/index.md b/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/index.md deleted file mode 100644 index 34450a3..0000000 --- a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/index.md +++ /dev/null @@ -1,36 +0,0 @@ -# BAT-016: 매도 가격 정책 정정 + 슬롯 entry_price→limit_price rename (sell dispatch 버그) - -- Status: released -- Profile: standard -- Source Discovery: dcy-012 -- Included REQ: REQ-013 (CHG-20260608-01), REQ-007 (CHG-20260608-02), REQ-036 (CHG-20260608-01), REQ-002 (CHG-20260608-01), REQ-006 (CHG-20260608-01), REQ-003 (CHG-20260608-01), REQ-015 (CHG-20260608-01) - -## Summary -- dcy-012(매도 주문이 현재가가 아닌 entry_price 지정가로 나가는 버그, issue #10)를 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-012 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**. -- 핵심: 슬롯의 매수 전용 `entry_price` 를 side 무관 "현재 pending 주문 지정가" 단일 필드 `limit_price` 로 내부 rename(Option A) 하고, pending 이탈(→HOLDING/→CLOSED) 시 clear 한다. `_dispatch` 는 `slot.limit_price` 한 필드만 읽어 가격/주문유형을 결정(값=지정가 `00`, 없음=시장가 `01`). `SellIntent`/sell payload 에 optional `limit_price` 추가, 자율 매도(safety-sell/kill-switch)는 가격 미전달 → 시장가. - -## Scope -- In Scope: - - 슬롯 `entry_price` → `limit_price` 내부 rename(도메인 모델/EXPECTED_SLOT_FIELDS, REQ-002) + pending 이탈 clear 생애주기 - - DB 컬럼 rename 마이그레이션 `006_*.sql`(idempotent) + persistence 매핑 (REQ-006) - - `_dispatch` 가 `slot.limit_price` 단일 필드로 가격/주문유형 결정 (REQ-013) - - `SellIntent`/sell payload optional `limit_price` + `_admit_sell` 운반, `SlotService.sell` optional 가격 (REQ-007/REQ-036) - - 컬럼 계약/드리프트 가드 갱신 (REQ-003) - - baseline 문서(runtime-flows/interface-contract/data-model) 정합 (REQ-015) -- Out of Scope: - - 외부 buy/sell payload·slot read 의 필드명(`entry_price`) rename — REQ-007/dcy-011 과 조율할 별도 계약 변경(NFR-3 호환 유지) - - issue #9(KIS 외부 취소 시 pending_sell 갇힘) 자체의 복구 로직 — 본 버그는 #9 의 한 원인 제거, 복구는 후속 분리 - - dispatch 시 현재가 자동 조회(`get_current_price`) 기반 지정가 — OQ-1 미채택 - - buy 주문 가격 의미 변경(매수 지정가는 동일 limit_price 로 set, 동작 동일) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-012). OQ-1(하이브리드)·OQ-2(가격 유무 분기)·OQ-3(전부 change-req) Answered. -- 버그 재현 조건: `entry_price` 가 채워진 **지정가 매수 슬롯**의 매도. 시장가 매수 슬롯은 entry_price=NULL 로 우연히 정상 → 검증은 지정가 매수 슬롯 케이스 필수(S-1). -- `OrderRequest.limit_price`(broker_port) 는 기존 존재 — 슬롯 필드 rename 으로 이름까지 정합. -- 마이그레이션은 글롭 재적용 모델이므로 006 은 idempotent DO-block(컬럼 존재 시에만 rename). diff --git a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/planning.md b/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/planning.md deleted file mode 100644 index bc60d95..0000000 --- a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/planning.md +++ /dev/null @@ -1,55 +0,0 @@ -# BAT-016 Planning - -- Status: confirmed -- Batch: BAT-016 -- Profile: standard - -## Included REQ -- REQ-002 (Interface, change-req): Slot `entry_price`→`limit_price` rename + clear-on-exit(→HOLDING/→CLOSED) 생애주기. -- REQ-006 (Data, change-req): daily_slot 컬럼 rename 마이그레이션 006(idempotent) + persistence 매핑. -- REQ-013 (Integration, change-req): `_dispatch` 가 `slot.limit_price` 단일 필드로 가격/주문유형 결정(매수가 재사용 제거). -- REQ-007 (Interface, change-req): `SellIntent`/sell payload optional `limit_price` + `_admit_sell` 운반; buy entry_price→limit_price 매핑. -- REQ-036 (Interface, change-req): `SlotService.sell` optional `limit_price`(default None → 자율 매도 시장가). -- REQ-003 (Testing, change-req): 컬럼 계약/드리프트 가드 limit_price 반영. -- REQ-015 (Documentation, change-req): runtime-flows/interface-contract/data-model 정합. - -## Delivery Plan -1. REQ-002: `models.py` Slot 필드 + EXPECTED_SLOT_FIELDS rename; `service.py` transit_slot/_apply_transition_fields 파라미터 rename + clear-on-exit(→HOLDING/→CLOSED), `SlotService.sell` optional limit_price. -2. REQ-013: `execution.py::_dispatch` 가 `slot.limit_price` 단일 필드 사용. -3. REQ-007: `intents.py` SellIntent.limit_price + _admit_sell 운반 + _admit_buy limit_price 매핑; `messaging_handlers.py` sell payload limit_price, slot read 외부 entry_price→limit_price 매핑. -4. REQ-006: `006_*.sql` rename 마이그레이션(idempotent) + `postgresql.py` 매핑. -5. REQ-003: 컬럼 계약/드리프트 가드 확인(상수 참조 자동 + DB 게이트). -6. REQ-015: baseline 3문서 정합. -7. 신규 테스트(test_sell_pricing) + 전체 회귀 + ruff + 인프라 게이트(PG 마이그레이션/컬럼 계약). - -## Dependencies -- Internal: - - dcy-003/BAT-004 broker execution(`_dispatch`) — 버그 지점. - - dcy-001/BAT-001 slot 도메인(REQ-002/003) — rename 대상. - - dcy-002/BAT-002 persistence(REQ-006) — 컬럼/마이그레이션. - - dcy-007/BAT-010 safety-sell(REQ-036 SlotService.sell) — 자율 매도 시장가 경로. - - dcy-011/BAT-015 — slot.symbol 서버 resolve(가정 A-1) 충족. -- External: - - PostgreSQL(rename 마이그레이션). KIS ORD_DVSN 00/01 매핑(OrderRequest 경유). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) `entry_price→limit_price` rename 은 도메인 모델·서비스 전이·persistence·마이그레이션·컬럼 계약 가드까지 **교차 절단(cross-cutting)** — 전이별 set/clear 매트릭스를 설계에서 못박지 않으면 매수 지정가 누수/복원 회귀(R-4) 위험. (b) 매도 가격 하이브리드(가격 유무 분기)의 단일 메커니즘 경계(자율=None=시장가, 재량=optional 지정가)를 설계에서 고정해야 reason 별 분기 혼입을 막는다(OQ-2). (c) released 집행 경로(R-1)·컬럼 rename 마이그레이션 idempotency 를 설계에서 명시. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-002 도메인 rename + clear-on-exit, 단위 green -2. M2 — REQ-013 _dispatch limit_price 단일 필드 + REQ-007/036 sell 가격 경로, test_sell_pricing green -3. M3 — REQ-006 마이그레이션 006 + persistence 매핑, PG 게이트 컬럼 계약 green -4. M4 — REQ-003 가드 + REQ-015 문서 정합 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High): released 집행 경로 매도 동작 변경. 대응: paper recording broker 로 손절/지정가/현재가>매수가 시나리오 검증 + change-req 이력. 실거래 배포는 release 단계 검증. -- R-2 (Medium): 재량 지정가 미체결 시 pending_sell 잔류. 대응: 본 주기는 가격 결정 분기에 한정, 미체결 처리는 issue #9/기존 롤백 범위. -- R-3 (Low, OQ-2): reason 별 차등 없이 가격 유무로 분기 → 자율 매도는 default None 으로 항상 시장가(체결성 보장). -- R-4 (Medium): rename/clear-on-exit 가 도메인/persistence/마이그레이션 교차. 대응: 전이별 set/clear 단위 + 컬럼 계약 가드 + idempotent 마이그레이션 + 외부 필드명 유지(경계 매핑). - -## Notes -- 1차 게이트: 전체 회귀 green(rename 호출부 누락 부재 확인). -- 버그 검증은 반드시 지정가 매수 슬롯(entry_price 채워짐) 매도 케이스 포함(S-1 측정 조건). -- 마이그레이션은 PG 게이트(실 PostgreSQL) 컬럼 계약/드리프트 테스트로 검증. diff --git a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/verification.md b/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/verification.md deleted file mode 100644 index 8d632b2..0000000 --- a/docs/batches/bat-016_20260608_sell-pricing-limit-price-rename/verification.md +++ /dev/null @@ -1,74 +0,0 @@ -# BAT-016 Verification - -- Status: verified -- Batch: BAT-016 - -## Acceptance Mapping - -### REQ-013 (dispatch 단일 필드, change-req) -| AC | Evidence | -|----|----------| -| dispatch 가 slot.limit_price 단일 필드로 결정(매수가 재사용 금지) | tests/application/test_sell_pricing.py::test_limit_buy_then_market_sell_does_not_reuse_buy_limit | -| 기존 buy/sell lifecycle 회귀 | tests/application/test_execution.py (전체) | - -### REQ-007 (sell intent 가격, change-req) -| AC | Evidence | -|----|----------| -| sell optional limit_price → 지정가 | test_sell_pricing.py::test_limit_buy_then_limit_sell_uses_intent_price | -| 미지정 → 시장가(호환) | test_sell_pricing.py::test_limit_buy_then_market_sell_does_not_reuse_buy_limit | -| buy entry_price → 슬롯 limit_price 매핑(동작 동일) | test_sell_pricing.py::test_limit_buy_then_market_sell... (buy_req order_type "00"/1000) | -| 멱등/동시성 회귀 | tests/application/test_intent_concurrency.py | - -### REQ-036 (자동 매도 시장가, change-req) -| AC | Evidence | -|----|----------| -| SlotService.sell(가격 없음) → 시장가 + closed | test_sell_pricing.py::test_safety_sell_dispatches_at_market | -| idempotency 회귀 | tests/application/test_auto_sell* / 전체 회귀 | - -### REQ-002 (도메인 rename + clear-on-exit, change-req) -| AC | Evidence | -|----|----------| -| limit_price clear on →HOLDING | tests/domain/slot/test_service.py::test_pending_buy_to_holding (limit_price is None) | -| clear on →CLOSED / →IDLE | test_service.py::test_closed_to_idle_resets_fields_and_bumps_counter + 전체 회귀 | -| 필드셋 계약 | tests/domain/slot/test_contract.py | - -### REQ-006 (마이그레이션 + persistence, change-req) -| AC | Evidence | -|----|----------| -| 컬럼 rename(데이터 보존) | tests/adapters/persistence/test_migration_and_drift.py::test_daily_slot_columns_match_expected_contract (PG 게이트) | -| 마이그레이션 idempotent | ::test_apply_migrations_is_idempotent | -| persistence round-trip | PG 게이트 repository/전이 테스트 전반 | - -### REQ-003 (컬럼 계약 가드, change-req) -| AC | Evidence | -|----|----------| -| 도메인 필드셋 = limit_price | tests/domain/slot/test_contract.py | -| DB 컬럼 드리프트 가드 = limit_price | test_migration_and_drift.py::test_daily_slot_columns_match_expected_contract / ::test_drift_detected_when_contract_extended | - -### REQ-015 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| runtime-flows/interface-contract/data-model 정합 | 문서 diff(BAT-016) | - -## Evidence -- Test: - - 단위 전체 회귀: `286 passed / 32 skipped`(신규 sell-pricing 4 + holding clear 단언 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `318 passed`(006 rename 마이그레이션 + 컬럼 계약 limit_price + idempotency + 전이 round-trip 포함). - - `ruff check trex_core tests` → All checks passed. -- Logs: `286 passed, 32 skipped, 1 deselected` / `318 passed, 1 deselected ~15.8s`. - -## 성공 기준(dcy-012) 대응 -- S-1(매도가 매수 지정가 재사용 안 함): test_limit_buy_then_market_sell_does_not_reuse_buy_limit(a) + test_limit_buy_then_limit_sell_uses_intent_price(b) + test_market_buy_slot_sells_at_market_regression(c). **지정가 매수 슬롯** 케이스 포함(측정 필수 조건 충족). -- S-2(손절 시장가 체결, pending_sell 갇힘 없음): test_safety_sell_dispatches_at_market. -- S-3(rename 마이그레이션 데이터 보존 + 생애주기): test_migration_and_drift(컬럼 계약+idempotency) + test_service(holding clear). -- S-4(코드-문서 정합): runtime-flows/interface-contract/data-model 갱신. - -## Carried-Over / 비차단 -- KIS 시장가 매도(`ORD_DVSN="01"`)의 paper/실계좌 수용·체결 특성(A-4): paper 검증 완료, 실계좌는 release 단계 운영 검증. -- 재량 지정가 미체결 시 pending_sell 잔류(R-2)·issue #9 복구는 본 주기 밖. -- 과거 누락/잘못 체결 데이터 보정은 범위 밖. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. rename 호출부 전환 누락은 전체 회귀(318 passed) green 으로 부재 확인. 외부 payload/read 필드명 유지로 전략 모듈 계약 무파괴. LIVE sandbox 토큰 테스트 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/design.md b/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/design.md deleted file mode 100644 index f478034..0000000 --- a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/design.md +++ /dev/null @@ -1,49 +0,0 @@ -# BAT-017 Design - -- Status: confirmed -- Batch: BAT-017 - -## 설계 목표 -- KIS 외부 취소를 종료 상태로 정확히 매핑해 pending_sell 영구 갇힘(issue #9)을 자동 복구한다. -- 부분 체결 후 취소를 정확 회계(과매도·이중기록 방지)하고, 미지 원인 갇힘을 Alert-only 워치독으로 가시화한다. - -## 1. FillStatus 계약 확장 (REQ-012) -- `FillStatus.cancelled: bool = False` 추가. `is_partial_cancel` 프로퍼티 = `cancelled and filled_qty > 0`. -- 매핑 규칙: - - 체결 0 + 외부 취소 → `state="cancelled"`(기존 `is_terminal_reject` True). - - 부분 체결(0= threshold` 면 `r.stale_pending += 1` + WARN. **상태 불변**. -- `slot.updated_at` 출처: mark_dispatched/transit 가 갱신 → "마지막 쓰기 이후 경과"가 곧 "pending 체류 시간" 근사. -- config: `DaemonConfig.pending_stale_threshold_sec`(env `TREX_PENDING_STALE_THRESHOLD`, default 0). daemon 이 ExecutionService 에 주입. - -## 5. 포지션 정합 연계 (REQ-031) -- `_on_partial_cancel` 이 slot.qty 를 잔여 held 와 일치시켜 reconcile_positions 의 입력 정합을 유지. reconcile 코드 자체 변경 없음(연계 change-req). - -## 6. baseline 문서 (REQ-015) -- interface-contract: FillStatus.cancelled/is_partial_cancel + state별 집행 처리 + KIS cncl_yn 매핑. -- runtime-flows: poll 단계 외부 취소(전량→롤백 / 부분→회계) + Alert-only stale 워치독 + limit_price clear 재매도. - -## 자동 복구 성립 근거 (A-2) -- `is_terminal_reject` 가 이미 `cancelled` 포함 + `_rollback` 이 pending_sell→holding 수행 → KIS 어댑터가 cancelled 만 정확히 반환하면 application 경로 재사용으로 복구(전량 취소 케이스는 코드 추가 없이 성립). - -## 테스트 설계 -- `tests/adapters/broker/test_kis_cancel.py`(httpx stub): full cancel→cancelled, zero-fill non-cancel→accepted, partial+cancel→is_partial_cancel(execution_id 확인), full fill 회귀. -- `tests/application/test_execution_cancel.py`(programmable broker): 전량 취소→holding 복귀+clear, 부분 취소→원장 1건+qty 차감+cost basis 유지, stale 워치독 WARN(상태 불변), 비활성 default. diff --git a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/implementation.md b/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/implementation.md deleted file mode 100644 index 1acf6fa..0000000 --- a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/implementation.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-017 Implementation - -- Status: completed -- Batch: BAT-017 - -## 구현 요약 -- dcy-013 KIS 외부 취소 감지(issue #9) + 부분 체결 회계 + pending stale 워치독(Alert-only)을 전부 change-req 로 구현(신규 REQ 없음). - -## 변경 내역 - -### REQ-012 — KIS 취소 감지 + FillStatus 확장 -- `trex_core/application/broker_port.py`: `FillStatus.cancelled: bool = False` + `is_partial_cancel`(=`cancelled and filled_qty > 0`). -- `trex_core/adapters/broker/kis.py::poll_fill`: `cncl_yn` 파싱(`=="Y"`, 미지 값 보수적 비취소). 체결 0+취소 → `state="cancelled"`; 부분 체결+취소 → `state="partially_filled"` + `cancelled=True` + execution_id/fee 합성. 전량 체결 경로 불변. - -### REQ-013/018 — 집행 부분 체결 회계 -- `trex_core/application/execution.py::_poll`: `is_filled` → `is_partial_cancel` → `is_terminal_reject` → else(대기+stale) 분기. -- `_on_partial_cancel`: 매도 `PENDING_SELL→HOLDING qty=잔여`(cost basis 유지), 매수 `PENDING_BUY→HOLDING qty=체결분 avg_price=체결가`; `_record_ledger`(REQ-018 재사용)로 체결분 원장 1회 + clear_dispatch + event. `r.filled += 1`. -- 전량 취소(`cancelled`, 체결 0)는 기존 `is_terminal_reject` → `_rollback`(holding 복귀 + clear_dispatch) 재사용. - -### REQ-014 — pending stale 워치독 (Alert-only) -- `execution.py`: `ExecutionService(stale_pending_threshold_sec=...)`(<=0/None 비활성), `_check_stale_pending`(age=now-updated_at, 초과 시 WARN + `r.stale_pending += 1`, 상태 불변), `ExecutionResult.stale_pending`. -- `trex_core/runtime/config.py`: `DaemonConfig.pending_stale_threshold_sec`(env `TREX_PENDING_STALE_THRESHOLD`, default 0). -- `trex_core/runtime/daemon.py`: ExecutionService 에 threshold 주입. - -### REQ-031 — 포지션 정합 연계 -- `_on_partial_cancel` 이 slot.qty 를 잔여 held 와 일치시켜 정합 입력 유지(과매도 방지). reconcile_positions 코드 변경 없음. - -### REQ-015 — baseline 문서 -- `docs/interface-contract.md`: FillStatus.cancelled/is_partial_cancel + state별 집행 처리 + KIS cncl_yn 매핑. Last Updated → BAT-017. -- `docs/runtime-flows.md`: poll 단계 외부 취소(전량→롤백 / 부분→회계) + Alert-only stale 워치독. Last Updated → BAT-017. - -## 테스트 -- 신규 `tests/adapters/broker/test_kis_cancel.py`(httpx stub): 4건 — full cancel→cancelled, zero-fill non-cancel→accepted, partial+cancel→is_partial_cancel(execution_id), full fill 회귀. -- 신규 `tests/application/test_execution_cancel.py`(programmable broker): 4건 — 전량 취소→holding 복귀+clear, 부분 취소→sell 원장 1건+qty 6 잔여+cost basis 1000 유지, stale 워치독 WARN(상태 PENDING_SELL 불변), 비활성 default 무경고. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `294 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `326 passed`. -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/index.md b/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/index.md deleted file mode 100644 index 43cc75e..0000000 --- a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/index.md +++ /dev/null @@ -1,36 +0,0 @@ -# BAT-017: KIS 외부 취소 감지 + 부분 체결 회계 + pending stale 워치독 (issue #9) - -- Status: released -- Profile: standard -- Source Discovery: dcy-013 -- Included REQ: REQ-012 (CHG-20260608-01), REQ-013 (CHG-20260608-02), REQ-018 (CHG-20260608-02), REQ-031 (CHG-20260608-01), REQ-014 (CHG-20260608-01), REQ-015 (CHG-20260608-02) - -## Summary -- dcy-013(KIS 외부 주문 취소 시 슬롯이 pending_sell 에 영구 갇힘 — cncl_yn 미파싱, issue #9)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-013 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**(워치독 임계 env 는 REQ-014 에 흡수). -- 핵심: KIS `poll_fill` 이 `inquire-daily-ccld` 의 `cncl_yn="Y"` 를 파싱해 외부 취소를 감지한다. 체결 0 취소 → `cancelled`(기존 롤백 경로로 holding 자동 복구), 부분 체결 후 취소 → `is_partial_cancel`(체결분 원장 1회 + qty 차감 + 잔여 holding 복귀, OQ-1 A안). 보강책으로 pending 주문 stale 워치독(OQ-2 Alert-only: WARN+카운터, 상태 미변경). - -## Scope -- In Scope: - - KIS `poll_fill` 의 `cncl_yn` 파싱 + `cancelled`/partial-cancel 매핑, `FillStatus.cancelled`/`is_partial_cancel` (REQ-012) - - 집행 루프 부분 체결 회계 `_on_partial_cancel`(체결분 원장 + qty 차감 + holding 복귀), 전량 취소 롤백 재사용 (REQ-013) - - 부분 체결분 원장 1회 기록(`_record_ledger` 재사용) (REQ-018) - - 잔여 포지션 정합 연계(슬롯 qty ↔ 실 잔고, 과매도 방지) (REQ-031) - - pending stale 워치독 Alert-only + `TREX_PENDING_STALE_THRESHOLD` env (REQ-014) - - baseline 문서(interface-contract/runtime-flows) 정합 (REQ-015) -- Out of Scope: - - buy 경로·paper 브로커 동작 변경(paper 는 이미 취소 정상 처리) - - 매도 가격 정책(dcy-012/BAT-016 에서 처리 — 본 주기는 취소 *감지*만) - - 워치독 auto-remediation(타임아웃 시 자동 롤백/취소) — 정상 slow fill·지정가 대기 상호작용·KIS 취소 능력 필요로 후속 분리 - - 과거 갇힘 슬롯의 일괄 보정(운영 절차) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-013). OQ-1(A안 정확 회계)·OQ-2(워치독 Alert-only)·OQ-3(전부 change-req) Answered. -- 자동 회복 성립 근거: `FillStatus.is_terminal_reject` 가 이미 `cancelled` 를 포함하고 `_rollback` 이 `pending_sell→holding` 을 수행(A-2) → KIS 어댑터가 취소만 정확히 반환하면 application 경로 재사용으로 복구. -- dcy-012(BAT-016) 가정 충족: holding 진입 시 `limit_price` clear → 재매도 시장가(A-1). -- R-1(KIS 취소 필드 규약): `cncl_yn` 가정. 실 응답 확인은 운영/검증 단계 — 미지 값 보수 처리 + stale 워치독(FR-3) backstop. diff --git a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/planning.md b/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/planning.md deleted file mode 100644 index 2653a6b..0000000 --- a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/planning.md +++ /dev/null @@ -1,51 +0,0 @@ -# BAT-017 Planning - -- Status: confirmed -- Batch: BAT-017 -- Profile: standard - -## Included REQ -- REQ-012 (Integration, change-req): KIS `poll_fill` `cncl_yn` 파싱 + `cancelled`/partial-cancel 매핑, `FillStatus.cancelled`/`is_partial_cancel`. -- REQ-013 (Integration, change-req): 집행 루프 `_on_partial_cancel`(체결분 원장 + qty 차감 + holding 복귀), 전량 취소 롤백 재사용. -- REQ-018 (Integration, change-req): 부분 체결분 원장 1회 기록(`_record_ledger` 재사용). -- REQ-031 (Exception, change-req): 잔여 포지션 정합 연계(슬롯 qty ↔ 실 잔고). -- REQ-014 (Exception, change-req): pending stale 워치독 Alert-only + `TREX_PENDING_STALE_THRESHOLD`. -- REQ-015 (Documentation, change-req): interface-contract/runtime-flows 정합. - -## Delivery Plan -1. REQ-012: `broker_port.py` FillStatus.cancelled + is_partial_cancel; `kis.py::poll_fill` cncl_yn 파싱(full cancel/partial cancel 매핑 + execution_id). -2. REQ-013/018: `execution.py::_poll` is_partial_cancel 분기 + `_on_partial_cancel`(원장 1회 + qty 차감 + holding). 전량 취소는 기존 is_terminal_reject 롤백. -3. REQ-014: `_check_stale_pending` + ExecutionResult.stale_pending + ExecutionService threshold; `config.py` `pending_stale_threshold_sec`/env; `daemon.py` 배선. -4. REQ-031: 부분 회계가 잔여 qty 정합 유지(연계, reconcile 코드 무변경) 확인. -5. REQ-015: interface-contract/runtime-flows 정합. -6. 신규 테스트(test_kis_cancel, test_execution_cancel) + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-003/BAT-004 broker execution(poll/rollback/REQ-012/013/014) — 변경 대상. - - dcy-004/BAT-006 fill→ledger(REQ-018) — 부분 체결분 기록. - - dcy-005/BAT-008 reconcile_positions(REQ-031) — 잔여 정합 연계. - - dcy-012/BAT-016 — holding 진입 시 limit_price clear(A-1, 재매도 시장가). -- External: - - KIS `inquire-daily-ccld` cncl_yn 필드(R-1, 실 응답 확인은 운영/검증). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) 부분 체결 후 취소 회계(A안)는 FillStatus 계약 확장 + 집행 분기 + 원장 기록 + 슬롯 qty/cost basis 정합까지 교차 — 매도/매수 각각의 qty·avg_price 처리 규칙을 설계에서 못박지 않으면 과매도/이중기록(R-3) 위험. (b) 전량 취소 자동 복구가 기존 is_terminal_reject 롤백 재사용으로 성립하는지(A-2) 설계에서 확인. (c) stale 워치독은 Alert-only 경계(상태 미변경)·임계 env·타임스탬프 출처(slot.updated_at)를 설계에서 고정. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-012 FillStatus.cancelled + KIS cncl_yn 매핑, test_kis_cancel green -2. M2 — REQ-013/018 부분 체결 회계 + 전량 취소 롤백, test_execution_cancel green -3. M3 — REQ-014 stale 워치독 + config/daemon 배선 -4. M4 — REQ-031 잔여 정합 확인 + REQ-015 문서 정합 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High): KIS 실제 취소 표현(cncl_yn 필드/값)이 가정과 다르면 취소를 여전히 accepted 로 오판. 대응: 미지 값 보수 처리 + stale 워치독(FR-3) backstop. 실 응답 확인은 운영/검증 단계. -- R-2 (Medium): 외부 취소→롤백→재매도 반복 시 safety-sell 무한 재매도. 대응: dcy-010(REQ-052) per-slot 쿨다운 확인, 필요 시 cancelled 도 쿨다운 트리거 포함(후속). -- R-3 (Medium): 부분 체결 후 단순 롤백 시 과매도. 대응: A안(체결분 원장 + qty 차감 + 잔여 holding)으로 구조적 차단, cost basis 유지. - -## Notes -- 1차 게이트: 전체 회귀 green. -- KIS 어댑터 단위는 httpx stub(fixture)로 cncl_yn 매핑 검증; 집행 회계는 programmable broker 로 FillStatus 주입 검증. -- 실 KIS 외부 취소 e2e 는 실계좌 세션 필요로 비차단 이월(운영 검증). diff --git a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/verification.md b/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/verification.md deleted file mode 100644 index 868e550..0000000 --- a/docs/batches/bat-017_20260608_external-cancel-detection-stale-watchdog/verification.md +++ /dev/null @@ -1,67 +0,0 @@ -# BAT-017 Verification - -- Status: verified -- Batch: BAT-017 - -## Acceptance Mapping - -### REQ-012 (KIS 취소 감지, change-req) -| AC | Evidence | -|----|----------| -| cncl_yn → cancelled (체결 0) | tests/adapters/broker/test_kis_cancel.py::test_full_external_cancel_maps_to_cancelled | -| 비취소 zero-fill → accepted | ::test_zero_fill_not_cancelled_stays_accepted | -| 부분 체결 + 취소 → is_partial_cancel + execution_id | ::test_partial_fill_then_cancel_flags_partial_cancel | -| 전량 체결 회귀 | ::test_full_fill_unaffected_by_cancel_flag | - -### REQ-013 (집행 부분 체결 회계, change-req) -| AC | Evidence | -|----|----------| -| 전량 외부 취소 → holding 복귀 + clear (영구 갇힘 0) | tests/application/test_execution_cancel.py::test_full_external_cancel_returns_slot_to_holding | -| 부분 취소 → qty 차감 + holding 잔여 + cost basis 유지 | ::test_partial_fill_then_cancel_records_and_keeps_remainder | -| 기존 filled/reject 회귀 | tests/application/test_execution.py 전체 | - -### REQ-018 (부분 체결분 원장 1회, change-req) -| AC | Evidence | -|----|----------| -| 부분 취소 시 sell 원장 1건(qty=체결분, price=체결가) | test_execution_cancel.py::test_partial_fill_then_cancel_records_and_keeps_remainder | - -### REQ-031 (포지션 정합 연계, change-req) -| AC | Evidence | -|----|----------| -| 부분 취소 후 slot.qty = 잔여 held (과매도 0) | test_execution_cancel.py::test_partial_fill_then_cancel_records_and_keeps_remainder (qty==6) | -| reconcile 로직 불변 회귀 | tests/application/test_reconciliation.py 전체 | - -### REQ-014 (pending stale 워치독 Alert-only, change-req) -| AC | Evidence | -|----|----------| -| 임계 초과 pending → WARN + 카운터, 상태 미변경 | test_execution_cancel.py::test_stale_pending_watchdog_warns_without_state_change | -| 비활성(default) → 무경고 | ::test_stale_watchdog_disabled_by_default | - -### REQ-015 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| interface-contract/runtime-flows 정합 | 문서 diff(BAT-017) | - -## Evidence -- Test: - - 단위 전체 회귀: `294 passed / 32 skipped`(신규 KIS 취소 4 + 집행 취소/워치독 4 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `326 passed`. - - `ruff check trex_core tests` → All checks passed. -- Logs: `294 passed, 32 skipped, 1 deselected` / `326 passed, 1 deselected ~15.9s`. - -## 성공 기준(dcy-013) 대응 -- S-1(외부 취소 → cancelled 감지 → holding 복귀, 영구 갇힘 0): test_full_external_cancel_maps_to_cancelled + test_full_external_cancel_returns_slot_to_holding. -- S-2(죽은 주문 추가 폴링 없음): 취소 감지 후 clear_dispatch 로 broker_order_id 비워져 list_dispatched_pending 에서 제외(폴링 중단) — test_full_external_cancel_returns_slot_to_holding(broker_order_id is None). -- S-3(부분 체결분 원장 1회 + 잔여 정합, 과매도/이중기록 0): test_partial_fill_then_cancel_records_and_keeps_remainder(sell 1건 + qty 6 + cost basis 1000). -- S-4(임계 초과 pending stale 가시화, 상태 미변경): test_stale_pending_watchdog_warns_without_state_change. - -## Carried-Over / 비차단 -- R-1(KIS `cncl_yn` 실제 필드/값 규약): `cncl_yn="Y"` 가정으로 구현, 실 응답 확인은 운영/검증 단계. 미지 값 보수 처리 + stale 워치독(FR-3) backstop 으로 무한 갇힘 가시화 안전망 확보. -- 실 KIS 외부 취소 full-stack e2e: 실계좌 세션 필요로 이월(운영 검증, 비차단). -- R-2(외부 취소→재매도 반복 루프): dcy-010 per-slot 쿨다운과 연계, cancelled 쿨다운 트리거 포함은 후속. -- 워치독 auto-remediation: 후속 분리(OQ-2 Alert-only 결정). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 전량 취소 자동 복구·부분 체결 회계·stale 워치독 모두 단위 green(326 passed 회귀). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관). KIS cncl_yn 실 규약은 운영 검증 비차단(보수 처리 + 워치독 backstop). diff --git a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/design.md b/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/design.md deleted file mode 100644 index cf3252c..0000000 --- a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-018 Design - -- Status: confirmed -- Batch: BAT-018 - -## 설계 목표 -- `trading_date` 를 설정 타임존(기본 KST) 기준으로 분리해 KST 캘린더 일자와 DB 일자를 일치시키고, 재시작 없이 일자 경계마다 다음날 슬롯을 자동 생성한다(issue #14). timestamp 는 UTC 유지(NFR-1). - -## 1. DailyPreallocationScheduler (REQ-005, REQ-030 패턴) -- `application/preallocation_scheduler.py`(신규). 의존: `slot_service`(preallocate_daily_slots), `total_slots`, `tz`(tzinfo), `schedule_time`(time, 기본 00:00), `clock`(주입식, 기본 utcnow). -- `run_once()`: `trading_date = clock().astimezone(tz).date()` → `preallocate_daily_slots(trading_date, total_slots)`(멱등) → `_last_fired_date = trading_date`. 반환 trading_date. -- `_maybe_tick()`: `now_tz = clock().astimezone(tz)`; `target = combine(now_tz.date(), schedule_time, tz)`; `now_tz < target` 면 skip; `_last_fired_date == now_tz.date()` 면 skip(오늘 이미 생성); 아니면 run_once. -- `run_forever(stop, tick_sec=60)`: boot run_once(당일 즉시 확보) 후 `stop.wait(tick_sec)` 루프 + try/except(루프 생존). -- 경계 의미: boot 는 항상 현재 TZ 일자 슬롯 확보(시각 무관). schedule_time 은 **다음날** 경계 fire 시점 게이트. 기본 00:00 → 자정 직후 다음날 생성. - -## 2. 도메인 무침범 (A-3) -- `SlotService.preallocate_daily_slots(trading_date: date, count)` 시그니처/멱등 그대로 재사용. TZ 계산은 스케줄러(호출부)에서만 수행 → REQ-002 도메인 본문 변경 없음. - -## 3. 설정 (REQ-048, TOML 일원화) -- `DaemonInstanceConfig`(TOML, frozen): `trading_timezone: str = "Asia/Seoul"`, `daily_preallocation_time: str = "00:00"`. `from_toml` 이 optional 키 파싱(부재 시 기본). -- `DaemonConfig`: 동일 두 필드(기본 KST/00:00). `from_env` 는 기본값 사용(TOML 일원화 — env var 미도입). `from_config` 가 TOML 값으로 `replace`. -- 운영자는 `--config` TOML 로 명시 지정(FR-4). 신규 설정 표면 아님. - -## 4. daemon 배선 (REQ-005) -- `__init__`: `DailyPreallocationScheduler(slot_service, total_slots, tz=ZoneInfo(config.trading_timezone), schedule_time=_parse_kst_time(config.daily_preallocation_time))`. -- `setup()`: UTC 날짜 `preallocate_daily_slots` 직접 호출 제거 → `preallocation_scheduler.run_once()`(boot, TZ 당일 확보). -- `_preallocation_loop`: `run_forever(stop, tick_sec=60)`. workers 에 `trex-preallocation` 추가(8 스레드). - -## 5. timestamp UTC 유지 (NFR-1) -- 슬롯 `created_at`/`updated_at`/전이 ts 는 기존대로 `datetime.now(tz=timezone.utc)`. 본 변경은 trading_date(date) 계산만 TZ 화. - -## 6. baseline 문서 (REQ-009) -- runtime-flows: 데몬 lifecycle(TOML 키 추가·8 스레드·setup preallocation_scheduler) + `일일 슬롯 preallocation` Flow 신규. -- data-model: trading_date=설정 TZ 기준 / timestamp=UTC 구분. -- config/instance.example.toml: 두 키 주석 문서화. - -## 멱등성/안정성 (NFR-2/3) -- 일자당 1회 fire + preallocate date 멱등 → 중복 tick·다중 인스턴스 중복 생성 0. 재시작 시 boot run_once 가 당일 확보. 무재시작 일자 경계는 _maybe_tick 가 다음날 생성. - -## 테스트 설계 -- `tests/application/test_preallocation_scheduler.py`(고정 clock 주입): KST/UTC 자정 경계 trading_date(S-1), timestamp UTC 유지(S-2), 일자 경계 자동 생성+멱등 재tick(S-3), 설정 TZ 변경(S-4), 트리거 시각 게이팅, idle 슬롯. -- `tests/runtime/test_instance_config.py` 확장: TOML 기본값(KST/00:00) + TOML 값 from_config 전파. diff --git a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/implementation.md b/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/implementation.md deleted file mode 100644 index 2d16f9c..0000000 --- a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/implementation.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-018 Implementation - -- Status: completed -- Batch: BAT-018 - -## 구현 요약 -- dcy-014 trading_date 설정 타임존(KST) 분리 + 일일 슬롯 preallocation(issue #14)을 전부 change-req 로 구현(신규 REQ 없음; 스케줄러는 REQ-030 패턴 준용). - -## 변경 내역 - -### REQ-005 — 스케줄러 + 부트스트랩 -- `trex_core/application/preallocation_scheduler.py`(신규): `DailyPreallocationScheduler` — `run_once()`(TZ trading_date 멱등 preallocate), `_maybe_tick()`(TZ 일자당 1회 + 트리거 시각 게이트), `run_forever(stop, tick_sec)`. clock 주입식(테스트 결정론). -- `trex_core/runtime/daemon.py`: `DailyPreallocationScheduler` 인스턴스화(`tz=ZoneInfo(config.trading_timezone)`, `schedule_time=_parse_kst_time(config.daily_preallocation_time)`); `setup()` 의 UTC 날짜 직접 preallocation → `preallocation_scheduler.run_once()`; `_preallocation_loop` + `trex-preallocation` 워커(8 스레드); `zoneinfo.ZoneInfo` import. 미사용 `datetime, timezone` import 제거. - -### REQ-048 — TOML 설정 -- `trex_core/runtime/config.py`: `DaemonInstanceConfig` 에 `trading_timezone="Asia/Seoul"`/`daily_preallocation_time="00:00"` + `from_toml` optional 파싱; `DaemonConfig` 동일 필드(기본값); `from_config` 가 TOML 값 전파(`replace`). `from_env` 는 기본값. -- `config/instance.example.toml`: 두 키 주석 문서화. - -### REQ-009 — baseline 문서 -- `docs/runtime-flows.md`: 데몬 lifecycle TOML 키·8 스레드·setup preallocation_scheduler + `일일 슬롯 preallocation` Flow 신규. Last Updated → BAT-018. -- `docs/data-model.md`: trading_date=설정 TZ 기준 / timestamp=UTC 구분. Last Updated → BAT-018. - -## 테스트 -- 신규 `tests/application/test_preallocation_scheduler.py`(고정 clock): 6건 — KST/UTC 자정 경계 trading_date(S-1), timestamp UTC 유지(S-2), 일자 경계 자동 생성+멱등 재tick(S-3), 설정 TZ 변경(S-4), 트리거 시각 게이팅, idle 슬롯. -- `tests/runtime/test_instance_config.py` 확장: TOML 기본값(KST/00:00) + TOML 값 from_config 전파 2건. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `302 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `334 passed`. -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/index.md b/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/index.md deleted file mode 100644 index 87ef889..0000000 --- a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-018: trading_date 설정 타임존(KST) 분리 + 일일 슬롯 preallocation (issue #14) - -- Status: released -- Profile: standard -- Source Discovery: dcy-014 -- Included REQ: REQ-005 (CHG-20260608-01), REQ-048 (CHG-20260608-01), REQ-009 (CHG-20260608-01) - -## Summary -- dcy-014(당일 슬롯 생성 기준 시간을 KST(설정 가능)로 분리 — trading_date 타임존 분리 + 일자 경계 preallocation, issue #14)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-014 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**(스케줄러는 REQ-030 SnapshotScheduler 패턴 준용). -- 핵심: `trading_date` 를 시스템 UTC 가 아니라 설정 타임존(기본 `Asia/Seoul`) 기준으로 계산하고, timestamp 는 UTC 유지. 데몬 시작 1회 UTC preallocation 을 `DailyPreallocationScheduler`(tick 루프 + TZ 일자당 1회 멱등 fire)로 대체해 재시작 없이 다음날 슬롯이 자동 생성된다. - -## Scope -- In Scope: - - `trading_date` 의 설정 가능한 타임존 분리(기본 KST) + timestamp UTC 유지 (REQ-005) - - TZ 일자 경계 기준 일일 멱등 preallocation 스케줄러 + daemon 배선 (REQ-005) - - 타임존·트리거 시각 TOML 설정(`trading_timezone`/`daily_preallocation_time`) (REQ-048) - - runtime-flows/data-model 문서 갱신 (REQ-009) -- Out of Scope: - - timestamp 저장 형식 변경(UTC 유지) - - 슬롯 용량/total_slots 정책 변경 - - 거래일 캘린더(주말·공휴일 비거래일 제외) — 매 캘린더일 생성으로 확정(OQ-2), 캘린더 도입은 후속 분리 - - REQ-002(slot 도메인) 본문 변경 — `trading_date: date` 시그니처 유지, TZ 계산은 호출부(스케줄러)에서 수행(A-3) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-014). OQ-1(기본 00:00 TZ 자정·TOML 설정)·OQ-2(매 캘린더일 생성)·OQ-3(전부 change-req) Answered. -- 스케줄러는 `SnapshotScheduler`(REQ-030)의 KST 일자당 1회 fire + tick 루프 패턴을 준용(A-1). -- 멱등성(A-2): `preallocate_daily_slots` 가 date 멱등 → 일일 재실행·다중 인스턴스·중복 tick 안전(NFR-2). -- 운영 설정은 기존 `--config` TOML(REQ-048)에 키 추가로 편입(신규 설정 표면 아님). from_env 는 KST/00:00 기본. diff --git a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/planning.md b/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/planning.md deleted file mode 100644 index 3f9a964..0000000 --- a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-018 Planning - -- Status: confirmed -- Batch: BAT-018 -- Profile: standard - -## Included REQ -- REQ-005 (Installation, change-req): setup() UTC→TZ trading_date + `DailyPreallocationScheduler` 신규 + daemon 스레드 배선. -- REQ-048 (Configuration, change-req): TOML `trading_timezone`/`daily_preallocation_time` 키 + from_config 전파. -- REQ-009 (Documentation, change-req): runtime-flows/data-model 정합. - -## Delivery Plan -1. REQ-005: `application/preallocation_scheduler.py`(SnapshotScheduler 패턴, TZ date + 일자당 1회 멱등 fire + boot run_once). -2. REQ-048: `config.py` DaemonInstanceConfig TOML 키 2종 + DaemonConfig 필드 + from_config 전파(+ from_env KST/00:00 기본). -3. REQ-005 배선: `daemon.py` setup() → `preallocation_scheduler.run_once()`, `_preallocation_loop` 스레드 + workers 등록, ZoneInfo(tz). -4. REQ-009: runtime-flows(데몬 lifecycle + 일일 preallocation Flow)/data-model(trading_date TZ vs UTC) + instance.example.toml. -5. 신규 테스트(test_preallocation_scheduler, test_instance_config 확장) + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-002/BAT-002 daemon 부트스트랩(REQ-005) — preallocation 지점. - - dcy-005/BAT-008 SnapshotScheduler(REQ-030) — 스케줄러 패턴 준용. - - dcy-009/BAT-013 DaemonInstanceConfig TOML(REQ-048) — 설정 키 추가. - - REQ-002 preallocate_daily_slots — date 멱등(재사용, 본문 변경 없음). -- External: - - stdlib `zoneinfo`(py3.12) — IANA 타임존. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) trading_date TZ 분리는 daemon 부트스트랩·스케줄러·설정·도메인 호출부 경계에 걸쳐, "TZ 는 호출부에서, 도메인 시그니처는 date 유지"(A-3) 경계를 설계에서 못박지 않으면 도메인 침범/회귀 위험. (b) 일자 경계 fire/멱등/트리거 시각 게이팅 규칙(SnapshotScheduler 준용)을 설계에서 고정해야 중복 생성·누락(NFR-2/3)을 막는다. (c) 설정 표면(TOML 일원화 + from_env 기본)·timestamp UTC 유지(NFR-1) 경계 명시. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-005 DailyPreallocationScheduler, test_preallocation_scheduler green -2. M2 — REQ-048 TOML 키 + from_config 전파, test_instance_config 확장 green -3. M3 — REQ-005 daemon 배선(setup + 스레드) -4. M4 — REQ-009 문서 정합 + instance.example.toml -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (Low, OQ-1): 트리거 시점 운영 기대 불일치. 대응: 기본 00:00 + TOML 설정, SnapshotScheduler 패턴 재사용. -- R-2 (Medium): TZ 전환 경계에서 기존 UTC 날짜 슬롯과 새 TZ trading_date 불일치. 대응: 멱등 생성으로 중복 방지 + 경계 케이스 테스트(KST/UTC 경계). -- R-3 (Low, OQ-2 수용): 비거래일 idle 슬롯 누적. 대응: 매 캘린더일 생성 수용, idle 은 buy admit 시에만 활성화로 무해. 거래일 캘린더 후속. - -## Notes -- 1차 게이트: 전체 회귀 green(부트스트랩 변경 무회귀). -- 경계 검증은 고정 clock 주입 단위로 결정론 확보(스레드 불필요). -- 멱등 preallocation 은 PG 게이트 전이/마이그레이션 테스트 회귀로도 간접 검증. diff --git a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/verification.md b/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/verification.md deleted file mode 100644 index f329868..0000000 --- a/docs/batches/bat-018_20260608_trading-date-timezone-daily-preallocation/verification.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-018 Verification - -- Status: verified -- Batch: BAT-018 - -## Acceptance Mapping - -### REQ-005 (스케줄러 + 부트스트랩, change-req) -| AC | Evidence | -|----|----------| -| trading_date = 설정 TZ 기준(UTC 아님) | tests/application/test_preallocation_scheduler.py::test_trading_date_uses_kst_not_utc_at_midnight_boundary | -| 일자 경계 자동 생성(무재시작) + 멱등 | ::test_cross_day_boundary_auto_creates_next_day_idempotently | -| 트리거 시각 게이팅 | ::test_trigger_time_gates_next_day_creation | -| idle 슬롯 생성 | ::test_idle_slots_created_for_trading_date | -| 부트스트랩/스레드 배선 회귀 | 전체 회귀(daemon import/setup) | - -### REQ-048 (TOML 설정, change-req) -| AC | Evidence | -|----|----------| -| TOML 기본값(KST/00:00) | tests/runtime/test_instance_config.py::test_trading_timezone_defaults_to_kst | -| TOML 값 from_config 전파 | ::test_trading_timezone_from_toml_propagates | -| 기존 TOML 키 회귀 | 기존 test_instance_config 전체 | - -### REQ-009 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| runtime-flows/data-model 정합 | 문서 diff(BAT-018) + instance.example.toml | - -## Evidence -- Test: - - 단위 전체 회귀: `302 passed / 32 skipped`(신규 스케줄러 6 + config 2 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `334 passed`. - - `ruff check trex_core tests` → All checks passed. -- Logs: `302 passed, 32 skipped, 1 deselected` / `334 passed, 1 deselected ~16.1s`. - -## 성공 기준(dcy-014) 대응 -- S-1(설정 TZ "오늘" = DB trading_date, UTC 자정 경계 포함): test_trading_date_uses_kst_not_utc_at_midnight_boundary(15:30Z → KST 6/9). -- S-2(timestamp UTC 유지): test_timestamps_stay_utc(updated_at utcoffset 0). -- S-3(무재시작 일자 경계 자동 생성 + 중복 0): test_cross_day_boundary_auto_creates_next_day_idempotently. -- S-4(TOML 타임존 설정 반영): test_configurable_timezone_changes_trading_date(UTC TZ → 6/8) + test_trading_timezone_from_toml_propagates. - -## Carried-Over / 비차단 -- 기본 타임존 `Asia/Seoul` 확정·TOML 키 명명(DATA): 구현에서 `trading_timezone`/`daily_preallocation_time` 로 확정. 운영 배포 안내는 instance.example.toml 에 문서화. -- 실 다중 인스턴스 동시 일일 preallocation 무중복(NFR-2): 멱등 preallocate 로 보장, 실 환경 동시 기동 e2e 는 운영 검증 비차단. -- 거래일 캘린더(주말/공휴일): OQ-2 결정으로 매 캘린더일 생성, 후속 분리. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 부트스트랩 TZ 전환·일일 preallocation·TOML 설정 모두 단위 green(334 passed 회귀). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/design.md b/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/design.md deleted file mode 100644 index adcda8d..0000000 --- a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/design.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-019 Design - -- Status: confirmed -- Batch: BAT-019 - -## 설계 목표 -- portfolio-hub 404(미등록 포트폴리오)의 초당 무한 재시도(실측 930회)를 제거하고, 일시 오류는 절제된 백오프로 재시도하되 상한 초과 시 dead-letter 로 종료한다. 터미널 실패는 가시화한다(issue #13). - -## 1. 오류 분류 (REQ-017) -- `LedgerPort` 에 `LedgerPermanentError(LedgerError)` 추가. -- `portfolio_hub.py`: 응답 분류 — 2xx 성공, 409→Conflict(불변), **404/400/422→Permanent**(`_PERMANENT_STATUS` frozenset), 그 외 4xx/5xx/network→Retryable. 408/429 는 permanent 집합에 없으므로 retryable 유지(OQ-1). -- `InMemoryLedger` 에 `permanent_executions` 주입 hook(테스트용). - -## 2. outbox 상태머신 (REQ-019) -- OutboxOutcome 추가: `PERMANENT_FAILURE`, `DEAD_LETTERED`. 둘 다 WorkItemStatus 신규 `DELIVERY_FAILED`(단일 터미널, 사유는 last_outcome). `_DELIVERY_FAILED_OUTCOMES` frozenset. -- `LedgerWorkItem.next_retry_at: Optional[datetime]` 추가. -- `LedgerWriter._process`: - - `LedgerPermanentError` → `mark_outcome(PERMANENT_FAILURE)` + WARN → 터미널(재시도 0). - - `LedgerRetryableError` → `new_attempts = item.attempts+1`; `new_attempts >= max_attempts` 면 `DEAD_LETTERED`+WARN(터미널); 아니면 `next_retry_at = clock() + backoff(new_attempts)` 로 `RETRYABLE_FAILURE`(pending). - - conflict/완료/중복 경로 불변. -- `_backoff_delay(n) = min(base*2^(n-1), cap) * (1 + 0.25*rng())`. clock/rng 주입(결정론). -- `mark_outcome(..., next_retry_at=None)` 시그니처 확장 — retryable 일 때만 저장, 그 외 None. - -## 3. list_pending 백오프 스킵 (REQ-019, NFR-3) -- InMemory: `status==PENDING and (next_retry_at is None or next_retry_at <= now)`. now 는 outbox 의 주입 clock(writer 와 공유해 결정론). -- PG: `WHERE status='pending' AND (next_retry_at IS NULL OR next_retry_at <= now())`. 부분 인덱스 `idx_ledger_outbox_pending_retry`. - -## 4. 마이그레이션 007 (REQ-019) -- `ADD COLUMN IF NOT EXISTS next_retry_at TIMESTAMPTZ`. -- status CHECK: DROP/ADD constraint 로 `delivery_failed` 추가(idempotent). last_outcome CHECK: permanent_failure/dead_lettered 추가. -- 글롭 재적용 모델 → DROP CONSTRAINT IF EXISTS + ADD 로 재실행 안전. - -## 5. 가시화 (REQ-018, NFR-2) -- `WriterStats.delivery_failed` 카운터. WARN 로그(permanent: reason / dead-letter: attempts+reason). status `delivery_failed` 로 pending 과 구분. -- dcy-011 `ledger_skipped` 패턴 정합; dead-letter 누락은 REQ-055 `find_ledger_gap_slots` 와 연계. - -## 6. 설정 (REQ-019) -- `DaemonConfig`: `ledger_retry_base_sec`(1.0)/`ledger_retry_cap_sec`(300.0)/`ledger_max_attempts`(8). env `TREX_LEDGER_RETRY_BASE_SEC`/`_CAP_SEC`/`TREX_LEDGER_MAX_ATTEMPTS`. daemon 이 LedgerWriter 에 주입. - -## 7. baseline 문서 (REQ-020) -- runtime-flows: ledger writer permanent/backoff/dead-letter + list_pending 스킵 + env. -- data-model: ledger_outbox status `delivery_failed`·`next_retry_at`·last_outcome 확장. -- interface-contract: LedgerPermanentError 오류 계약. - -## 정합/멱등 (NFR-4) -- 단일 터미널 패턴(A-3): 409 conflict_blocked 와 동형 → outbox 처리 구조 최소 변경. correlation_id dedup(REQ-019) 불변. - -## 테스트 설계 -- adapter: 404/400/422→permanent(param), 429→retryable, 409→conflict, 5xx/network→retryable. -- outbox(programmable LedgerPort + 주입 clock/rng): permanent 즉시 터미널·재시도 0, retryable 백오프 스킵→due 후 재시도, max_attempts dead-letter, WARN 가시화, 백오프 행이 신규 행 비잠식. -- PG 게이트: delivery_failed permanent 영속, 백오프 행 list_pending 스킵, 기존 retryable(base=0 즉시 due) 정합. -- 회귀: 기존 retryable 테스트(test_ledger_outbox/test_execution_ledger)에 공유 clock 주입으로 백오프 정합. diff --git a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/implementation.md b/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/implementation.md deleted file mode 100644 index 87b0ac8..0000000 --- a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/implementation.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-019 Implementation - -- Status: completed -- Batch: BAT-019 - -## 구현 요약 -- dcy-015 ledger outbox 영속 오류 분류 + 백오프/dead-letter + 가시화(issue #13)를 전부 change-req 로 구현(신규 REQ 없음; 백오프 env 는 REQ-019 흡수). - -## 변경 내역 - -### REQ-017 — 오류 분류 -- `trex_core/application/ledger_port.py`: `LedgerPermanentError(LedgerError)` 추가. -- `trex_core/adapters/ledger/portfolio_hub.py`: `_PERMANENT_STATUS={400,404,422}` → `LedgerPermanentError`; 409 conflict·408/429/5xx/network retryable 불변. 헤더 계약 갱신. -- `trex_core/adapters/ledger/in_memory.py`: `permanent_executions` 주입 hook. - -### REQ-019 — outbox 백오프/dead-letter -- `trex_core/application/ledger_outbox.py`: - - OutboxOutcome `PERMANENT_FAILURE`/`DEAD_LETTERED`; WorkItemStatus `DELIVERY_FAILED`(단일 터미널); `_DELIVERY_FAILED_OUTCOMES`. - - `LedgerWorkItem.next_retry_at`. - - `LedgerWriter(retry_base_sec/retry_cap_sec/max_attempts/clock/rng)` + `_backoff_delay` + `_process`(permanent→터미널, retryable→백오프/dead-letter). - - `mark_outcome(..., next_retry_at=...)`; `WriterStats.delivery_failed`. - - InMemory outbox: `clock` 주입, `list_pending` 백오프 스킵, mark_outcome 신규 outcome/next_retry_at. -- `trex_core/adapters/persistence/postgresql.py`: `_OUTBOX_COLUMNS` next_retry_at; `list_pending` `next_retry_at IS NULL OR <= now()` 스킵; `mark_outcome` permanent/dead-letter→delivery_failed + next_retry_at; `_row_to_item` next_retry_at. -- `trex_core/adapters/persistence/sql/007_ledger_outbox_delivery_failed_and_backoff.sql`(신규): next_retry_at 컬럼 + status/last_outcome CHECK 확장(idempotent) + 부분 인덱스. -- `trex_core/runtime/config.py`: `ledger_retry_base_sec`/`_cap_sec`/`ledger_max_attempts` + env. `trex_core/runtime/daemon.py`: LedgerWriter 에 주입. - -### REQ-018 — 가시화 -- `ledger_outbox.py` writer: permanent/dead-letter WARN 로그 + `WriterStats.delivery_failed` 카운터, status `delivery_failed` 구분. - -### REQ-020 — baseline 문서 -- `docs/runtime-flows.md`(ledger writer permanent/backoff/dead-letter + list_pending 스킵 + env), `docs/data-model.md`(ledger_outbox status/next_retry_at/last_outcome), `docs/interface-contract.md`(LedgerPermanentError). Last Updated → BAT-019. - -## 테스트 -- 신규 `tests/adapters/ledger/test_portfolio_hub_ledger.py` 확장: 404/400/422 permanent(param) + 429 retryable. -- 신규 `tests/application/test_ledger_outbox_backoff.py`: permanent 즉시 터미널·재시도 0, retryable 백오프 스킵→due 재시도, max_attempts dead-letter, WARN 가시화, 백오프 행 비잠식(5건). -- 신규 PG 게이트 `tests/adapters/persistence/test_postgresql_ledger_outbox.py` 확장: delivery_failed permanent 영속, 백오프 list_pending 스킵, 기존 retry(base=0) 정합. -- 회귀 정합: `test_ledger_outbox.py`/`test_execution_ledger.py` 의 retryable 테스트에 공유 clock 주입(백오프 advance). - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `311 passed / 34 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `345 passed`(마이그레이션 007 + delivery_failed/backoff PG round-trip 포함). -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/index.md b/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/index.md deleted file mode 100644 index 2dda74b..0000000 --- a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-019: ledger outbox 영속 오류 분류 + 백오프/dead-letter + 가시화 (issue #13) - -- Status: released -- Profile: standard -- Source Discovery: dcy-015 -- Included REQ: REQ-017 (CHG-20260608-01), REQ-019 (CHG-20260608-01), REQ-018 (CHG-20260608-03), REQ-020 (CHG-20260608-01) - -## Summary -- dcy-015(portfolio-hub 404 가 retryable 로 분류되어 백오프·상한 없이 영구 재시도, issue #13)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-015 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**(백오프 env 는 REQ-019 에 흡수). -- 핵심: portfolio-hub 어댑터가 **404/400/422 → `LedgerPermanentError`** 로 분류하고, outbox 가 이를 터미널 `delivery_failed` 로 즉시 처리(재시도 0). 일시 오류는 **지수 백오프+jitter+max_attempts** 로 재시도하되 상한 초과 시 `delivery_failed`(dead-letter). `list_pending` 이 `next_retry_at` 미도래 행을 건너뛰어 FIFO head-of-line 잠식을 막고, 터미널 실패는 WARN+카운터로 가시화한다. - -## Scope -- In Scope: - - 영속 오류 분류 분리(404/400/422 → `LedgerPermanentError`) (REQ-017) - - retryable 지수 백오프+jitter + `next_retry_at` + max_attempts + 단일 터미널 `delivery_failed`(permanent/dead-letter, 사유 구분) + `list_pending` 백오프 스킵 + status enum/컬럼 마이그레이션(007) + 백오프 env (REQ-019) - - 터미널/dead-letter WARN+메트릭 가시화 (REQ-018) - - runtime-flows/data-model/interface-contract 정합 (REQ-020) -- Out of Scope: - - safety-sell→KIS 영속 거부 backoff(dcy-010/REQ-052, 경로 다름) - - portfolio_id 누락 ledger skip(dcy-011/#11) - - dead-letter 행 재처리 트리거(운영 명령/자동 재처리) — 재처리 가능한 상태 제공까지, 트리거는 후속 분리 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-015). OQ-1(404/400/422 영속)·OQ-2(지수 백오프+next_retry_at·max-attempts env·단일 `delivery_failed`)·OQ-3(전부 change-req) Answered. -- 단일 터미널 패턴(A-3): 409 `conflict_blocked` 와 동일하게 `delivery_failed` 단일 status + last_outcome(permanent_failure/dead_lettered)로 사유 구분 → 스키마 최소 변경. -- 가시화(A-1): dcy-011 `ledger_skipped` 카운터·`find_ledger_gap_slots`(REQ-055) 패턴 정합. -- dead-letter 는 폐기 아님 — 운영자 재처리 가능 상태(R-3 완화). -- 마이그레이션 007 은 글롭 재적용 모델 → ADD COLUMN IF NOT EXISTS + DROP/ADD CONSTRAINT 로 idempotent. diff --git a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/planning.md b/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/planning.md deleted file mode 100644 index aeff489..0000000 --- a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-019 Planning - -- Status: confirmed -- Batch: BAT-019 -- Profile: standard - -## Included REQ -- REQ-017 (Integration, change-req): 404/400/422 → `LedgerPermanentError` 분류 + LedgerPort 타입. -- REQ-019 (Exception, change-req): 백오프+jitter+`next_retry_at`+max_attempts+`delivery_failed`+마이그레이션 007+`list_pending` 스킵+env. -- REQ-018 (Integration, change-req): 터미널/dead-letter WARN+카운터 가시화. -- REQ-020 (Documentation, change-req): runtime-flows/data-model/interface-contract 정합. - -## Delivery Plan -1. REQ-017: `ledger_port.py` `LedgerPermanentError`; `portfolio_hub.py` 404/400/422 매핑 + 헤더; `in_memory.py` permanent 주입 hook. -2. REQ-019: `ledger_outbox.py` OutboxOutcome(permanent_failure/dead_lettered)+WorkItemStatus(delivery_failed)+`next_retry_at`+LedgerWriter 백오프/dead-letter+`list_pending` 스킵+InMemory clock; `postgresql.py` next_retry_at/status/list_pending skip/mark_outcome; `sql/007_*.sql`; `config.py` env + daemon 배선. -3. REQ-018: writer WARN + `WriterStats.delivery_failed`. -4. REQ-020: 문서 3종 정합. -5. 테스트(adapter classify, outbox backoff/dead-letter/permanent/visibility, PG delivery_failed/skip, 기존 retryable 백오프 정합 migration) + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-004/BAT-006 LedgerPort/outbox/writer(REQ-017/018/019) — 변경 대상. - - dcy-011/BAT-015 ledger skip 가시화·REQ-055 — 가시화 패턴 정합(A-1). - - dcy-010/BAT-014 백오프 env 관례(A-2, 경로 다름). -- External: - - PostgreSQL(status enum/next_retry_at 마이그레이션). portfolio-hub 4xx/5xx 규약(R-1, 운영 확인). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) 오류 분류 분리 + outbox 상태머신 확장(영속/백오프/dead-letter)이 LedgerPort·어댑터·outbox·PG·마이그레이션에 교차 — 단일 터미널 `delivery_failed`(사유 구분) 경계와 `next_retry_at` 스킵 규칙을 설계에서 못박지 않으면 상태 정합(R-2)·과대 분류(R-1) 위험. (b) 백오프 공식·max_attempts 전이·clock 주입(결정론 테스트) 경계 확정. (c) status enum CHECK 마이그레이션 idempotency(글롭 재적용) 명시. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-017 분류 + LedgerPermanentError, adapter 테스트 green -2. M2 — REQ-019 outbox 백오프/dead-letter/next_retry_at + InMemory, backoff 테스트 green -3. M3 — REQ-019 PG next_retry_at/status/skip + 마이그레이션 007, PG 게이트 green -4. M4 — REQ-018 가시화 + REQ-020 문서 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (Medium): 영속 분류 과대 → 정상 거래 ledger 영구 실패. 대응: 404/400/422 한정(OQ-1) + dcy-011 admission portfolio_id 검증으로 등록-지연 404 감소 + dead-letter 가 운영자 재처리 가능 상태. -- R-2 (Medium): status enum 마이그레이션이 기존 conflict_blocked/pending/completed 와 부정합. 대응: 기존 터미널 패턴 그대로 따르는 단일 새 터미널 + 마이그레이션/전이 테스트. -- R-3 (Medium): dead-letter 단순 포기 시 ledger 영구 누락 고착. 대응: 운영자 재처리 가능 상태 + REQ-055 find_ledger_gap_slots·skip 가시화 연계. - -## Notes -- 1차 게이트: 전체 회귀 green(백오프 도입이 기존 retryable 테스트에 미치는 영향 정합). -- 결정론: LedgerWriter/InMemory outbox 에 clock 주입 + rng 주입(jitter)로 백오프 테스트 결정화. -- PG list_pending 백오프 스킵은 인프라 게이트로 검증. diff --git a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/verification.md b/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/verification.md deleted file mode 100644 index ae02931..0000000 --- a/docs/batches/bat-019_20260608_ledger-outbox-permanent-error-backoff/verification.md +++ /dev/null @@ -1,57 +0,0 @@ -# BAT-019 Verification - -- Status: verified -- Batch: BAT-019 - -## Acceptance Mapping - -### REQ-017 (오류 분류, change-req) -| AC | Evidence | -|----|----------| -| 404/400/422 → LedgerPermanentError | tests/adapters/ledger/test_portfolio_hub_ledger.py::test_client_errors_are_permanent | -| 429 → retryable (영속 아님) | ::test_429_stays_retryable | -| 409 → conflict / 5xx·network → retryable 회귀 | ::test_409_surfaces_as_ledger_conflict_error / ::test_5xx_is_retryable / ::test_network_error_is_retryable | - -### REQ-019 (백오프/dead-letter, change-req) -| AC | Evidence | -|----|----------| -| 영속 → delivery_failed 즉시(재시도 0) | tests/application/test_ledger_outbox_backoff.py::test_permanent_error_terminates_immediately_no_retry | -| retryable 백오프 + next_retry_at 스킵 | ::test_retryable_backoff_skips_until_next_retry_at | -| max_attempts → dead-letter delivery_failed | ::test_retryable_dead_letters_after_max_attempts | -| 백오프 행 FIFO 비잠식 | ::test_backoff_row_does_not_block_new_rows | -| PG delivery_failed 영속 + 마이그레이션 007 | tests/adapters/persistence/test_postgresql_ledger_outbox.py::test_writer_marks_delivery_failed_permanent_on_pg | -| PG 백오프 list_pending 스킵 | ::test_backoff_row_skipped_by_pending_scan_on_pg | -| 기존 retryable 정합(백오프) | test_ledger_outbox.py::test_writer_retries_until_success / test_execution_ledger.py::test_retryable_failure_then_eventually_completes / PG::test_writer_retries_on_pg | - -### REQ-018 (가시화, change-req) -| AC | Evidence | -|----|----------| -| 터미널 delivery_failed WARN + 카운터 | test_ledger_outbox_backoff.py::test_permanent_failure_emits_warning (+ WriterStats.delivery_failed 단언 전반) | - -### REQ-020 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| runtime-flows/data-model/interface-contract 정합 | 문서 diff(BAT-019) | - -## Evidence -- Test: - - 단위 전체 회귀: `311 passed / 34 skipped`(신규 backoff 5 + adapter 4 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `345 passed`(마이그레이션 007 적용 + delivery_failed/backoff PG round-trip 포함). - - `ruff check trex_core tests` → All checks passed. -- Logs: `311 passed, 34 skipped, 1 deselected` / `345 passed, 1 deselected ~17.4s`. - -## 성공 기준(dcy-015) 대응 -- S-1(404 즉시 터미널, attempts 폭증 없음): test_permanent_error_terminates_immediately_no_retry(ledger.calls==1) + PG::test_writer_marks_delivery_failed_permanent_on_pg. -- S-2(일시 오류 백오프 + max_attempts dead-letter, 무한 1초 재시도 0): test_retryable_backoff_skips_until_next_retry_at + test_retryable_dead_letters_after_max_attempts. -- S-3(터미널/dead-letter WARN·구분 status 가시화): test_permanent_failure_emits_warning + delivery_failed status 단언. -- S-4(백오프/영속 행 FIFO 비잠식): test_backoff_row_does_not_block_new_rows + PG::test_backoff_row_skipped_by_pending_scan_on_pg. - -## Carried-Over / 비차단 -- R-1(영속 분류 과대): 404/400/422 한정 + dcy-011 admission portfolio_id 검증으로 등록-지연 404 감소 + dead-letter 운영자 재처리 가능. portfolio-hub 실 4xx/5xx 규약(DATA)은 운영 검증 비차단. -- dead-letter 재처리 트리거(운영 명령/자동화)는 Out of Scope(후속 분리) — 본 주기는 재처리 가능한 `delivery_failed` 상태 제공까지. -- 실 portfolio-hub full-stack(미등록 portfolio 404) e2e 는 운영 검증 비차단(InMemory permanent 주입 + adapter status 매핑으로 단위 검증). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 영속 분류·백오프·dead-letter·가시화 모두 단위 green + PG 마이그레이션 round-trip(345 passed). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/design.md b/docs/batches/bat-020_20260608_operator-cli-rpc-commands/design.md deleted file mode 100644 index fb12bf8..0000000 --- a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/design.md +++ /dev/null @@ -1,45 +0,0 @@ -# BAT-020 Design - -- Status: confirmed -- Batch: BAT-020 - -## 설계 목표 -- 운영자가 CLI 로 조회·매수·매도·일시정지를 수행하되, 매매는 데몬 admission(단일 writer)을 거쳐 불변식을 보존한다. 운영자 조회가 전략 생존 신호로 오인되지 않게 한다(R-4). - -## 1. 워치독 억제 (REQ-021/034, 서버) -- `make_snapshot_rpc_handler`: `touch_watchdog = bool(body.get("touch_watchdog", True))`; `actor=="operator"` 면 강제 false. `watchdog_store and touch_watchdog` 일 때만 holding 슬롯 `last_seen_at` 갱신. -- 기본값(플래그 미지정) = 현행(전략 폴링=갱신) → 전략 모듈 무변경(하위호환). 운영자 CLI 는 항상 억제. - -## 2. CLI 구조 (REQ-032/022) -- `main(argv, *, rpc=None, confirm=None)`: `rpc(queue, payload, *, correlation_id, timeout)->dict`, `confirm(prompt)->bool` 주입 가능(테스트 broker-free). 미주입 시 config 기반 transport(`call_rpc`) + 대화형 confirm. -- 인스턴스 라우팅 `_resolve_instance`: `--instance` > `--config`(TOML instance_key) > `default`(OQ-1). -- 큐: `topology.snapshot_queue(instance)`/`intent_queue(instance)`. -- pause/resume/paused: 기존 `PostgreSQLPauseStore` direct-DB 유지(OQ-3). - -## 3. 명령별 매핑 -- `universe`: snapshot RPC `{view:"all", actor:"operator", touch_watchdog:false, trading_date}` → assets 출력. -- `slots [--status held|buyable|all]`: snapshot RPC(억제) → assets; `holding`→`held` view 매핑. -- `enter [--capital/--qty/--entry-price/--strategy-id/--stop-loss/--take-profit]`: buy intent RPC(portfolio_id 필수, symbol 서버 resolve; symbol 미전송). -- `exit [--price/--reason]`: sell intent RPC(slot_id + optional limit_price; 미지정 시 시장가). -- trading_date 는 설정 타임존(dcy-014) 기준 today. - -## 4. enter/exit 안전장치 (REQ-032, OQ-2) — `_intent_guard` -- `--dry-run`: resolved intent + 대상 인스턴스만 출력, 미제출(exit 0). -- live(`broker=kis`+allow_live) + `--live` 없음 → 에러(exit 2), 미제출. -- `--yes` 없음: `--json`(비대화형)이면 에러(exit 2); 아니면 confirm 프롬프트(기본 No) — 거부 시 미제출(exit 1). -- 통과 시 intent RPC 제출. admission 응답(`status/slot_id/reason`) 표시, `admitted` 아니면 exit 1. -- `_broker_mode(cfg)` → (broker, is_live). is_live = kis + allow_live. - -## 5. 출력 (REQ-032) -- 사람용: 요약/표. `--json`: RPC 응답을 그대로 JSON(`json.dumps`). 실패: stderr 에 error(json 모드는 `{"error":...}`), exit 3. - -## 6. baseline 문서 (REQ-026) -- interface-contract: 운영자 CLI↔RPC 매핑 표 + snapshot 요청 `touch_watchdog`/`actor` 플래그. -- runtime-flows: 운영자 CLI flow(라우팅/억제/안전장치/실패). - -## 범위 밖 정합 -- snapshot envelope 은 asset-centric(REQ-028 strict no-price) — slot_id 미포함. `exit` 는 운영자 제공 slot_id 를 intent RPC 로 전달(snapshot 에 slot_id 노출은 별도 계약 변경, 본 주기 밖). `slots` 는 보유 자산 상황 인지용. - -## 테스트 설계 -- `tests/runtime/test_cli_operator.py`(주입 rpc recorder/confirm): universe 억제 플래그/큐, slots held view, 인스턴스 라우팅, `--json` 유효성, enter 확인 거부 미제출/`--yes` 제출/rejected 비0/`--dry-run`/`--json` 요구 `--yes`, exit 가격/시장가, live 이중 ack(gate), RPC 실패 표시. -- `tests/application/test_messaging_snapshot_consumer.py`: 기본 요청 watchdog touch / operator 요청 suppress. diff --git a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/implementation.md b/docs/batches/bat-020_20260608_operator-cli-rpc-commands/implementation.md deleted file mode 100644 index d035c4f..0000000 --- a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/implementation.md +++ /dev/null @@ -1,36 +0,0 @@ -# BAT-020 Implementation - -- Status: completed -- Batch: BAT-020 - -## 구현 요약 -- dcy-016 운영자 CLI 확장(universe/slots/enter/exit, 데몬 RPC 통일) + 운영자 조회 워치독 억제를 전부 change-req 로 구현(신규 REQ 없음). - -## 변경 내역 - -### REQ-021/034 — 워치독 억제 (서버) -- `trex_core/application/messaging_handlers.py::make_snapshot_rpc_handler`: `touch_watchdog = bool(body.get("touch_watchdog", True))`; `actor=="operator"` → 강제 false. `watchdog_store and touch_watchdog` 일 때만 holding `last_seen_at` 갱신. 기본값=현행(전략 폴링 갱신, 하위호환). - -### REQ-032/022 — 운영자 CLI -- `trex_core/runtime/cli.py`(재작성): `main(argv, *, rpc=None, confirm=None)` 주입 가능. - - 라우팅 `_resolve_instance`(`--instance` > `--config` instance_key > `default`). - - `universe`/`slots`: snapshot RPC(`actor="operator"`/`touch_watchdog=false`), `holding`→`held`. - - `enter `: buy intent RPC(portfolio_id 필수, symbol 미전송 — 서버 resolve). - - `exit `: sell intent RPC(optional `--price`→limit_price, 미지정 시 시장가). - - 안전장치 `_intent_guard`: `--dry-run`(미제출), live(`broker=kis`+allow_live)는 `--yes --live` 이중 ack, `--yes` 없으면 confirm(기본 No)/`--json` 비대화형은 `--yes` 필수. - - `--json` 출력, RPC 실패는 stderr error + exit 3. admission rejected → exit 1. - - pause/resume/paused: 기존 `PostgreSQLPauseStore` direct-DB 유지(OQ-3). - - transport RPC: config 기반 `RabbitMQTransport`/`InMemoryTransport` `call_rpc` 재사용(REQ-022). - -### REQ-026 — baseline 문서 -- `docs/interface-contract.md`: 운영자 CLI↔RPC 매핑 표 + snapshot 요청 `touch_watchdog`/`actor` 플래그. Last Updated → BAT-020. -- `docs/runtime-flows.md`: 운영자 CLI flow(라우팅/억제/안전장치/실패). Last Updated → BAT-020. - -## 테스트 -- 신규 `tests/runtime/test_cli_operator.py`(주입 rpc/confirm, broker/DB free): 14건 — universe 억제 플래그/큐, slots held view, 인스턴스 라우팅, `--json` 유효성, enter 확인거부 미제출/`--yes` 제출/rejected 비0/`--dry-run`/`--json` 요구 `--yes`, exit 가격/시장가, live 이중 ack(gate+proceed), RPC 실패 표시. -- `tests/application/test_messaging_snapshot_consumer.py` 확장: 기본 요청 watchdog touch / operator 요청 suppress 2건. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `327 passed / 34 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `361 passed`. -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/index.md b/docs/batches/bat-020_20260608_operator-cli-rpc-commands/index.md deleted file mode 100644 index de634eb..0000000 --- a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-020: 운영자 CLI 확장 (universe/slots/enter/exit) + 워치독 억제 (dcy-016) - -- Status: released -- Profile: standard -- Source Discovery: dcy-016 -- Included REQ: REQ-032 (CHG-20260608-01), REQ-022 (CHG-20260608-01), REQ-021 (CHG-20260608-02), REQ-034 (CHG-20260608-01), REQ-026 (CHG-20260608-01) - -## Summary -- dcy-016(운영자 CLI 명령 확장 — universe/슬롯 확인 + enter/exit + pause/resume, 데몬 RPC 통일)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-016 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**. -- 핵심: `trex-core` CLI 에 `universe`/`slots`(snapshot RPC)·`enter`(buy intent RPC)·`exit`(sell intent RPC) 추가. 전부 데몬 RabbitMQ RPC 로 통일해 단일 writer admission·불변식 보존(DB 직접 쓰기 없음). 운영자 조회는 `actor="operator"`/`touch_watchdog=false` 로 워치독 생존 신호를 억제(R-4). enter/exit 안전장치(확인 프롬프트/`--yes`/`--yes --live`/`--dry-run`). pause/resume 은 기존 direct-DB 유지. - -## Scope -- In Scope: - - `universe`/`slots`/`enter`/`exit` CLI(전부 데몬 RPC) + 응답 표시 (REQ-032) - - CLI request-reply RPC 클라이언트 재사용 (REQ-022) - - snapshot 요청 워치독 억제 플래그(`touch_watchdog`/`actor`) (REQ-021) + 핸들러 억제 동작 (REQ-034) - - enter/exit 안전장치(확인/`--yes`/live 이중 ack/`--dry-run`) + 인스턴스 라우팅(`--config`/`--instance`/default) + 전 명령 `--json` - - interface-contract/runtime-flows 정합 (REQ-026) -- Out of Scope: - - 신규 서버측 RPC 표면/도메인 로직(기존 표면 재사용 — 워치독 억제만 snapshot 핸들러 국소 변경) - - buy/sell 계약 자체 변경(dcy-011/dcy-012) - - kill_switch CLI 노출(별도 결정) - - pause/resume 의 control RPC 통일(미채택 — 기존 direct-DB 유지, OQ-3) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-016). OQ-1(`--config`+`--instance`/default)·OQ-2(안전장치 A안)·OQ-3(전부 change-req)·OQ-4(워치독 플래그) Answered. -- snapshot envelope 은 asset-centric(REQ-028 strict no-price) — slot_id 미포함. 따라서 `exit ` 는 운영자 제공 slot_id 를 intent RPC 로 전달(snapshot 에 slot_id 노출은 별도 계약 변경이라 본 주기 밖). `slots` 는 보유 자산(asset_id/symbol/qty) 상황 인지용. -- 테스트 결정론: CLI 는 `rpc`/`confirm` 주입 가능(no broker/DB), snapshot 핸들러는 fake watchdog recorder. diff --git a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/planning.md b/docs/batches/bat-020_20260608_operator-cli-rpc-commands/planning.md deleted file mode 100644 index 4638fea..0000000 --- a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/planning.md +++ /dev/null @@ -1,49 +0,0 @@ -# BAT-020 Planning - -- Status: confirmed -- Batch: BAT-020 -- Profile: standard - -## Included REQ -- REQ-032 (Interface, change-req): CLI universe/slots/enter/exit + 인스턴스 라우팅 + enter/exit 안전장치 + `--json`. -- REQ-022 (Integration, change-req): CLI request-reply RPC 클라이언트(`call_rpc`) 재사용. -- REQ-021 (Integration, change-req): snapshot 요청 워치독 억제 플래그. -- REQ-034 (Interface, change-req): 핸들러가 억제 플래그 시 `last_seen_at` 미갱신. -- REQ-026 (Documentation, change-req): interface-contract/runtime-flows 정합. - -## Delivery Plan -1. REQ-021/034(서버): `messaging_handlers.py::make_snapshot_rpc_handler` 에 `touch_watchdog`/`actor` 억제 분기(기본 현행 유지). -2. REQ-032/022(CLI): `cli.py` 재작성 — pause/resume/paused(direct-DB 유지) + universe/slots/enter/exit(RPC). 인스턴스 라우팅(`_resolve_instance`), RPC 빌드/주입(`rpc`), 안전장치(`_intent_guard`: confirm/`--yes`/`--yes --live`/`--dry-run`), `--json`, 실패 표시. -3. REQ-026(문서): interface-contract(CLI↔RPC 매핑 + 억제 플래그), runtime-flows(운영자 CLI flow). -4. 테스트: CLI(주입 rpc/confirm) + snapshot 핸들러 억제 + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-008/BAT-008 CLI(REQ-032 pause/resume) — 확장 대상. - - dcy-006/BAT-007 transport `call_rpc`(REQ-022) — 재사용. - - dcy-005/BAT-008 snapshot 핸들러/envelope(REQ-028/021) + 워치독(REQ-034) — 억제 대상. - - dcy-011 buy(portfolio_id 필수)·dcy-012 sell(slot_id+optional price) 계약 — CLI payload 근거. - - dcy-009 `_instance_queue`(REQ-049) — 라우팅. -- External: - - RabbitMQ(실 RPC). 단위는 주입 rpc 로 broker-free. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) enter/exit 는 실주문 표면(R-1) — 안전장치(확인/`--yes`/live 이중 ack/`--dry-run`)와 `--json` 비대화형 상호작용 규칙을 설계에서 못박지 않으면 오발주 위험. (b) 워치독 억제는 snapshot 핸들러 서버 동작 변경(REQ-034) — 기본값 현행 유지(하위호환) 경계와 플래그 의미를 설계에서 고정해야 안전 기능(safety-sell) 무력화/회귀를 막는다. (c) 인스턴스 라우팅(`--config`/`--instance`/default) 우선순위와 테스트용 rpc/confirm 주입 구조를 설계에서 명시. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-021/034 snapshot 억제 분기, 핸들러 테스트 green -2. M2 — REQ-032/022 CLI universe/slots/enter/exit + 안전장치, CLI 테스트 green -3. M3 — REQ-026 문서 정합 -4. M4 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High): enter/exit 오발주/실주문. 대응: 확인 프롬프트 기본 ON·`--yes`(paper)·`--yes --live` 이중 ack·`--dry-run`·rejected 표시·대상 인스턴스 항상 표시. admission portfolio_id 검증(dcy-011)이 1차 방어선. -- R-2 (Medium): 잘못된 인스턴스 라우팅. 대응: `--config` canonical + `--instance` override + default + 출력에 대상 인스턴스 표시. -- R-3 (Low): RPC 통일로 데몬 미가동 시 조회 불가. 대응: 의도된 트레이드오프 문서화 + 명확한 에러 표시. -- R-4 (High): 운영자 조회의 워치독 부수효과로 safety-sell 무력화. 대응: 억제 플래그(REQ-021/034), 기본값 현행 유지. - -## Notes -- 1차 게이트: 전체 회귀 green(워치독 기본 동작 무회귀). -- CLI 단위는 broker/DB 없이 rpc/confirm 주입으로 결정론 확보. -- 실 RabbitMQ 왕복 e2e 는 기존 transport 인프라 게이트로 커버(call_rpc 자체). diff --git a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/verification.md b/docs/batches/bat-020_20260608_operator-cli-rpc-commands/verification.md deleted file mode 100644 index 3e37a1c..0000000 --- a/docs/batches/bat-020_20260608_operator-cli-rpc-commands/verification.md +++ /dev/null @@ -1,60 +0,0 @@ -# BAT-020 Verification - -- Status: verified -- Batch: BAT-020 - -## Acceptance Mapping - -### REQ-032 (운영자 CLI, change-req) -| AC | Evidence | -|----|----------| -| universe snapshot RPC 조회(직접 DB 없음) | tests/runtime/test_cli_operator.py::test_universe_uses_snapshot_queue_with_operator_suppression | -| slots view 매핑(holding→held) | ::test_slots_status_holding_maps_to_held_view | -| enter buy intent RPC 제출 | ::test_enter_with_yes_submits_buy_intent | -| exit sell intent RPC 제출(가격/시장가) | ::test_exit_with_yes_submits_sell_with_limit_price / ::test_exit_market_when_no_price | -| enter/exit 안전장치(확인/dry-run/json 요구 yes) | ::test_enter_aborts_without_confirmation_no_rpc / ::test_enter_dry_run_does_not_submit / ::test_enter_json_requires_yes | -| live 이중 ack | ::test_live_requires_live_flag / ::test_live_proceeds_with_yes_live | -| 인스턴스 라우팅 | ::test_instance_shortcut_routes_to_instance_queue | -| rejected 비0 종료 | ::test_enter_rejected_returns_nonzero | - -### REQ-022 (CLI RPC 클라이언트, change-req) -| AC | Evidence | -|----|----------| -| call_rpc 재사용(주입 가능) | test_cli_operator.py 전반(주입 rpc recorder) | -| RPC 실패 명확 표시 | ::test_rpc_failure_surfaces_error | - -### REQ-021/034 (워치독 억제, change-req) -| AC | Evidence | -|----|----------| -| 기본 요청 watchdog touch | tests/application/test_messaging_snapshot_consumer.py::test_strategy_poll_touches_watchdog_by_default | -| operator 요청 suppress | ::test_operator_query_suppresses_watchdog_touch | -| CLI 가 억제 플래그 전송 | test_cli_operator.py::test_universe_uses_snapshot_queue_with_operator_suppression | - -### REQ-026 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| interface-contract/runtime-flows 정합 | 문서 diff(BAT-020) | - -## Evidence -- Test: - - 단위 전체 회귀: `327 passed / 34 skipped`(신규 CLI 14 + 워치독 2 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `361 passed`. - - `ruff check trex_core tests` → All checks passed. -- Logs: `327 passed, 34 skipped, 1 deselected` / `361 passed, 1 deselected ~17.8s`. - -## 성공 기준(dcy-016) 대응 -- S-1(universe/slots snapshot RPC 조회, 직접 DB 없음): test_universe_*/test_slots_*. -- S-2(enter/exit intent RPC 제출 + admitted/rejected 표시, DB 쓰기 0): test_enter_with_yes_*/test_exit_*/test_enter_rejected_*. -- S-3(확인 프롬프트/`--yes`/live 가드, 미확인 시 미제출): test_enter_aborts_*/test_live_requires_live_flag/test_live_proceeds_*. -- S-4(인스턴스 라우팅 + 실패 표시): test_instance_shortcut_*/test_rpc_failure_surfaces_error. -- S-5(`--json` 유효 + enter/exit `--json` 요구 `--yes`): test_json_output_is_valid_json/test_enter_json_requires_yes. - -## Carried-Over / 비차단 -- 실 RabbitMQ 왕복 CLI e2e: 기존 transport `call_rpc` 인프라 게이트로 커버(CLI 는 주입 rpc 단위). 실 다중 인스턴스 라우팅 e2e 는 운영 검증 비차단. -- snapshot envelope 에 slot_id 미노출 → `exit` 는 운영자 제공 slot_id 사용. snapshot 에 slot_id 노출은 REQ-028 strict shape 변경이라 본 주기 밖(별도 결정). -- kill_switch CLI 노출은 Out of Scope(후속). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. CLI 명령·안전장치·워치독 억제 모두 단위 green(361 passed 회귀, 워치독 기본 동작 무회귀). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/design.md b/docs/batches/bat-021_20260608_watchdog-ownership-visibility/design.md deleted file mode 100644 index 3265689..0000000 --- a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/design.md +++ /dev/null @@ -1,41 +0,0 @@ -# BAT-021 Design - -- Status: confirmed -- Batch: BAT-021 - -## 설계 목표 -- 전략 이탈로 슬롯이 워치독(안전망) 소유로 넘어간 상태와 safety-sell 발동을 무성에서 가시화한다(issue #12). 가시화는 best-effort — 판정·청산을 절대 막지 않는다(NFR-1). - -## 1. 워치독 상태 노출 (REQ-034/021) -- `WatchdogStore.last_seen(slot_id) -> Optional[datetime]` read-only getter 추가(판정 로직 불변). -- `make_snapshot_rpc_handler(..., watchdog_timeout_sec=120)` + `_attach_watchdog(payload, slot_repo, day, watchdog_store, timeout)`: - - holding 슬롯마다 `{slot_id, portfolio_id, symbol, expired(is_expired), last_seen}` 산출 → `payload["watchdog"]`. - - try/except 로 감싸 실패 시 응답 불변(best-effort). envelope·stub 두 경로 모두 attach. -- additive top-level 필드라 REQ-028 strict asset shape(no-price) 불변. - -## 2. safety-sell 가시화 (REQ-037/038/039, OQ-2) -- `SafetySellService(instance_key="default")` + `self._owned: set` + `self.counters: dict`. -- `_log_fire(slot, reason)`: `_WARN_REASONS={forced_close_at, stop_loss}` → WARN, 그 외(take_profit, market_close_liquidation) → INFO. 구조화 필드(phase/slot_id/portfolio_id/reason/instance_key) + `counters["fire:{reason}"]++`. check_forced_close/check_prices/check_market_close 의 발동 지점에서 호출. -- `_update_ownership(now)`: grace 중이면 skip. `current = {holding ∧ watchdog_expired}`; `current - _owned` → WARN(owned:enter), `_owned - current` → INFO(owned:exit); `_owned = current`. run_once 매 tick 호출. 예외 격리. -- per-tick 스킵/no-fire 평가는 로그 X, 카운터만(30초 로그 스팸 방지). 영속 거부 반복 경고는 REQ-052 별도. - -## 3. CLI 표시 (REQ-032) -- `_cmd_slots`: snapshot 응답의 `watchdog` 필드에서 `expired=true` 인 portfolio_id 집합 → 자산 출력에 `[WATCHDOG-OWNED: strategy absent]` 태그 + `watchdog_owned=N` 요약. `--json` 은 원응답 그대로. - -## 4. 배선 -- daemon: snapshot handler 에 `watchdog_timeout_sec=config.safety_sell.watchdog_timeout_sec`; SafetySellService 에 `instance_key=config.instance_key`. - -## 5. baseline 문서 (REQ-042) -- runtime-flows: 워치독 갱신 flow 에 상태 노출(5) + `safety-sell 가시화` flow 신규(소유 진입/이탈·발동 차등·카운터·best-effort). -- interface-contract: snapshot 응답 `watchdog[]` 필드. - -## best-effort 격리 (NFR-1) -- `_attach_watchdog`·`_update_ownership` 예외 무시(no-op), `_log_fire` 는 순수 로그/카운터. 워치독 판정(is_expired)·청산(`SlotService.sell`) source of truth 불변. - -## 재시작 (R-3) -- WatchdogStore·`_owned`·counters in-memory → 재시작 시 소유 상태 소실(폴링 재개 시 재구성). 발동 로그는 외부 로그 싱크에 이력 유지. 영속 승격은 Out of Scope. - -## 테스트 설계 -- `tests/application/test_safety_sell_visibility.py`: 소유 진입 WARN+카운터, 이탈 INFO+카운터, 안정 시 churn 없음, forced_close WARN, take_profit INFO, grace 억제(6건). instance_key 로그 포함 단언. -- `tests/application/test_messaging_snapshot_consumer.py`: 워치독 만료 holding → watchdog 필드 expired/last_seen 노출. -- `tests/runtime/test_cli_operator.py`: slots 가 expired 슬롯을 WATCHDOG-OWNED 로 표시. diff --git a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/implementation.md b/docs/batches/bat-021_20260608_watchdog-ownership-visibility/implementation.md deleted file mode 100644 index a3531c8..0000000 --- a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/implementation.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-021 Implementation - -- Status: completed -- Batch: BAT-021 - -## 구현 요약 -- dcy-017 워치독 소유권 가시화(snapshot 워치독 상태 + safety-sell 발동 로깅)를 전부 change-req 로 구현(신규 REQ 없음, Redis 미채택). - -## 변경 내역 - -### REQ-034/021 — 워치독 상태 노출 -- `trex_core/application/watchdog.py`: `last_seen(slot_id) -> Optional[datetime]` read-only getter. -- `trex_core/application/messaging_handlers.py`: `make_snapshot_rpc_handler(..., watchdog_timeout_sec=120)` + `_attach_watchdog(payload, ...)` — holding 슬롯 `{slot_id, portfolio_id, symbol, expired, last_seen}` 를 `payload["watchdog"]` 에 best-effort 동봉(envelope·stub 두 경로). try/except 격리. -- `trex_core/runtime/daemon.py`: handler 에 `watchdog_timeout_sec=config.safety_sell.watchdog_timeout_sec` 배선. - -### REQ-037/038/039 — safety-sell 발동·소유 가시화 -- `trex_core/application/safety_sell.py`: - - `SafetySellService(instance_key="default")` + `self._owned: set` + `self.counters: dict` + `_count`. - - `_log_fire(slot, reason)`: WARN(`forced_close_at`/`stop_loss`)·INFO(`take_profit`/`market_close_liquidation`) + `fire:{reason}` 카운터, 구조화 필드. - - `_update_ownership(now)`: grace 밖에서 holding∧expired 집합 diff → 진입 WARN(`owned:enter`)/이탈 INFO(`owned:exit`). 예외 격리. - - check_forced_close/check_prices/check_market_close 발동 지점에서 `_log_fire`; run_once 가 매 tick `_update_ownership`. -- `trex_core/runtime/daemon.py`: SafetySellService 에 `instance_key=config.instance_key`. - -### REQ-032 — CLI 표시 -- `trex_core/runtime/cli.py::_cmd_slots`: snapshot 응답 `watchdog` 에서 `expired` 슬롯 → `[WATCHDOG-OWNED: strategy absent]` 태그 + `watchdog_owned=N` 요약. - -### REQ-042 — baseline 문서 -- `docs/runtime-flows.md`: 워치독 갱신 flow 에 상태 노출(5) + `safety-sell 가시화` flow 신규. Last Updated → BAT-021. -- `docs/interface-contract.md`: snapshot 응답 `watchdog[]` 필드. Last Updated → BAT-021. - -## 테스트 -- 신규 `tests/application/test_safety_sell_visibility.py`: 6건 — 소유 진입 WARN+카운터(instance_key 포함), 이탈 INFO+카운터, 안정 시 churn 없음, forced_close WARN, take_profit INFO, grace 억제. -- `tests/application/test_messaging_snapshot_consumer.py` 확장: 워치독 만료 holding → watchdog 필드 expired/last_seen 노출(operator 비침습). -- `tests/runtime/test_cli_operator.py` 확장: slots 가 expired 슬롯 WATCHDOG-OWNED 표시. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `335 passed / 34 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `369 passed`. -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/index.md b/docs/batches/bat-021_20260608_watchdog-ownership-visibility/index.md deleted file mode 100644 index 0daf70d..0000000 --- a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-021: 워치독 소유권 가시화 — 스냅샷 워치독 상태 + safety-sell 발동 로깅 (dcy-017) - -- Status: released -- Profile: standard -- Source Discovery: dcy-017 -- Included REQ: REQ-034 (CHG-20260608-02), REQ-021 (CHG-20260608-03), REQ-037 (CHG-20260608-01), REQ-038 (CHG-20260608-01), REQ-039 (CHG-20260608-01), REQ-032 (CHG-20260608-02), REQ-042 (CHG-20260608-01) - -## Summary -- dcy-017(전략 이탈로 슬롯이 내부 청산 루틴(워치독) 소유로 넘어간 상태를 가시화, issue #12)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-017 OQ-3 결정대로 **전부 change-req, 신규 REQ 없음**. -- 핵심: snapshot RPC 응답에 holding 슬롯 워치독 상태(`expired`/`last_seen`)를 read-only 동봉(REQ-034/021)하고 `trex-core slots` 가 WATCHDOG-OWNED(전략 이탈)를 표시(REQ-032). safety-sell 의 워치독 소유 진입(WARN)/이탈(INFO)·발동(forced_close·stop_loss=WARN, take_profit·market_close=INFO)을 카운터+구조화 로그로 가시화(REQ-037/038/039). 모든 가시화는 best-effort(청산을 막지 않음). Redis projection·push 알림 미채택(OQ-1 (a)). - -## Scope -- In Scope: - - snapshot RPC 응답 워치독 상태 필드(best-effort) + `WatchdogStore.last_seen` (REQ-034/021) - - `trex-core slots` 워치독 소유 표시 (REQ-032) - - safety-sell 워치독 소유 진입/이탈 + 발동(phase 차등 WARN/INFO) 로그 + in-process 카운터 (REQ-037/038/039) - - runtime-flows/interface-contract 정합 (REQ-042) -- Out of Scope: - - 워치독 판정 로직·timeout 기준 변경(가시화만) - - WatchdogStore in-memory → 영속 store 승격(정본 변경) - - Redis projection·pub/sub push 알림(OQ-1 (a) 미채택) — push 하드 요구 시 후속 분리 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-017). OQ-1((a) CLI/RPC 노출, Redis 미채택)·OQ-2(차등 매트릭스)·OQ-3(전부 change-req) Answered. -- dcy-016 으로 관측 함정(조회가 워치독 리셋) 이미 해소 → 비침습 on-demand 관측으로 issue #12 충족(A-1). -- best-effort 격리(NFR-1): _attach_watchdog·_update_ownership·_log_fire 모두 예외 격리, source of truth(in-memory 워치독)·청산 동작 불변. -- 메트릭은 in-process 카운터(`SafetySellService.counters`) — 기존 REQ 범위(신규 인프라 없음). -- WatchdogStore in-memory → 소유 *상태* 는 재시작 시 소실(폴링 재개 시 재구성), 발동 *로그* 는 이력 유지(R-3). diff --git a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/planning.md b/docs/batches/bat-021_20260608_watchdog-ownership-visibility/planning.md deleted file mode 100644 index 283cb77..0000000 --- a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/planning.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-021 Planning - -- Status: confirmed -- Batch: BAT-021 -- Profile: standard - -## Included REQ -- REQ-034 (Interface, change-req): `WatchdogStore.last_seen` + snapshot 워치독 상태 노출. -- REQ-021 (Integration, change-req): snapshot 응답 `watchdog[]` 필드. -- REQ-037 (Integration, change-req): 워치독 소유 진입/이탈 + forced_close 발동 가시화. -- REQ-038 (Integration, change-req): stop_loss(WARN)/take_profit(INFO) 발동 가시화. -- REQ-039 (Integration, change-req): market_close 발동 가시화. -- REQ-032 (Interface, change-req): `trex-core slots` 워치독 소유 표시. -- REQ-042 (Documentation, change-req): runtime-flows/interface-contract 정합. - -## Delivery Plan -1. REQ-034/021(서버): `watchdog.py` `last_seen` getter; `messaging_handlers.py` `_attach_watchdog`(best-effort) + `watchdog_timeout_sec` 파라미터; daemon 배선. -2. REQ-037/038/039(가시화): `safety_sell.py` `instance_key`/`counters`/`_log_fire`(phase 차등)/`_update_ownership`(소유 진입·이탈) + check_*·run_once 배선; daemon instance_key 배선. -3. REQ-032(CLI): `cli.py` `_cmd_slots` 가 watchdog 필드로 WATCHDOG-OWNED 표시. -4. REQ-042(문서): runtime-flows(워치독 노출 + safety-sell 가시화 flow), interface-contract(snapshot watchdog 필드). -5. 테스트: safety-sell 가시화 + snapshot watchdog 필드 + CLI 표시 + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-007/BAT-010·011 safety-sell/워치독(REQ-034/037/038/039) — 가시화 대상. - - dcy-016/BAT-020 운영자 CLI `slots`·워치독 억제 읽기(REQ-032/021) — 비침습 관측 기반(A-1). - - dcy-011/dcy-013 의 WARN+카운터 가시화 패턴 정합(NFR-3). -- External: - - 없음(Redis 미채택). in-process 카운터. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) 가시화 부수효과가 워치독 판정·safety-sell 청산을 절대 막으면 안 됨(R-1/NFR-1) — best-effort 격리 경계를 설계에서 못박아야 안전 기능 회귀를 막는다. (b) 워치독 소유 진입/이탈 diff 의 grace 처리·상태 보관(재시작 소실, R-3)·phase별 로그 레벨 매트릭스(OQ-2)를 설계에서 고정. (c) snapshot 응답에 watchdog 필드 추가가 REQ-028 strict envelope 와 충돌하지 않도록 additive top-level 필드 경계 확정. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-034/021 watchdog last_seen + snapshot 필드, 핸들러 테스트 green -2. M2 — REQ-037/038/039 safety-sell 소유/발동 가시화 + 카운터, 가시화 테스트 green -3. M3 — REQ-032 CLI 표시 -4. M4 — REQ-042 문서 정합 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High): 가시화가 실패/지연해 워치독 판정·청산을 막음. 대응: _attach_watchdog/_update_ownership/_log_fire 예외 격리, source of truth(in-memory) 불변. (NFR-1) -- R-2 (Low, OQ-1): 메커니즘 과잉. 대응: Redis 미채택, CLI/RPC 노출 + 로깅으로 한정. -- R-3 (Low): WatchdogStore in-memory → 재시작 시 소유 상태 소실. 대응: live 반영임을 문서화, 발동 로그는 이력 유지. - -## Notes -- 1차 게이트: 전체 회귀 green(워치독 판정·safety-sell 트리거 무회귀 — 관측만 추가). -- 가시화 테스트는 caplog(WARN/INFO) + counters 단언으로 결정론. -- snapshot watchdog 필드는 operator(touch_watchdog=false) 요청으로 비침습 검증(폴링이 last_seen 갱신 안 함). diff --git a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/verification.md b/docs/batches/bat-021_20260608_watchdog-ownership-visibility/verification.md deleted file mode 100644 index a9872b3..0000000 --- a/docs/batches/bat-021_20260608_watchdog-ownership-visibility/verification.md +++ /dev/null @@ -1,71 +0,0 @@ -# BAT-021 Verification - -- Status: verified -- Batch: BAT-021 - -## Acceptance Mapping - -### REQ-034 (워치독 상태 노출, change-req) -| AC | Evidence | -|----|----------| -| snapshot 응답 holding 슬롯 expired/last_seen 노출 | tests/application/test_messaging_snapshot_consumer.py::test_snapshot_exposes_watchdog_state_for_holding_slots | -| 관측이 워치독 리셋 안 함(비침습) | ::test_operator_query_suppresses_watchdog_touch (dcy-016 회귀) | -| 판정 동작 불변 | tests/application/test_safety_sell_worker.py 전체 회귀 | - -### REQ-021 (snapshot watchdog 필드, change-req) -| AC | Evidence | -|----|----------| -| 응답 watchdog[] shape | test_messaging_snapshot_consumer.py::test_snapshot_exposes_watchdog_state_for_holding_slots | - -### REQ-037 (소유 진입/이탈 + forced_close, change-req) -| AC | Evidence | -|----|----------| -| 소유 진입 WARN + 카운터 | tests/application/test_safety_sell_visibility.py::test_watchdog_ownership_entry_warns | -| 소유 이탈 INFO + 카운터 | ::test_watchdog_ownership_exit_on_return | -| 안정 시 churn 없음 | ::test_no_ownership_churn_when_stable | -| forced_close 발동 WARN + 카운터 | ::test_forced_close_fire_logs_warn | -| grace 중 억제 | ::test_grace_period_suppresses_ownership | - -### REQ-038 (SL/TP 발동, change-req) -| AC | Evidence | -|----|----------| -| take_profit 발동 INFO + 카운터 | test_safety_sell_visibility.py::test_take_profit_fire_logs_info | -| stop_loss WARN(매트릭스) | _log_fire WARN reasons + forced_close WARN 테스트로 레벨 검증 | - -### REQ-039 (market_close 발동, change-req) -| AC | Evidence | -|----|----------| -| market_close INFO + 카운터 | _log_fire INFO 경로(test_take_profit INFO 동형) + 기존 market_close 회귀 | - -### REQ-032 (CLI 표시, change-req) -| AC | Evidence | -|----|----------| -| slots WATCHDOG-OWNED 표시 | tests/runtime/test_cli_operator.py::test_slots_marks_watchdog_owned | - -### REQ-042 (baseline 문서, change-req) -| AC | Evidence | -|----|----------| -| runtime-flows/interface-contract 정합 | 문서 diff(BAT-021) | - -## Evidence -- Test: - - 단위 전체 회귀: `335 passed / 34 skipped`(신규 visibility 6 + snapshot 1 + CLI 1 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `369 passed`. - - `ruff check trex_core tests` → All checks passed. -- Logs: `335 passed, 34 skipped, 1 deselected` / `369 passed, 1 deselected ~18.1s`. - -## 성공 기준(dcy-017) 대응 -- S-1(슬롯이 워치독 소유로 넘어감을 운영자가 인지): snapshot watchdog 필드 노출 + CLI WATCHDOG-OWNED 표시 + 소유 진입 WARN. -- S-2(소유 접수·발동·이탈 구분 관측, 무성 0): test_safety_sell_visibility(소유 진입/이탈, forced_close WARN, take_profit INFO) + 카운터. -- S-3(관측이 last_seen 미갱신): test_operator_query_suppresses_watchdog_touch(dcy-016) — snapshot watchdog 테스트도 operator 요청으로 비침습. -- S-4(가시화 실패가 청산 차단 안 함): `_attach_watchdog`/`_update_ownership` try/except 격리(설계), 판정·청산 source of truth 불변 — 기존 safety-sell 트리거 회귀 green. - -## Carried-Over / 비차단 -- WatchdogStore in-memory → 소유 상태 재시작 시 소실(폴링 재개 시 재구성, R-3). 발동 로그는 이력 유지. 영속 승격은 Out of Scope(별도 주기). -- Redis projection·push 알림 미채택(OQ-1 (a)) — push 하드 요구 시 후속 분리. -- 메트릭은 in-process 카운터(`SafetySellService.counters`) — 외부 메트릭 시스템 연동은 운영 배포 절차/후속. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. 워치독 상태 노출·safety-sell 가시화 모두 단위 green(369 passed 회귀, 판정·청산 무회귀). LIVE sandbox 토큰 deselect 는 `.env` 환경 의존(본 batch 무관). diff --git a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/design.md b/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/design.md deleted file mode 100644 index 0e02f6b..0000000 --- a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/design.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-022 Design - -- Status: draft -- Batch: BAT-022 -- Profile: standard - -## 설계 목표 -- snapshot envelope 에 슬롯 단위 read-only `slots[]` 를 additive 추가해, `trex-core slots` 가 universe 가 아닌 데몬의 실제 슬롯 상태를 표시하게 한다. strategy-facing `assets[]` 계약은 비파괴. - -## 1. snapshot envelope `slots[]` 스키마 (REQ-028 AC-4) -- envelope top-level 에 `slots` 키 추가(additive). 항상 배열, 비면 `[]`(null 금지). -- 각 항목(per Slot, `slot_repo.list_slots(trading_date)` 기준): - - `slot_id`: str (UUID) - - `status`: str — `idle`|`pending_buy`|`holding`|`pending_sell`|`closed` (SlotState 값) - - `symbol`: str|null - - `qty`: float|null - - `limit_price`: float|null (pending 주문 지정가; dcy-012 의 side-무관 필드) - - `avg_price`: float|null (매수 체결가/평단) - - `portfolio_id`: str|null (= asset_id) -- **경계**: `assets[]`(portfolio 단위, strategy-facing, no-price)와 독립. `slots[]` 는 operator-facing 이라 가격(`limit_price`/`avg_price`) 노출 허용(REQ-028 AC-3 의 no-price 는 `assets[]` 한정). -- meta(`view`/`available_slots`/`degraded`/`reason`)·`assets[]`·`watchdog[]` 불변. - -## 2. envelope 산출 (`build_snapshot_envelope`, portfolio.py) -- 기존 `assets[]`/`available_slots`/degraded 산출 유지. -- `slots = [ _slot_to_dict(s) for s in slot_repo.list_slots(trading_date) ]` 추가, `to_dict()` payload 에 `"slots": slots` 동봉. -- `_slot_to_dict` 는 Slot 모델 필드를 위 스키마로 매핑(없는 값은 null). -- best-effort 불필요(순수 read, 외부 I/O 없음) — 단 예외가 envelope 산출 전체를 깨지 않도록 슬롯 매핑은 방어적으로. - -## 3. CLI `slots` (REQ-032, cli.py `_cmd_slots`) -- 요청 payload 는 기존대로 `actor="operator"`, `touch_watchdog=False`(비침습) 유지. -- 응답에서 `slots = resp.get("slots", [])` 를 읽는다(기존 `assets` 대신). -- `--status` 필터를 **슬롯 status 기준**으로 적용: - - `all` → 전체, `holding`/`held` → status==holding, `buyable` → status==idle (가용 슬롯), (추가) `pending` → pending_buy|pending_sell 고려. - - 매핑 표는 구현에서 확정(planning Design Gate (d)). -- 출력: `instance=… view=… count= watchdog_owned=` 헤더 + 슬롯별 `slot_id status symbol qty limit_price/avg_price portfolio_id` + watchdog-owned 태그(slot.portfolio_id ↔ watchdog expired 매칭). -- `--json` 은 envelope(또는 `slots`) 그대로 JSON. -- `_cmd_universe` 는 `assets[]` 유지(변경 없음) — 두 명령 표면 분리. - -## 4. watchdog 결합 -- 기존 `watchdog[]`(expired by portfolio_id, dcy-017)와 `slots[]` 를 CLI 에서 결합: 슬롯의 `portfolio_id` 가 watchdog expired 집합에 있으면 `[WATCHDOG-OWNED: strategy absent]` 태그. - -## 5. 문서 (interface-contract) -- snapshot 응답 계약에 `slots[]` 필드(스키마 위와 동일) 추가. `assets[]` vs `slots[]` 구분(universe vs 데몬 슬롯) 명시. - -## best-effort / 안전 -- 본 변경은 read-only 노출 + CLI 표시라 매매·청산·워치독 판정에 영향 없음. 슬롯 도메인/전이 불변. - -## 미해결/구현 확정 항목 -- `--status` → 슬롯 status 매핑 최종표(특히 `buyable`/`pending` 의미). -- `slots[]` 항목에 `trading_date` 포함 여부(현재 미포함, 요청이 단일 trading_date 라 불요). diff --git a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/implementation.md b/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/implementation.md deleted file mode 100644 index c08a05d..0000000 --- a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/implementation.md +++ /dev/null @@ -1,73 +0,0 @@ -# BAT-022 Implementation - -- Status: in-delivery -- Batch: BAT-022 -- Profile: standard - -## Changed Files -- `trex_core/application/portfolio.py` — `SnapshotEnvelope` 에 `slots: list[dict]`(additive) + `to_dict()` 동봉, **canonical `slot_to_dict(s)`**(operator-facing: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at), `build_snapshot_envelope` 가 `slot_to_dict` 로 `slots[]`(전체 슬롯 테이블) 산출. (REQ-028 AC-4) -- `trex_core/application/messaging_handlers.py` — 로컬 `_slot_to_dict` 를 제거하고 `portfolio.slot_to_dict` import(단일 source of truth → BAT-007 stub 경로와 BAT-008 envelope 경로가 **동일 슬롯 스키마** 산출). `_slot_to_dict = slot_to_dict` alias 유지. -- `trex_core/runtime/cli.py` — `_cmd_slots` 가 `resp["slots"]`(실제 슬롯 상태)를 읽어 status 포함 표시, `--status` 를 슬롯 status 기준 client-side 필터(all/holding/buyable=idle/pending), watchdog-owned 를 slot.portfolio_id 로 결합; `--status` choices 에 `pending` 추가. 가격은 `entry_price`(canonical 외부명) 표시. `_cmd_universe` 는 `assets[]` 유지. (REQ-032 CHG-20260608-03) -- `docs/interface-contract.md` — snapshot 응답에 `slots[]` 스키마 계약 추가(canonical shape), `assets[]` vs `slots[]` 구분 명시, CLI↔RPC 매핑 행 정정(`slots` = slot 상태). (REQ-028/032 baseline 동기화) -- `tests/application/test_snapshot_selection.py`, `tests/application/test_snapshot_handler.py` — envelope key set 에 `slots` 추가, no-price 검증을 `assets[]` 로 scope. -- `tests/runtime/test_cli_operator.py` — `_SNAPSHOT` 에 canonical `slots[]` 추가; `slots` 가 universe 가 아닌 슬롯 상태 표시·`--status` 슬롯 status 필터·watchdog 결합 테스트로 갱신. - -## Post-verification 보정 (BAT-022 범위 내, REQ-032) -- 운영 사용 중 발견: `universe --json` 이 envelope 전체(assets + 신규 `slots[]` + `watchdog[]` + meta)를 덤프해 출력이 과다. envelope 에 `slots[]` 를 추가한 부수효과 — `universe` 는 universe(assets)만 보여야 함. -- 수정: `trex_core/runtime/cli.py` `_cmd_universe` 의 json 을 `{instance, view, assets, available_slots, degraded, reason}` 로 scope(`slots[]`/`watchdog[]` 제외 — `slots` 명령 소관). `slots --json` 은 이미 `{instance, status, slots}` 로 scope 되어 일관. -- 검증: 신규 단위 `tests/runtime/test_cli_operator.py::test_universe_json_is_scoped_to_universe`(키셋·누출 없음) + 라이브 `trex-core universe --json` → keys={assets,available_slots,degraded,instance,reason,view}, slots/watchdog 누출 없음. cli 17 passed, ruff clean. - -## 검증 중 발견 (중요) -- BAT-007 stub 경로(`messaging_handlers`)는 이미 `_slot_to_dict` 로 `slots[]` 를 반환하고 있었다 — 갭은 **BAT-008 envelope 경로(snapshot_store 연결 시, 운영 배포)에만** 존재했다(`build_snapshot_envelope` 이 slots 미동봉). 두 경로의 슬롯 dict 스키마가 달라 CLI 일관성도 깨질 뻔해, canonical `slot_to_dict` 로 통일(외부명 `entry_price` 유지, dcy-012 호환). - -## Execution Log -1. portfolio.py: `slots[]` envelope 필드 + `_slot_to_dict` + builder 동봉. -2. cli.py: `_cmd_slots` 가 `slots[]` 읽기·status 필터·watchdog 결합으로 정정, `universe` 분리 유지. -3. 기존 테스트 4건(OLD 동작 검증)을 새 계약으로 갱신. -4. 검증 수행(아래). - -## Validation -- ruff: `ruff check`(변경 파일) → All checks passed. -- 타깃 단위: `pytest tests/application/test_snapshot_selection.py tests/application/test_snapshot_handler.py tests/runtime/test_cli_operator.py` → 31 passed. -- 전체 회귀: `pytest -q` → **338 passed, 1 failed, 34 skipped**. 유일 실패 `tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` 는 KIS 샌드박스 env(`TREX_KIS_BROKER_BASE_URL` 이 live URL)에 의존하는 **사전 환경 이슈**로 본 변경(portfolio/cli)과 무관. - -## Remaining Risks -- 인프라 게이트(실 PostgreSQL/RabbitMQ)·운영 재현(`trex-core slots` 가 실제 슬롯 N개 status 표시)은 verification 단계에서 확인 필요. -- KIS 샌드박스 env 테스트 실패는 본 batch 범위 밖(환경 설정) — verification 에서 분리 기록. - -## 구현 단계 - -### M1 — REQ-028 envelope `slots[]` -- [ ] `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가. - - `_slot_to_dict(slot)` 헬퍼: `{slot_id, status, symbol, qty, limit_price, avg_price, portfolio_id}` 매핑(없는 값 null). - - `slot_repo.list_slots(trading_date)` 순회 → `slots` 리스트, `to_dict()` payload 에 `"slots"` 동봉. 빈 배열 `[]`. -- [ ] `assets[]`/meta/`watchdog[]`/degraded 불변 확인. - -### M2 — REQ-032 CLI `slots` -- [ ] `trex_core/runtime/cli.py` `_cmd_slots`: - - `slots = resp.get("slots", [])` 로 변경(기존 `assets`). - - `--status` → 슬롯 status 필터 매핑(all/holding(held)/buyable(idle)/pending). - - 출력: `count=len(slots)` + 슬롯별 `slot_id status symbol qty price portfolio_id` + watchdog-owned 태그(portfolio_id 매칭). - - `--json` 경로 유지. -- [ ] `_cmd_universe` 무변경(assets[] 유지) 확인. - -### M3 — 문서 -- [ ] `docs/interface-contract.md` snapshot 응답에 `slots[]` 필드 계약 + `assets[]` vs `slots[]` 구분 명시. -- [ ] 필요 시 `docs/runtime-flows.md` 보강. - -### M4 — 테스트/게이트 -- [ ] envelope `slots[]` 스키마 단위(빈 배열·status enum·가격 필드 포함). -- [ ] `slots` CLI 가 슬롯 상태(status 포함) 표시, universe(`assets[]`)와 구분. -- [ ] `--status` 필터 케이스(idle/holding/pending). -- [ ] 전체 회귀 + ruff + 인프라 게이트 green. - -## 변경 파일 (예상) -- `trex_core/application/portfolio.py` (`build_snapshot_envelope`, `_slot_to_dict`) -- `trex_core/runtime/cli.py` (`_cmd_slots`) -- `docs/interface-contract.md` -- tests: snapshot envelope `slots[]`, cli `slots` 슬롯 상태 표시 - -## 구현 노트 -- 순수 read 노출 — 슬롯 도메인/전이/워치독 판정/청산 무변경. -- 비침습: 요청은 `actor="operator"`/`touch_watchdog=false` 유지(dcy-016). -- 가격 노출은 `slots[]`(operator) 한정 — strategy `assets[]` no-price 규칙 불변. diff --git a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/index.md b/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/index.md deleted file mode 100644 index 8116412..0000000 --- a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-022: 슬롯 상태 스냅샷 가시화 — `slots` 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속) - -- Status: released -- Profile: standard -- Source Discovery: dcy-016 (후속 change-req) -- Included REQ: REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) - -## Summary -- dcy-016 으로 추가된 `trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭을 정정한다(운영 관측: 10개 슬롯인데 universe 1개·status 없음). 근인은 BAT-007 에서 HTTP 슬롯 read(REQ-008/025) 제거 후 **슬롯 상태 read 표면 부재** + snapshot envelope 이 슬롯 상태를 운반하지 않음. -- 핵심: snapshot envelope 에 read-only per-slot `slots[]`(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`)를 **additive** 추가(REQ-028 AC-4)하고, `trex-core slots` 가 이를 표시(REQ-032). strategy-facing `assets[]`(universe) 계약은 불변, `universe` 명령은 `assets[]` 유지로 둘을 명확히 분리. dcy-017 의 per-slot `watchdog[]` 동봉 선례와 동일 방식. - -## Scope -- In Scope: - - snapshot envelope 에 read-only `slots[]` 필드 추가 (REQ-028 AC-4) — `build_snapshot_envelope` - - `trex-core slots` 가 `slots[]`(실제 슬롯 상태, `status` 포함)를 표시, `--status` 필터를 슬롯 상태 기준으로 적용 (REQ-032) - - `universe` 명령은 `assets[]`(universe) 유지 — 두 명령 표면 분리 - - watchdog-owned 표시(REQ-032 CHG-20260608-02)를 `slots[]` 항목과 결합 - - interface-contract 정합 (snapshot 응답 `slots[]` 필드) -- Out of Scope: - - strategy-facing `assets[]`(universe) 계약 변경 (불변) - - 슬롯 상태 도메인/전이 로직 변경 (가시화·노출만) - - WatchdogStore in-memory → 영속 승격 - - 신규 슬롯 read RPC/HTTP 표면 (기존 snapshot envelope 확장으로 충족) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-016 후속). REQ-028 CHG-20260608-01 / REQ-032 CHG-20260608-03 change-req 로 두 REQ 가 Implemented→Approved 로 내려옴. -- 설계 정합성: dcy-017/BAT-021 이 이미 envelope 에 top-level `watchdog[]` 를 additive 동봉한 선례 → `slots[]` 도 동일 패턴(additive, strict envelope 비파괴). -- 1차 게이트: 전체 회귀 green — strategy `assets[]`(REQ-028 AC-1~3)·snapshot 동작 무회귀(필드 추가만). -- `slots[]` 와 `watchdog[]` 는 둘 다 슬롯 단위 read-only projection — CLI `slots` 에서 결합 표시. diff --git a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/planning.md b/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/planning.md deleted file mode 100644 index 1de80c4..0000000 --- a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/planning.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-022 Planning - -- Status: draft -- Batch: BAT-022 -- Profile: standard - -## Included REQ -- REQ-028 (Interface, change-req CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]` 추가(AC-4). strategy `assets[]` 불변. -- REQ-032 (Interface, change-req CHG-20260608-03): `trex-core slots` 가 universe `assets[]` 대신 envelope `slots[]`(실제 슬롯 상태) 표시. `universe` 는 `assets[]` 유지. - -## Delivery Plan -1. REQ-028(서버 envelope): `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가 — `slot_repo.list_slots(day)` 로 per-slot 상태(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`) 매핑. 빈 배열은 `[]`. `assets[]`/meta/degraded 불변. (필요 시 `messaging_handlers.py` 핸들러 배선 확인.) -2. REQ-032(CLI): `trex_core/runtime/cli.py` `_cmd_slots` 가 `resp.get("slots", [])` 를 읽어 슬롯 상태(status 포함) 출력. `--status all|held|holding|buyable` 를 슬롯 status 기준으로 필터. watchdog-owned 표시(기존)를 `slots[]` 항목과 결합(slot 의 portfolio_id ↔ watchdog expired 매칭). `_cmd_universe` 는 `assets[]` 유지. -3. 문서: interface-contract 의 snapshot 응답에 `slots[]` 필드 계약 추가(REQ-028 Impacted). runtime-flows 필요 시 보강. -4. 테스트: envelope `slots[]` 스키마(빈 배열·status enum) + `slots` CLI 가 실제 슬롯 상태 표시(universe 와 구분) + 전체 회귀 + ruff + 인프라 게이트. - -## Dependencies -- Internal: - - dcy-005/BAT-008 snapshot envelope(REQ-028) — 확장 대상. - - dcy-016/BAT-020 운영자 CLI `slots`(REQ-032) — 정정 대상(현재 universe 표시). - - dcy-017/BAT-021 envelope `watchdog[]` additive 동봉 — 동일 패턴 선례 + CLI 결합 표시. - - `SlotRepository.list_slots`/Slot 모델(`status`/`limit_price`/`avg_price`/`portfolio_id`) — slots[] 산출 소스. -- External: - - 없음. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) snapshot envelope 은 strict 계약(REQ-028 AC-1~3: `assets[]` null 금지·가격 제외 등)이라 `slots[]` 를 **additive top-level 필드**로 두어 기존 strategy 계약을 비파괴해야 한다 — 필드 경계·스키마를 설계에서 고정. (b) `slots[]` 항목 스키마(어떤 Slot 필드를 노출/제외할지 — 가격은 `limit_price`/`avg_price` 노출하되 strategy `assets[]` 의 no-price 규칙과 혼동 없게) 확정. (c) `slots[]`(슬롯 단위) ↔ `watchdog[]`(슬롯 단위) ↔ `assets[]`(portfolio 단위)의 키 관계(`portfolio_id`/`slot_id`)와 CLI 결합 표시 규칙 고정. (d) `--status` 필터 의미를 universe view(all/buyable/held)가 아닌 **슬롯 status(idle/holding/pending_*)** 기준으로 재정의. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-028 envelope `slots[]` 산출 + 스키마 테스트 green -2. M2 — REQ-032 `slots` CLI 가 슬롯 상태 표시(+watchdog 결합), universe 와 구분 -3. M3 — interface-contract `slots[]` 계약 정합 -4. M4 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (Medium): `slots[]` 추가가 strict envelope(REQ-028 AC-1~3)·기존 strategy 소비자를 깨뜨림. 대응: additive top-level 필드로만 추가, `assets[]`/meta 불변, 기존 envelope 테스트 green 유지. -- R-2 (Medium): `--status` 필터 의미 변경(universe view → 슬롯 status)이 기존 `slots` 사용자 기대와 다름. 대응: design 에서 필터 매핑 명시, verification 에서 status별 케이스 검증. -- R-3 (Low): `slots[]` 와 `assets[]` 혼동(둘 다 symbol/portfolio_id 포함). 대응: CLI 출력에 슬롯 status 컬럼을 1급으로 표시, 문서에서 universe vs slots 구분 명시. - -## Notes -- 1차 게이트: 전체 회귀 green(snapshot strategy 계약·슬롯 도메인 무회귀 — 노출 필드만 추가). -- `slots[]` 검증은 operator(`touch_watchdog=false`) 요청으로 비침습 확인(조회가 워치독 리셋 안 함, dcy-016). -- 가격 노출: `slots[]` 는 운영자용이라 `limit_price`/`avg_price` 노출 OK(REQ-028 AC-3 의 no-price 는 strategy `assets[]` 한정). diff --git a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/verification.md b/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/verification.md deleted file mode 100644 index a902c05..0000000 --- a/docs/batches/bat-022_20260608_slot-state-snapshot-visibility/verification.md +++ /dev/null @@ -1,73 +0,0 @@ -# BAT-022 Verification - -- Status: approved -- Batch: BAT-022 -- Profile: standard -- Ready for Release: true (단위 green + baseline 동기화 + 실 인프라 운영 재현 PASS; pytest 인프라 게이트는 Human Approval Memo 로 수용된 CI 잔여) - -## Acceptance Mapping - -### REQ-028 AC-4 — envelope `slots[]` -- V-1 (스키마): envelope 에 `slots[]` 포함, canonical shape(slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at). - - Evidence: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`(envelope key set 에 `slots` 포함, list), `test_snapshot_handler.py::test_envelope_shape_returned`(body 에 `slots` list). → PASS -- V-2 (빈 배열): 슬롯 없을 때 `slots == []`(null 아님). - - Evidence: `SnapshotEnvelope.slots` default `[]` + `to_dict` 가 `list(...)`. envelope 테스트 green. → PASS -- V-3 (status enum): `status` 가 SlotState 값. - - Evidence: `slot_to_dict` 가 `s.status.value` 사용. → PASS -- V-4 (회귀): `assets[]`(AC-1~3)·meta·degraded·`watchdog[]` 불변. - - Evidence: 전체 회귀 338 passed(기존 snapshot 계약 테스트 green, no-price 는 `assets[]` 한정으로 유지). → PASS - -### REQ-032 (CHG-20260608-03) — CLI `slots` -- V-5 (슬롯 상태 표시): `slots` 가 universe `assets` 가 아니라 envelope `slots[]`(status 포함) 표시. - - Evidence: `tests/runtime/test_cli_operator.py::test_slots_shows_real_slot_state_not_universe`(count=2, `S-1 holding`/`S-2 idle`). → PASS -- V-6 (status 필터): `--status holding`/`idle`/`pending` 가 슬롯 status 기준 필터. - - Evidence: `test_slots_status_filters_by_slot_status`(holding → count=1, S-2 제외). → PASS -- V-7 (universe 분리): `slots`≠`universe`. - - Evidence: `_cmd_universe` 는 `assets[]` 유지(코드 diff), `_cmd_slots` 는 `slots[]` 읽음; universe 테스트 green. → PASS -- V-8 (watchdog 결합): expired 슬롯에 `[WATCHDOG-OWNED]` 태그(portfolio_id 매칭). - - Evidence: `test_slots_marks_watchdog_owned`(watchdog_owned=1, WATCHDOG-OWNED). → PASS -- V-9 (`--json`): 유효 JSON. - - Evidence: `_emit(..., as_json=True)`; `--json` 경로 단위(test_cli_operator 의 기존 json 테스트 green). → PASS - -### 비침습 (dcy-016 정합) -- V-10: operator 요청(`touch_watchdog=false`)으로 조회 시 워치독 미갱신. - - Evidence: `_cmd_slots` payload `actor="operator"`/`touch_watchdog=False`; `test_slots_shows_real_slot_state_not_universe` 가 payload 확인. → PASS - -## Baseline 문서 동기화 (Architecture Impact: interface change) -- `docs/interface-contract.md` — snapshot 응답 `slots[]` 스키마 + `assets[]` vs `slots[]` 구분 + CLI↔RPC 매핑 정정. → 갱신 완료(REQ-028/032 Impacted Area). -- `docs/runtime-flows.md` — slot 노출 자체는 read-only(흐름 변화 없음); 추가 갱신 불요로 판단(슬롯 도메인/전이 무변경). - -## 검증 명령/결과 -- ruff: `ruff check`(변경 파일 5개) → All checks passed. -- 단위 회귀: `pytest -q` → **338 passed, 1 failed, 34 skipped**. - - 실패 `tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` — KIS 샌드박스 env(`TREX_KIS_BROKER_BASE_URL` 이 live URL)에 의존하는 사전 환경 이슈. 본 batch(portfolio/cli/messaging_handlers/문서)와 무관(파일 미접촉). Blocking 아님(범위 밖). - - skipped 34 = 인프라 게이트(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL` 미설정). - -## 운영 재현 (실 PostgreSQL + RabbitMQ, 신규 코드 데몬) — PASS -- 데몬을 신규 코드로 재기동(`trex-core-daemon --config config/test.toml`, PID 2965049), 토폴로지 ready 확인. -- `trex-core slots --config config/test.toml` → `instance=test status=all count=10 watchdog_owned=0` + **10개 슬롯이 각각 `status`(idle)·slot_id·qty·limit·avg·pid 표시**. (이전 갭: universe 1개·status 없음) -- `trex-core slots --status holding` → `count=0`(holding 없음 — 정확). 슬롯 status 필터 동작 확인(V-6 live). -- `trex-core universe` → `assets=1`(portfolio 005930) — **여전히 universe 표시**. `slots` ↔ `universe` 분리 확인(V-7 live). -- DB ground truth `daily_slot`: `(2026-06-08, 'idle', 10)` — CLI `slots` 출력과 **정확히 일치**. (V-5 live) -- 요청은 `actor="operator"`/`touch_watchdog=false`(CLI 기본) — 비침습(V-10 live). -- → **보고된 갭(universe 표시·status 없음) 해소 실증 완료**. BAT-008 envelope 경로(snapshot_store 연결, 운영 배포)를 실 인프라로 end-to-end 검증. - -## Remaining (non-blocking) -- **pytest 인프라 게이트**(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL`): 전용 테스트 인프라 CI 항목. 현 환경엔 runtime infra 만 있고 통합 테스트 `pg_clean` 이 슬롯 테이블 TRUNCATE → runtime DB 로 실행 시 실 데이터 파괴라 미실행. 단 위 운영 재현이 실 인프라 end-to-end 경로(BAT-008 envelope)를 이미 PASS 로 커버하므로 release 차단 사유 아님. delivery CI(전용 테스트 인프라)에서 보강 권장. - -## 검증 결과 요약 -- 코드/단위/문서: green (AC-4·REQ-032 매핑 전부 PASS, baseline 동기화 완료). -- 실 인프라 운영 재현: PASS (slots=10 status 표시·DB 일치·universe 분리·비침습). -- `Ready for Release: true` — pytest 인프라 게이트만 전용 환경 CI 잔여(비차단). - -## 미검증/한계 -- pytest 통합 테스트(인프라 게이트)는 전용 테스트 인프라에서 수행 권장 — 운영 재현으로 실 경로는 검증됨. -- 두 경로(BAT-007 stub / BAT-008 envelope) `slots[]` 스키마 일치는 canonical `slot_to_dict` 단일화로 보장(코드 레벨). 운영 재현은 BAT-008 envelope 경로(운영 배포 형상) 검증. - -## Human Approval Memo -- 승인 결정: 승인(release-candidate 승격 허용) -- 승인자: Justin Ko (사용자 명시 지시 — "둘 다 진행") -- 승인 시각(KST): 2026-06-08 19:43 -- 수용 범위: **pytest 통합 인프라 게이트(전용 테스트 인프라)** 의 본 세션 미수행을 잔여 CI 항목으로 수용. -- 잔여 리스크: pytest 통합 테스트 harness 미실행. 단 (a) 신규 코드 데몬으로 실 PostgreSQL+RabbitMQ end-to-end 운영 재현이 BAT-008 envelope 경로를 PASS 로 검증(slots=10·status·DB 일치·universe 분리·비침습), (b) 단위 회귀 338 passed + ruff clean + baseline 동기화 완료. 잔여는 동일 경로의 CI 형식 보강에 한정. -- 승인 근거: 운영 재현이 보고된 갭(universe 표시)의 해소를 실 인프라로 직접 실증하여, 표준 pytest 인프라 게이트와 동등 이상의 실 경로 증거를 제공. runtime DB 로의 pytest 실행은 `pg_clean` TRUNCATE 로 실 데이터 파괴 위험이라 미수행이 안전상 정당. diff --git a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/design.md b/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/design.md deleted file mode 100644 index 0e0bb87..0000000 --- a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-023 Design - -- Status: confirmed -- Batch: BAT-023 - -## 설계 목표 -- ledger write 응답의 portfolio-hub 권위 cash/quantity 를 스냅샷에 write-back 해 "수량 live·현금 stale" 비대칭을 제거(option D 활성화). 공백 구간은 추정 현금으로 메우되 안전·멱등·best-effort 를 보장. - -## 1. 응답 계약 확장 (REQ-017) -- `LedgerWriteOutcome{result: LedgerWriteResult, cash_balance_after: Optional[float], asset_quantity_after: Optional[float]}` 신설. `record_trade` 반환 타입 변경. -- `portfolio_hub.py`: 2xx 응답 root flat `cash_balance_after`/`asset_quantity_after` 파싱(`_opt_num`, 누락/형변환 실패 시 None). new(201)/duplicate(200) 모두. 409→Conflict, 404/400/422→Permanent(dcy-015) 예외 유지. -- `in_memory.py`: `cash_after`/`quantity_after` 주입 파라미터 → outcome 에 실어 반환(테스트용). - -## 2. write-back (REQ-019/030, option D) -- `LedgerWriter(snapshot_store=None)` 주입. `_process` 가 `new`·`duplicate` outcome 후 `_writeback(trade, outcome)` 호출. -- `_writeback`: snapshot_store None 또는 `*_after` None 이면 skip. 아니면 `update_cash_position(trade.asset_id, cash_after, qty_after)`. **try/except 로 격리**(실패는 WARN, ledger outcome·청산 무차단, NFR-1). -- duplicate 도 write-back(OQ-2 b): `*_after` 가 절대 권위값이라 멱등(중복 적용 위험 없음, NFR-3). 409/422 는 예외라 자연 skip. - -## 3. 추정 현금 오버레이 (REQ-029 / OQ-4) -- source = **미정산 outbox delta**(슬롯 아님): 슬롯 기반이면 write-back 후에도 슬롯이 holding 이라 double-count. 미정산(pending) outbox 행은 정확히 "fill∼write-back 공백" 집합. -- `trade_cash_delta(trade)`: buy → -(qty*price + fee + tax), sell → +(qty*price - fee - tax)(코어 근사). -- `LedgerOutbox.pending_cash_delta() -> {asset_id: Σdelta}`(InMemory: pending 행 합 / PG: SQL SUM CASE). 백오프 무관 전체 pending. -- `_apply_cash_estimate(payload, ledger_outbox)`(snapshot 핸들러): pending delta 있는 자산에 `cash_balance += delta` + `cash_estimated:true`. best-effort(예외 시 응답 불변). -- write-back 완료 → 행이 pending 이탈 → delta 0 → 추정 사라지고 store 는 권위값. 자연 수렴, double-count 없음. - -## 4. 수량 source (REQ-029, OQ-1 B) -- 슬롯 오버레이(`build_snapshot_envelope`) **변경 없음** — lifecycle 중 슬롯이 코어 권위 즉시값(불변식 A). write-back 은 현금 + 슬롯 밖 base 수량을 신선화. 슬롯↔write-back 불일치는 REQ-031 포지션 정합 경고로 노출(숨기지 않음). - -## 5. daemon 배선 -- 생성 순서: `SnapshotStore` 를 `LedgerWriter` 보다 먼저 생성(write-back target). -- `LedgerWriter(snapshot_store=self.snapshot_store)`, snapshot 핸들러 `ledger_outbox=self.outbox`. - -## 6. baseline 문서 (REQ-030 정합 포함) -- interface-contract: `LedgerWriteOutcome`/`*_after`/write-back/envelope `cash_estimated`. -- runtime-flows: ledger writer write-back 단계 + snapshot 응답 추정 오버레이. -- REQ-030 Status Approved→Implemented(파일 + srs/index register). - -## 테스트 설계 -- adapter: `*_after` 파싱(new/duplicate/누락). -- writer: write-back new/duplicate/skip(None)/best-effort(store 예외)/store 미주입; pending_cash_delta 합; estimate overlay + write-back 후 수렴. -- PG 게이트: `pending_cash_delta` SQL. -- 회귀: record_trade 반환 타입 변경에 따른 기존 ledger 테스트(outcome.result) 정합 — 기존 테스트 더블/소비처 전환. diff --git a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/implementation.md b/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/implementation.md deleted file mode 100644 index c6b4f9c..0000000 --- a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/implementation.md +++ /dev/null @@ -1,41 +0,0 @@ -# BAT-023 Implementation - -- Status: completed -- Batch: BAT-023 - -## 구현 요약 -- dcy-018 option D 활성화(ledger write 응답 cash/quantity write-back) + 추정 현금 오버레이를 전부 change-req 로 구현(REQ-030 Approved→Implemented). - -## 변경 내역 - -### REQ-017 — 응답 계약 확장 -- `trex_core/application/ledger_port.py`: `LedgerWriteOutcome{result, cash_balance_after?, asset_quantity_after?}` 신설; `record_trade` 반환 타입 → `LedgerWriteOutcome`. -- `trex_core/adapters/ledger/portfolio_hub.py`: 2xx 응답 root flat `cash_balance_after`/`asset_quantity_after` 파싱(`_opt_num`, new+duplicate, 누락 None). 409/404/400/422 예외 유지. -- `trex_core/adapters/ledger/in_memory.py`: `cash_after`/`quantity_after` 주입 + outcome 반환. - -### REQ-019/030 — write-back + 추정 source -- `trex_core/application/ledger_outbox.py`: `LedgerWriter(snapshot_store=None)` + `_writeback`(new+duplicate, `*_after` None/no-store skip, try/except best-effort); `_process` 가 outcome.result 소비. `trade_cash_delta` + `LedgerOutbox.pending_cash_delta()`(InMemory pending 합). -- `trex_core/adapters/persistence/postgresql.py`: `pending_cash_delta` SQL(SUM CASE by asset_id, status='pending'). -- `trex_core/runtime/daemon.py`: SnapshotStore 를 LedgerWriter 보다 먼저 생성 + `snapshot_store` 주입. - -### REQ-029 — 추정 오버레이 + 슬롯 오버레이 유지 -- `trex_core/application/messaging_handlers.py`: `_apply_cash_estimate(payload, ledger_outbox)` — 미정산 자산에 `cash_balance += pending delta` + `cash_estimated:true`(best-effort). snapshot 핸들러 `ledger_outbox` 파라미터. 슬롯 오버레이(`build_snapshot_envelope`)는 불변. -- `trex_core/runtime/daemon.py`: snapshot 핸들러에 `ledger_outbox=self.outbox` 배선. - -### REQ-030 — Approved→Implemented -- `docs/srs/Integration/req-030_*.md` Status Approved→Implemented + CHG-20260608-01; `docs/srs/index.md` register REQ-030 → Implemented. - -### 문서 -- `docs/interface-contract.md`: LedgerWriteOutcome/`*_after`/write-back/envelope `cash_estimated`. Last Updated → BAT-023. -- `docs/runtime-flows.md`: ledger writer write-back 단계 + snapshot 추정 오버레이. Last Updated → BAT-023. - -## 테스트 -- `tests/adapters/ledger/test_portfolio_hub_ledger.py` 확장: `*_after` 파싱(new/duplicate/누락) 3건. -- 신규 `tests/application/test_ledger_option_d.py`: write-back(new/duplicate/skip/best-effort/no-store) + pending_cash_delta + estimate overlay + write-back 후 수렴 (11건). -- 신규 PG 게이트 `tests/adapters/persistence/test_postgresql_ledger_outbox.py::test_pending_cash_delta_on_pg`. -- 회귀: `test_ledger_outbox_backoff.py` programmable ledger → LedgerWriteOutcome 반환 전환. - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `350 passed / 35 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` 환경 의존, 본 batch 무관). -- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `385 passed`(pending_cash_delta SQL 포함). -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/index.md b/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/index.md deleted file mode 100644 index 1ae0c6c..0000000 --- a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-023: ledger write 응답 cash/position write-back (option D 활성화) + 추정 현금 오버레이 - -- Status: released -- Profile: standard -- Source Discovery: dcy-018 -- Included REQ: REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) - -## Summary -- dcy-018(ledger write 응답의 cash/quantity 를 portfolio 스냅샷에 write-back — option D 활성화 + 수량 source 통일, 운영 환류)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-018 OQ-3 대로 **전부 change-req, 신규 REQ 없음**(REQ-030 은 Approved→Implemented). -- 핵심: portfolio-hub#8(배포·동작 중)의 ledger write 응답 root flat `cash_balance_after`/`asset_quantity_after`(절대 권위값)를 어댑터가 파싱(REQ-017) → `LedgerWriter` 가 `new`·`duplicate` 에 `SnapshotStore.update_cash_position` write-back(REQ-019/030, best-effort). "수량 live·현금 stale" 비대칭 제거. 체결~write-back 공백은 미정산 outbox delta 기반 **추정 현금 오버레이**(`cash_estimated` 라벨, REQ-029/OQ-4)로 메우고 권위값 도착 시 수렴. - -## Scope -- In Scope: - - `record_trade` → `LedgerWriteOutcome{result, *_after}` + 어댑터 `*_after` 파싱(new+duplicate, 누락 None) (REQ-017) - - `LedgerWriter` SnapshotStore write-back(new+duplicate, best-effort) + `pending_cash_delta` (REQ-019) - - 추정 현금 오버레이(`_apply_cash_estimate`, `cash_estimated` 플래그) + 슬롯 오버레이 유지 (REQ-029, OQ-1 B/OQ-4 i) - - option D 활성화 → REQ-030 Approved→Implemented - - interface-contract/runtime-flows 정합 -- Out of Scope: - - portfolio-hub#8 자체(외부, 배포·동작 중) - - 일일 스냅샷 스케줄러 fetch 재설계(REQ-030 AC-1 유지) - - 슬롯 오버레이 제거(OQ-1 B 로 유지 확정) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-018). OQ-1 B / OQ-2 b(new+duplicate write-back) / OQ-3 a(change-req) / OQ-4 (i)(추정 오버레이) Answered. DATA(응답 스키마)=dcy-005 A-7 `*_after` + portfolio-hub#8 동작 중. -- best-effort 격리(NFR-1): write-back·추정 모두 예외 격리, ledger 멱등(REQ-019)·청산 무차단. -- 멱등(NFR-3): `*_after` 가 절대 권위값이라 duplicate write-back 도 중복 적용 위험 없음. -- 추정(OQ-4)은 코어 근사 수수료라 "추정/미정산" 라벨 필수, 노출 구간은 outbox drain(~1s)으로 짧음. diff --git a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/planning.md b/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/planning.md deleted file mode 100644 index 293fda2..0000000 --- a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-023 Planning - -- Status: confirmed -- Batch: BAT-023 -- Profile: standard - -## Included REQ -- REQ-017 (Integration, change-req): `record_trade`→`LedgerWriteOutcome` + 어댑터 `*_after` 파싱. -- REQ-019 (Exception, change-req): `LedgerWriter` write-back + `pending_cash_delta` + SnapshotStore 주입. -- REQ-029 (Interface, change-req): 추정 현금 오버레이 + 슬롯 오버레이 유지. -- REQ-030 (Integration, change-req): option D 활성화 → Approved→Implemented. - -## Delivery Plan -1. REQ-017: `ledger_port.py` `LedgerWriteOutcome`; `portfolio_hub.py` `*_after` 파싱(new+duplicate); `in_memory.py` outcome 반환 + cash/qty 주입. -2. REQ-019/030: `ledger_outbox.py` LedgerWriter `snapshot_store` 주입 + `_writeback`(new+duplicate, best-effort) + `trade_cash_delta`/`pending_cash_delta`; `postgresql.py` `pending_cash_delta` SQL; daemon SnapshotStore 배선(생성 순서 조정). -3. REQ-029: `messaging_handlers.py` `_apply_cash_estimate`(snapshot 핸들러, ledger_outbox 주입); 슬롯 오버레이 불변; daemon ledger_outbox 배선. -4. 문서: interface-contract(LedgerWriteOutcome·*_after·cash_estimated)/runtime-flows(write-back·추정 flow). REQ-030 Status Approved→Implemented(파일+register). -5. 테스트 + 전체 회귀 + ruff + 인프라 게이트(PG pending_cash_delta). - -## Dependencies -- Internal: - - dcy-004/BAT-006 LedgerPort/outbox/writer(REQ-017/019) — 변경 대상. - - dcy-005/BAT-008 SnapshotStore/update_cash_position/envelope(REQ-029/030) — write-back sink + 오버레이. -- External: - - portfolio-hub#8 ledger write 응답 `*_after`(배포·동작 중, dcy-005 A-7). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) `record_trade` 반환 타입 변경(LedgerWriteResult→LedgerWriteOutcome)은 LedgerPort·두 어댑터·LedgerWriter·테스트 더블에 교차 — 계약 확장 경계를 설계에서 고정. (b) write-back best-effort 격리(실패가 ledger 멱등·청산 무차단)와 멱등(절대 *_after, duplicate 안전) 규칙을 못박아야 안전. (c) 추정 현금 source = 미정산 outbox delta(슬롯 아님 — 청산 후 double-count 방지) + 슬롯 오버레이 유지 경계 + daemon 생성 순서(SnapshotStore before LedgerWriter). → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-017 LedgerWriteOutcome + 어댑터 파싱, adapter 테스트 green -2. M2 — REQ-019/030 write-back + pending_cash_delta, option D 테스트 green -3. M3 — REQ-029 추정 오버레이 + daemon 배선 -4. M4 — 문서 정합 + REQ-030 Implemented 전환 -5. M5 — 전체 회귀 + ruff + 인프라 게이트 - -## Risks -- R-1 (High→완화): write-back/추정이 ledger·청산을 막음. 대응: best-effort 예외 격리(NFR-1), write 성공 이후 분리 수행. source of truth(outbox 멱등) 불변. -- R-2 (해소): 응답 스키마 미확정. 대응: `*_after` dcy-005 A-7 확정 + portfolio-hub#8 동작 중. 누락 시 None→skip. -- R-3 (Low): 추정 부정확(근사 수수료). 대응: "추정/미정산" 라벨 + outbox drain(~1s) 짧은 구간 + write-back 수렴. -- R-4 (Medium): duplicate 중복 적용. 대응: `*_after` 절대값이라 멱등(덮어쓰기). - -## Notes -- 1차 게이트: 전체 회귀 green(record_trade 반환 타입 변경 호출부 전환 무누락). -- 추정/write-back 결정론: InMemoryLedger(cash_after/quantity_after 주입) + InMemoryLedgerOutbox + SnapshotStore. -- PG `pending_cash_delta` 는 인프라 게이트로 검증. diff --git a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/verification.md b/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/verification.md deleted file mode 100644 index 57e9b55..0000000 --- a/docs/batches/bat-023_20260608_ledger-cash-position-writeback-option-d/verification.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-023 Verification - -- Status: verified -- Batch: BAT-023 - -## Acceptance Mapping - -### REQ-017 (응답 계약 확장, change-req) -| AC | Evidence | -|----|----------| -| record_trade → LedgerWriteOutcome + `*_after`(new) | tests/adapters/ledger/test_portfolio_hub_ledger.py::test_outcome_parses_cash_quantity_after_on_new | -| duplicate `*_after` | ::test_outcome_parses_after_on_duplicate | -| 응답 누락 → None | ::test_outcome_after_none_when_response_omits | -| 409/404/422 예외 회귀 | ::test_409_surfaces_as_ledger_conflict_error / ::test_client_errors_are_permanent | - -### REQ-019 (write-back + pending_cash_delta, change-req) -| AC | Evidence | -|----|----------| -| new write-back | tests/application/test_ledger_option_d.py::test_writeback_updates_snapshot_on_new | -| duplicate write-back(멱등) | ::test_writeback_applies_on_duplicate_too | -| `*_after` 누락 시 skip | ::test_writeback_skipped_when_after_missing | -| best-effort(store 예외 무차단) | ::test_writeback_best_effort_does_not_block_completion | -| store 미주입 무크래시 | ::test_no_writeback_without_store | -| pending_cash_delta 합(InMemory/PG) | ::test_pending_cash_delta_sums_by_asset / test_postgresql_ledger_outbox.py::test_pending_cash_delta_on_pg | - -### REQ-029 (추정 오버레이 + 슬롯 오버레이 유지, change-req) -| AC | Evidence | -|----|----------| -| 미정산 자산 추정 cash + cash_estimated | tests/application/test_ledger_option_d.py::test_apply_cash_estimate_overlays_pending_delta | -| write-back 후 수렴(추정 제거) | ::test_estimate_clears_after_writeback | -| 슬롯 오버레이 불변 회귀 | tests/application/test_messaging_snapshot_consumer.py 전체 | - -### REQ-030 (option D Approved→Implemented, change-req) -| AC | Evidence | -|----|----------| -| AC-2/AC-3 deferred 해소(ledger write `*_after` → SnapshotStore) | test_ledger_option_d.py(write-back 전반) + REQ-017/019 evidence | -| AC-1 baseline 스케줄러 회귀 | tests/application/test_preallocation_scheduler.py + snapshot scheduler 회귀 | - -## Evidence -- Test: - - 단위 전체 회귀: `350 passed / 35 skipped`(신규 option D 11 + adapter 3 포함; `::test_sandbox_token_and_submit` deselect). - - 인프라 게이트(PG trex_core_test + RabbitMQ): `385 passed`(pending_cash_delta SQL 포함). - - `ruff check trex_core tests` → All checks passed. -- Logs: `350 passed, 35 skipped, 1 deselected` / `385 passed, 1 deselected ~17.9s`. - -## 성공 기준(dcy-018) 대응 -- S-1(매수/매도 후 cash+quantity 모두 권위값 갱신): test_writeback_updates_snapshot_on_new(+duplicate). 수량은 슬롯 오버레이(유지) + write-back base 신선화. -- S-2(portfolio-hub 응답 누락/불가 시 skip + ledger 정상): test_writeback_skipped_when_after_missing + test_writeback_best_effort_does_not_block_completion. -- S-3(DUPLICATE 중복 적용 없음): test_writeback_applies_on_duplicate_too(절대 권위값 멱등). -- (OQ-4) 추정 현금: test_apply_cash_estimate_overlays_pending_delta + test_estimate_clears_after_writeback. - -## Carried-Over / 비차단 -- 실 데몬 + portfolio-hub#8 full-stack option D e2e(실 `*_after` 응답 → 스냅샷 갱신)는 운영 배포 검증(REL-005 등)에서 확인 — 단위는 InMemoryLedger `*_after` 주입 + adapter 파싱으로 커버. -- 추정 현금은 코어 근사 수수료 기반 — "추정/미정산" 라벨로 커버, write-back 수렴(짧은 구간). - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. option D write-back·추정 오버레이·REQ-030 전환 모두 단위 green(385 passed 회귀, record_trade 반환 타입 변경 호출부 전환 무누락). LIVE/실 portfolio-hub#8 full-stack 은 운영 배포 검증 비차단. diff --git a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/implementation.md b/docs/batches/bat-024_20260609_operator-enter-forced-close-at/implementation.md deleted file mode 100644 index 171fea5..0000000 --- a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/implementation.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-024 Implementation - -- Status: completed -- Batch: BAT-024 - -## Plan Summary -- 운영자 CLI `enter` 에 `--forced-close-at `(절대시간) 옵션을 추가하고, 지정 시 buy intent payload 의 기존 `forced_close_at` 필드에 매핑한다(REQ-032 AC-5/6). 잘못된 형식은 제출 전 거부, 미지정 시 payload 미포함(하위호환). 도메인/계약/forced-close 루프는 불변. - -## Changed Files -- `trex_core/runtime/cli.py` - - `enter` 서브파서에 `--forced-close-at`(dest `forced_close_at`) 인자 추가. - - `_cmd_enter`: 제출 **전** `datetime.fromisoformat` 으로 검증(실패 → `_fail` + rc=1), 정규화된 ISO8601 문자열을 `summary` 와(지정 시) payload `forced_close_at` 에 포함. 미지정 시 payload 키 미포함. -- `tests/runtime/test_cli_operator.py` - - `test_enter_with_forced_close_at_maps_to_payload`(AC-5), `test_enter_without_forced_close_at_omits_field`(AC-6 하위호환), `test_enter_rejects_malformed_forced_close_at_before_submit`(AC-6 형식 거부) 추가. -- `docs/interface-contract.md` - - buy payload `forced_close_at` 항목에 CLI `--forced-close-at` 노출 설명 추가, 운영자 CLI 매핑 표 `enter` 행에 `--forced-close-at` 반영. - -## Execution Log -1. `_build_parser` 의 `enter` 서브파서에 `--forced-close-at` 인자 추가(help: ISO8601 절대시각). -2. `_cmd_enter` 에 제출 전 파싱·검증 + payload 조건부 포함 로직 추가(기존 `datetime` import 재사용). -3. 단위 테스트 3종 추가. -4. interface-contract 정합 갱신. -5. `pytest tests/runtime/test_cli_operator.py` → 20 passed. 전체 회귀 `pytest`(KIS 샌드박스 env 테스트 deselect) → 353 passed / 35 skipped. `ruff check` → All checks passed. - -## Validation -- 단위: `tests/runtime/test_cli_operator.py` 20 passed(신규 3종 포함). -- 회귀: 전체 단위 353 passed, 35 skipped(인프라 게이트 `TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL` 미설정), 1 deselected(KIS 샌드박스 env, 본 batch 무관). -- lint: `ruff check trex_core/runtime/cli.py tests/runtime/test_cli_operator.py` All passed. - -## Remaining Risks -- naive(타임존 없는) timestamp 입력 시 핸들러 `_opt_dt`(`datetime.fromisoformat`) 규약을 그대로 따른다 — CLI 는 노출·형식 검증만 담당하고 타임존 정규화는 도입하지 않음(범위 밖). 운영자가 KST 의도를 명확히 하려면 오프셋 포함(`+09:00`) 권장(help 예시에 명시). -- 실 데몬 e2e(enter → admitted 슬롯 `forced_close_at` 설정 → REQ-037 만기 매도)는 verification 에서 다룬다. diff --git a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/index.md b/docs/batches/bat-024_20260609_operator-enter-forced-close-at/index.md deleted file mode 100644 index 7ae9b47..0000000 --- a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-024: 운영자 `enter` 시간 기반 자동청산 옵션 — `--forced-close-at` (절대시간) 노출 (issue #17) - -- Status: released -- Profile: batch-lite -- Source Discovery: dcy-016 (issue #17 change-req) -- Included REQ: REQ-032 (CHG-20260609-01) -- Excluded REQ: 없음 - -## Summary -- issue #17(`trex-core enter` 에 시간 기반 자동청산 옵션 부재) 를 batch-lite 로 delivery 한다. `enter` 는 `--stop-loss`/`--take-profit` 만 노출하고 `forced_close_at` 을 넣을 인자가 없어, 시간 기반 청산은 별도 수동/스케줄 `exit` 가 필요했다. -- 핵심: `trex_core/runtime/cli.py` `_cmd_enter` 에 **`--forced-close-at ` (절대시간)** 옵션 추가 + buy intent payload 에 파싱된 `forced_close_at` 매핑. buy intent 계약(REQ-007/021, 핸들러가 이미 `forced_close_at` optional 수용)·도메인(`Slot.forced_close_at`, REQ-066)·forced-close 만기 매도 루프(REQ-037)는 **이미 구현·불변** — 본 batch 는 CLI 노출 갭만 보완한다. 상대시간(`--hold-minutes`)은 미채택(사용자 결정). - -## Scope -- In Scope: - - `_cmd_enter` 에 `--forced-close-at` 인자 추가(절대시간, ISO8601 파싱) + 잘못된 형식 제출 전 거부 (REQ-032 AC-5/6) - - buy intent payload 에 지정 시 `forced_close_at` 포함, 미지정 시 미포함(하위호환) - - `docs/interface-contract.md` enter 옵션 표 + payload `forced_close_at` 노출 정합 - - `tests/runtime/test_cli_operator.py` enter `--forced-close-at` payload 매핑·형식 거부·미지정 회귀 -- Out of Scope: - - 상대시간 옵션(`--hold-minutes`) — 미채택 - - buy intent 계약/도메인/forced-close 루프 변경(이미 구현·불변) - - `exit`/`update_thresholds` 의 forced_close_at 노출(별도 주제) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite with no architecture impact) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Profile **batch-lite**: 단일 파일(`cli.py`) 국소 변경 + 테스트/문서, 신규 계약/도메인/런타임 흐름 없음(기존 `forced_close_at` 경로 재사용), DB/스레드/마이그레이션 없음 → **Design Gate: no**(design.md 불요). -- REQ-032 는 CHG-20260609-01 로 **Approved**(신규 AC-5/6 미구현) 상태. 본 batch release-candidate + confirm-req-implemented 로 **Implemented** 복귀 예정. -- 발견 근거: GitHub issue #17. diff --git a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/planning.md b/docs/batches/bat-024_20260609_operator-enter-forced-close-at/planning.md deleted file mode 100644 index 90fe5d0..0000000 --- a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/planning.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-024 Planning - -- Status: confirmed -- Batch: BAT-024 -- Profile: batch-lite - -## Included REQ -- REQ-032 (CHG-20260609-01) — 운영자 `enter` `--forced-close-at` (절대시간) 옵션 - -## Delivery Plan -1. `trex_core/runtime/cli.py` `_build_parser` 의 `enter` 서브파서에 `--forced-close-at`(dest `forced_close_at`, ISO8601 문자열) 인자 추가. -2. `_cmd_enter` 에서 `args.forced_close_at` 가 주어지면 `datetime.fromisoformat` 으로 파싱(잘못된 형식은 제출 전 `_fail` + 비정상 종료), buy intent payload 에 `forced_close_at`(ISO8601 문자열)으로 포함. 미지정 시 payload 에 키 미포함(하위호환). -3. `docs/interface-contract.md` 의 enter 옵션 목록/매핑에 `--forced-close-at` 추가. -4. `tests/runtime/test_cli_operator.py` 테스트 추가/갱신. - -## Dependencies -- Internal: - - buy intent 핸들러 `_handle_buy_intent`(messaging_handlers.py:87) 가 이미 `forced_close_at` 수용 — 변경 불필요. - - 도메인 `Slot.forced_close_at`(REQ-066), forced-close 만기 매도 루프(REQ-037) — 변경 불필요. -- External: - - 없음. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 단일 파일(`cli.py`) 국소 변경. 신규 계약 필드/도메인/런타임 흐름 없음(기존 `forced_close_at` 경로 재사용). 구조·인터페이스 구조 변경 없음(옵션 additive). 설계 판단이 change-req(CHG-20260609-01) AC-5/6 에 완비. - -## Milestones -1. CLI 인자 + payload 매핑 + 형식 검증 구현. -2. 단위 테스트(payload 매핑/형식 거부/미지정 회귀) green + interface-contract 정합. - -## Risks -- R-1: timestamp 파싱 모호성(타임존 없는 naive vs aware). 대응: `datetime.fromisoformat` 로 파싱하되 payload 에는 입력 문자열을 ISO8601 로 전달(핸들러 `_opt_dt` 와 동일 규약). naive/aware 처리는 기존 핸들러 규약을 따른다 — 본 batch 는 CLI 노출만 담당. -- R-2: 잘못된 형식이 데몬까지 가서 admission 실패로 나타나는 회귀. 대응: CLI 제출 **전** 파싱 검증 후 거부(AC-6). diff --git a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/verification.md b/docs/batches/bat-024_20260609_operator-enter-forced-close-at/verification.md deleted file mode 100644 index 72a5626..0000000 --- a/docs/batches/bat-024_20260609_operator-enter-forced-close-at/verification.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-024 Verification - -- Status: approved -- Batch: BAT-024 -- Profile: batch-lite - -## Acceptance Mapping - -### REQ-032 (CHG-20260609-01) — 운영자 `enter --forced-close-at` - -| AC | 검증 | Evidence | -|----|------|----------| -| AC-5: `--forced-close-at ` → buy intent payload `forced_close_at` 매핑 | `enter PF-1 --forced-close-at 2026-06-09T15:20:00+09:00 --yes` → payload `forced_close_at` 동일값 | `tests/runtime/test_cli_operator.py::test_enter_with_forced_close_at_maps_to_payload` → PASS | -| AC-6a: 미지정 시 payload 미포함(하위호환) | `enter PF-1 --yes` → payload 에 `forced_close_at` 키 없음 | `::test_enter_without_forced_close_at_omits_field` → PASS | -| AC-6b: 잘못된 형식 제출 전 거부 | `enter PF-1 --forced-close-at notatime --yes` → rc=1, RPC 미호출 | `::test_enter_rejects_malformed_forced_close_at_before_submit` → PASS | - -### 회귀 (기존 AC-1~4 + enter/exit/universe/slots 불변) -- `tests/runtime/test_cli_operator.py` 20 passed(기존 17 + 신규 3) — pause/resume/universe/slots/enter/exit/안전장치 전부 green. -- 전체 단위 회귀 353 passed / 35 skipped / 1 deselected. - -## 다운스트림 경로 (이미 구현·검증, 본 batch 무변경) -- buy intent 핸들러가 payload `forced_close_at` 수용: `trex_core/application/messaging_handlers.py:87`(`_opt_dt`) — REQ-007/021, 기존 구현. -- 도메인 `Slot.forced_close_at`(REQ-066) + forced-close 만기 매도 루프(REQ-037, BAT-010/011 검증 완료). 본 batch 는 CLI 노출만 추가하므로 다운스트림 재검증 불요. - -## 검증 명령/결과 -- `pytest tests/runtime/test_cli_operator.py -q` → **20 passed**. -- `pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → **353 passed, 35 skipped, 1 deselected**. -- `ruff check trex_core/runtime/cli.py tests/runtime/test_cli_operator.py` → All checks passed. - -## Baseline 문서 동기화 -- `docs/interface-contract.md` — buy payload `forced_close_at` 의 CLI `--forced-close-at` 노출 + 운영자 CLI 매핑 표 `enter` 행 갱신. (runtime-flows 는 흐름 무변경 — forced-close 경로 기존 그대로, 추가 갱신 불요.) - -## 검증 결과 요약 -- AC-5/6 전부 단위 PASS, 기존 AC·명령 회귀 green, baseline 정합 완료. -- **Ready for Release: true.** - -## 미검증/한계 -- 실 데몬 + LIVE/paper full-stack e2e(enter `--forced-close-at` → admitted 슬롯 `forced_close_at` 설정 → REQ-037 만기 시 시장가 청산)는 본 batch 의 책임 경계(=올바른 payload 필드 방출) 밖이며, 다운스트림(REQ-037)은 BAT-010/011 에서 이미 검증됨. CLI 책임은 payload 매핑 단위로 직접 커버. -- pytest 인프라 게이트(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL`)는 본 변경과 무관(CLI 는 RPC 주입 단위 테스트, DB/브로커 불요) — skipped 35 는 본 batch 범위 밖. diff --git a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/design.md b/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/design.md deleted file mode 100644 index 409d711..0000000 --- a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-025 Design - -- Status: confirmed -- Batch: BAT-025 - -## 설계 목표 -- 슬롯 자원을 진입 횟수 기준으로 고르게 재사용하고 idle 소진 시 closed 슬롯을 재할당하도록, slot-scoped `entry_count` 를 도메인 정본에 영속하고 buy admission 후보를 idle∪closed 로 확장한다. 동시성·계약·기존 동작(회귀) 안전을 보장한다. - -## 1. 슬롯 도메인 필드 (REQ-002) -- `Slot.entry_count: int = 0`(take_profit_price 뒤, created_at 앞). `EXPECTED_SLOT_FIELDS` 에 `"entry_count"` 추가(같은 위치). -- slot-scoped 누적 진입 횟수. `daily_symbol_entries`(symbol-scoped, AC-3)와 무관 — 본 batch 에서 후자 미변경. - -## 2. 상태 머신 전이 (REQ-002) -- `_BASE_TRANSITIONS` 에 `(CLOSED, PENDING_BUY)` 추가(#9). base 7→8 tuple, `ALLOWED_TRANSITIONS` 14→15. -- docstring "6 states + 9 base allowed transitions" 갱신(`* → error` family 는 별도 집합이므로 base 카운트는 tuple 8 + 주석 #9). - -## 3. 전이 필드 계산 (REQ-002 / `_apply_transition_fields`) -- **closed→pending_buy clean-slate**(limit_price lifecycle 분기 앞): `to_state is PENDING_BUY and slot.status is CLOSED` 면 점유 필드(symbol/qty/avg_price/limit_price/allocated_capital/order_id/strategy_id/broker_order_id/portfolio_id/sell_strategy_id/forced_close_at/stop_loss_price/take_profit_price)를 None 으로 리셋한 `slot` 으로 교체 → 이전 사이클 점유가 새 진입에 누수되지 않음. `entry_count` 는 리셋 대상 제외(누적 보존). -- 이후 generic `replace` 에서 caller-supplied 값으로 새 진입 필드 적용(None 이면 keep — clean-slate 후이므로 None 유지). -- **entry_count +1**: `new_entry_count = slot.entry_count + (1 if to_state is PENDING_BUY else 0)` → idle→ 와 closed→ 모두 +1. 그 외 전이·closed→idle 복구는 보존. `replace(..., entry_count=new_entry_count)`. - -## 4. 후보 selector (REQ-006) -- `SlotRepository.find_allocatable_slot(trading_date)` Protocol 추가: idle∪closed 후보를 `entry_count asc → updated_at asc → slot_id` 정렬로 1건(없으면 None). caller 가 advisory lock+재시도로 마지막 후보 경합 처리. -- PG: `SELECT {_SLOT_COLUMNS} FROM daily_slot WHERE trading_date=%s AND status IN ('idle','closed') ORDER BY entry_count ASC, updated_at ASC, slot_id ASC LIMIT 1`. 기존 `find_idle_slot` 와 동일하게 FOR UPDATE 미사용(동시성은 transit advisory lock). -- in-memory: idle/closed 후보 중 `min(key=(entry_count, updated_at or min, str(slot_id)))`. - -## 5. persistence flat 매핑 (REQ-006) -- `_SLOT_COLUMNS` 끝에 `entry_count`(인덱스 18, 총 19컬럼). `update_slot` SET 에 `entry_count=%s` + params. `_row_to_slot` 에 `entry_count=int(row[18]) if row[18] is not None else 0`. -- 마이그레이션 `008_add_slot_entry_count.sql`: `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS entry_count INTEGER NOT NULL DEFAULT 0`(additive·멱등, 004 패턴). - -## 6. admission (REQ-007) -- `_admit_buy` 재시도 루프: `find_idle_slot` → `find_allocatable_slot`. transit from_state 하드코딩 `IDLE` → `slot.status`(후보가 idle 이면 idle→, closed 면 closed→ 직접 재할당). 후보 없으면 `no_idle_slot` 거부. `SlotStateMismatchError` 시 다음 후보 재시도(경합 보존). 멱등(intent_log dedup) 불변. - -## 7. baseline 문서 (REQ-009) -- `data-model.md`: daily_slot `entry_count` 필드, Lifecycle `closed→pending_buy`, State Rules(전이 패밀리 8 base + clean-slate). 두 카운터 구분. -- `runtime-flows.md`: buy admission `find_allocatable_slot`(idle∪closed, entry_count 정렬)·closed 재할당, transit_slot 흐름에 `→pending_buy 시 entry_count +1` 단계. - -## 테스트 설계 -- domain: 전이 카운트(14→15, closed→pending_buy allowed/disallowed 재배치); 계약 가드(EXPECTED_SLOT_FIELDS); entry_count 증분(idle→/non-buy 미증가)·closed→idle 복구 보존; closed→pending_buy clean-slate(미공급 필드 클리어)·+1; selector(none/idle-only/closed 포함/entry_count 오름차순). -- application: closed 재할당 admit(idle 소진 시), idle 우선(closed 보다), 양쪽 소진 reject; 동시성 경합 회귀(test_intent_concurrency). -- PG 게이트: 마이그레이션/드리프트(`EXPECTED_SLOT_FIELDS`↔information_schema) — entry_count 자동 반영. -- 회귀: 전체 단위(flat 매핑 추가로 인한 slot read/write 무누락) green. diff --git a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/implementation.md b/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/implementation.md deleted file mode 100644 index 7415187..0000000 --- a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/implementation.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-025 Implementation - -- Status: completed -- Batch: BAT-025 - -## 구현 요약 -- dcy-019(slot-scoped entry_count + closed 슬롯 직접 재할당, issue #18)을 전부 change-req 로 구현(신규 REQ 없음). 기존 `daily_symbol_entries`(symbol-scoped) 미변경. - -## 변경 내역 - -### REQ-002 — 슬롯 도메인 필드 + 상태 머신 + 전이 로직 -- `trex_core/domain/slot/models.py`: `Slot.entry_count: int = 0`(take_profit_price 뒤) + `EXPECTED_SLOT_FIELDS` 에 `"entry_count"` 추가. -- `trex_core/domain/slot/state.py`: `_BASE_TRANSITIONS` 에 `(CLOSED, PENDING_BUY)` 추가(#9, base tuple 7→8, ALLOWED_TRANSITIONS 14→15). docstring "9 base allowed transitions" 갱신. -- `trex_core/domain/slot/service.py` `_apply_transition_fields`: closed→pending_buy clean-slate 점유 초기화 블록(limit_price lifecycle 앞) + `new_entry_count = slot.entry_count + (1 if to_state is PENDING_BUY else 0)` + generic `replace(..., entry_count=new_entry_count)`. - -### REQ-006 — 마이그레이션 + persistence + selector -- `trex_core/adapters/persistence/sql/008_add_slot_entry_count.sql`(신규): `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS entry_count INTEGER NOT NULL DEFAULT 0`(additive·멱등). -- `trex_core/adapters/persistence/postgresql.py`: `_SLOT_COLUMNS` +`entry_count`(인덱스 18); `update_slot` SET `entry_count=%s` + params; `_row_to_slot` `entry_count=int(row[18]) if row[18] is not None else 0`; 신규 `find_allocatable_slot`(idle∪closed, `ORDER BY entry_count ASC, updated_at ASC, slot_id ASC LIMIT 1`, FOR UPDATE 미사용). -- `trex_core/domain/slot/repository.py`: `find_allocatable_slot` Protocol 추가. -- `trex_core/domain/slot/in_memory_repository.py`: `find_allocatable_slot`(idle/closed 후보 `min(key=(entry_count, updated_at or min, str(slot_id)))`). - -### REQ-007 — buy admission 일반화 -- `trex_core/application/intents.py` `_admit_buy`: `find_idle_slot` → `find_allocatable_slot`, transit from_state `SlotState.IDLE` → `slot.status`(closed 후보 직접 재할당). 후보 소진 `no_idle_slot` 거부·`SlotStateMismatchError` 재시도 루프·멱등 불변. - -### REQ-003 — 테스트 계약 정합 -- `tests/domain/slot/test_state.py`: `closed→pending_buy` allowed 이동, disallowed 에서 제거, `ALLOWED_TRANSITIONS` 크기 14→15. -- 신규 `tests/domain/slot/test_slot_entry_count.py`: entry_count 증분(idle→/non-buy 미증가/closed→idle 보존), closed→pending_buy clean-slate+ +1, `find_allocatable_slot` 정렬(none/idle-only/closed 포함/entry_count 오름차순). -- 신규 `tests/application/test_buy_admission_reallocation.py`: closed 재할당 admit / idle 우선 / 양쪽 소진 reject. -- `tests/domain/slot/test_contract.py`·`tests/adapters/persistence/test_migration_and_drift.py`: `EXPECTED_SLOT_FIELDS` 갱신으로 entry_count 자동 검증. - -### REQ-009 — baseline 문서 -- `docs/data-model.md`: daily_slot `entry_count` 필드(두 카운터 구분), Lifecycle `closed→pending_buy`, State Rules(전이 패밀리 8 base 포함 + closed→pending_buy clean-slate). -- `docs/runtime-flows.md`: buy admission `find_allocatable_slot`(idle∪closed, entry_count 정렬)·closed 직접 재할당, transit_slot 흐름 `→pending_buy 시 entry_count +1` 단계 추가. - -## 테스트 -- 신규 단위: `test_slot_entry_count.py`(12건), `test_buy_admission_reallocation.py`(3건). -- 수정: `test_state.py`(전이 카운트/허용집합). - -## 검증 실행 -- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → `366 passed / 35 skipped / 1 deselected`. (deselect 한 KIS sandbox 테스트는 LIVE `.env` 가 운영 KIS URL 을 가리켜 사전 환경 실패 — 본 batch 무관.) -- `ruff check trex_core tests` → All checks passed. -- PG 게이트(마이그레이션 008 round-trip + EXPECTED_SLOT_FIELDS 드리프트): 라이브 runtime DSN 미사용(`pg_clean` truncate 파괴적 회피). test DSN(`TREX_POSTGRESQL_TEST_DSN`) 환경에서 자동 검증 — 마이그레이션은 004 의 검증된 additive·멱등 패턴 동일, 드리프트 가드는 `EXPECTED_SLOT_FIELDS`(entry_count 포함)↔information_schema 로 자동 강제. diff --git a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/index.md b/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/index.md deleted file mode 100644 index 4e8c0d0..0000000 --- a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/index.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-025: slot-scoped entry_count + closed 슬롯 직접 재할당 (dcy-019) - -- Status: released -- Profile: standard -- Source Discovery: dcy-019 -- Included REQ: REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01) - -## Summary -- dcy-019(slot 진입 횟수 필드 + idle∪closed 후보 중 최저 진입 우선 선택 + closed 직접 재할당, issue #18)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-019 OQ-1/2/3 결정대로 **전부 change-req, 신규 REQ 없음**. -- 핵심: daily_slot/`Slot` 에 slot-scoped `entry_count`(기본 0) 필드 + 멱등 마이그레이션 008(REQ-002/006), 상태 머신 `closed→pending_buy` 신규 전이(8→9 base, ALLOWED_TRANSITIONS 14→15)(REQ-002), buy admission 의 후보를 idle∪closed 로 확장하고 `entry_count→updated_at→slot_id` 결정적 정렬로 선택하는 `find_allocatable_slot` selector(REQ-006) + `_admit_buy` 일반화(REQ-007), `→pending_buy`(idle→/closed→) 시 entry_count +1 및 closed→pending_buy clean-slate 점유 초기화(REQ-002). 기존 `daily_symbol_entries`(symbol-scoped 재진입 카운터)와 분리·미변경. - -## Scope -- In Scope: - - `Slot.entry_count`(기본 0) + `EXPECTED_SLOT_FIELDS` + 멱등 마이그레이션 008 + persistence flat 매핑 (REQ-002/006) - - 상태 머신 `closed→pending_buy` 신규 전이 + `_apply_transition_fields` clean-slate + entry_count +1 (REQ-002) - - `find_allocatable_slot`(idle∪closed, entry_count→updated_at→slot_id) selector + in-memory 동등 (REQ-006) - - `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status (REQ-007) - - 테스트 계약 정합(전이 카운트·필드셋·신규 단위) (REQ-003) - - data-model/runtime-flows baseline 정합 (REQ-009) -- Out of Scope: - - `daily_symbol_entries`(symbol-scoped 재진입 카운터) 제거/변경 — 별도 REQ-002 change-req 트랙(후속) - - 운영자 CLI/스냅샷에 slot `entry_count` 노출 (dcy-019 §5 제외) - - 슬롯별 PnL 집계, 전략 모듈에 선택 정책 노출, preallocation 총량/스케줄 변경 - - snapshot `available_slots`(=idle_count) capacity 의미 변경 (dcy-019 범위 밖 — 보수적 under-report 유지) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-019). OQ-1 (a) daily_slot 단독 / OQ-2 entry_count→updated_at→slot_id / OQ-3 closed→pending_buy 직접 전이·→pending_buy 시 +1 Answered. -- additive·하위호환(NFR-3): 마이그레이션 008 기본 0, closed 후보 부재 시 기존 idle-only 동작 보존(회귀 green). -- 동시성(NFR-1): selector find 쿼리는 기존 `find_idle_slot` 와 동일하게 FOR UPDATE 미사용 — claim 경합은 `transit_slot` advisory lock + `SlotStateMismatchError` 재시도 루프로 직렬화(중복 카운트 없음). -- 신규 slot `entry_count` 와 `daily_symbol_entries.entry_count` 는 키·목적이 다른 별개 카운터(R-1) — data-model 에 구분 기록. -- PG 게이트 마이그레이션/드리프트 테스트는 라이브 runtime DSN 미사용(`pg_clean` truncate 파괴적) — test DSN 환경에서 자동 검증. diff --git a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/planning.md b/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/planning.md deleted file mode 100644 index 7e994b9..0000000 --- a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/planning.md +++ /dev/null @@ -1,49 +0,0 @@ -# BAT-025 Planning - -- Status: confirmed -- Batch: BAT-025 -- Profile: standard - -## Included REQ -- REQ-002 (Interface, change-req): `Slot.entry_count` 필드 + `EXPECTED_SLOT_FIELDS`, 상태 머신 `closed→pending_buy` 신규 전이, `_apply_transition_fields` clean-slate + entry_count +1. -- REQ-003 (Testing, change-req): 전이 카운트(14→15)·필드셋 계약 가드 정합 + entry_count/reallocation 신규 단위 테스트. -- REQ-006 (Data, change-req): 멱등 마이그레이션 008 + flat slot 매핑 + `find_allocatable_slot` selector. -- REQ-007 (Interface, change-req): `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status. -- REQ-009 (Documentation, change-req): data-model/runtime-flows baseline 정합. - -## Delivery Plan -1. REQ-002 도메인: `models.py` `Slot.entry_count`(기본 0)+`EXPECTED_SLOT_FIELDS`; `state.py` `_BASE_TRANSITIONS` 에 `closed→pending_buy`; `service.py` `_apply_transition_fields` closed→pending_buy clean-slate 점유 초기화 + 모든 `→pending_buy` 시 `entry_count`+1. -2. REQ-006 영속화: `sql/008_add_slot_entry_count.sql`(ADD COLUMN IF NOT EXISTS, 기본 0); `postgresql.py` `_SLOT_COLUMNS`/`update_slot`/`_row_to_slot` +entry_count; `find_allocatable_slot`(idle∪closed ORDER BY entry_count,updated_at,slot_id LIMIT 1); `repository.py` Protocol + `in_memory_repository.py` 동등. -3. REQ-007 admission: `intents.py` `_admit_buy` selector 전환 + from_state=slot.status(closed 후보 직접 재할당). -4. REQ-003 테스트: `test_state.py`(전이 14→15), `test_contract.py`(entry_count 포함은 EXPECTED 갱신으로 자동), 신규 `test_slot_entry_count.py`(증분/보존/clean-slate/selector 정렬) + `test_buy_admission_reallocation.py`(closed admit/idle 우선/양쪽 소진 reject). -5. REQ-009 문서: `data-model.md`(entry_count 필드/lifecycle/state rules), `runtime-flows.md`(admission selector/transit_slot entry_count step). -6. 단위 회귀 + ruff + (test DSN 가용 시) PG 게이트 마이그레이션/드리프트. - -## Dependencies -- Internal: - - dcy-001/BAT-001 slot 도메인(state/models/service/repository, REQ-002/003) — 변경 대상. - - dcy-002/BAT-002 PostgreSQL 영속화 + intent admission(REQ-006/007) — 변경 대상. - - dcy-014/BAT-018 trading_date 경계 preallocation — entry_count 는 일자 범위 slot row 기준(불변, A-1/A-2). -- External: - - 없음(전부 내부 도메인/스키마). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) 상태 머신 불변식 변경(8→9 base 전이) + closed→pending_buy clean-slate 의미는 도메인 계약(REQ-001/002 NFR-2)·계약 가드·기존 closed→idle 리셋과 교차 → 설계에서 전이/리셋 경계 고정. (b) admission 후보 확장(idle∪closed)이 마지막 슬롯 경합 동시성(advisory lock+재시도)·중복 카운트 없음을 깨지 않도록 selector(FOR UPDATE 미사용, find_idle_slot 동등)·증가 시점(단일 transit) 규칙을 못박아야 안전. (c) daily_slot 컬럼 추가가 `EXPECTED_SLOT_FIELDS`·flat read/write 순서를 깨면 광범위 회귀(R-3) → 모델·계약·persistence·data-model 동일 묶음 정합. → design.md 동일 턴 scaffold. - -## Milestones -1. M1 — REQ-002 도메인(field/transition/service) + 전이·계약 가드 green -2. M2 — REQ-006 마이그레이션 008 + persistence 매핑 + selector + in-memory 동등 -3. M3 — REQ-007 `_admit_buy` 일반화 + reallocation 단위 green -4. M4 — REQ-003 신규 단위 테스트 + REQ-009 문서 정합 -5. M5 — 단위 전체 회귀 + ruff (+ test DSN PG 게이트) - -## Risks -- R-1 (Low): 신규 slot `entry_count` ↔ `daily_symbol_entries.entry_count` 이름 혼동. 대응: 테이블/키/목적/증가시점 data-model 구분, 후자 미변경(REQ-002 AC-3 회귀 보존). -- R-2 (Medium): closed 후보 포함으로 동시성/점유 리셋 어긋남 → 잘못된 재사용·상태 오염. 대응: advisory lock + 단일 transit 내 상태/점유/카운트 갱신, closed→pending_buy 가 기존 closed→idle 리셋 의미 재사용, 경합 회귀 테스트. -- R-3 (Medium): daily_slot 컬럼 추가가 `EXPECTED_SLOT_FIELDS`/flat 순서 깨면 광범위 회귀. 대응: 모델·계약·persistence(_SLOT_COLUMNS/row index)·data-model 동일 묶음 + 계약 가드 green. - -## Notes -- 1차 게이트: 단위 전체 회귀 green(entry_count 추가로 인한 flat 매핑/계약 무누락). -- 결정론(NFR-2): in-memory selector(entry_count→updated_at→slot_id) + PG ORDER BY 동일 키. -- PG 마이그레이션/드리프트는 라이브 runtime DSN 미사용(파괴적 truncate 회피) — test DSN 자동 검증. diff --git a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/verification.md b/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/verification.md deleted file mode 100644 index b9a3c36..0000000 --- a/docs/batches/bat-025_20260609_slot-entry-count-and-closed-reallocation/verification.md +++ /dev/null @@ -1,63 +0,0 @@ -# BAT-025 Verification - -- Status: verified -- Batch: BAT-025 - -## Acceptance Mapping - -### REQ-002 (entry_count 필드 + closed→pending_buy 전이 + 전이 로직, change-req) -| AC | Evidence | -|----|----------| -| `Slot.entry_count` 가 `EXPECTED_SLOT_FIELDS` 에 포함 | tests/domain/slot/test_contract.py::test_slot_fields_match_expected_contract | -| 6 상태 / 9 base 전이(`closed→pending_buy`) | tests/domain/slot/test_state.py::test_allowed_transition[CLOSED-PENDING_BUY] / ::test_allowed_transitions_set_size(15) | -| `→pending_buy` 시 entry_count +1(idle→/closed→), 그 외 보존 | tests/domain/slot/test_slot_entry_count.py::TestEntryCount(증분/non-buy/closed→idle 보존) | -| closed→pending_buy clean-slate 점유 초기화 | ::TestClosedReallocation::test_closed_to_pending_buy_clean_slate_and_bumps_count / ::test_closed_to_pending_buy_clears_prior_position_when_not_supplied | - -### REQ-003 (테스트 계약 정합, change-req) -| AC | Evidence | -|----|----------| -| 전이 집합 테스트 `closed→pending_buy` allowed + 크기 15 | tests/domain/slot/test_state.py | -| 컬럼 계약 가드 entry_count 포함 | tests/domain/slot/test_contract.py | -| entry_count/reallocation 신규 단위 | tests/domain/slot/test_slot_entry_count.py, tests/application/test_buy_admission_reallocation.py | - -### REQ-006 (마이그레이션 + persistence + selector, change-req) -| AC | Evidence | -|----|----------| -| daily_slot `entry_count` 컬럼(마이그레이션 008, 멱등) | sql/008_add_slot_entry_count.sql + tests/adapters/persistence/test_migration_and_drift.py(test DSN 게이트, EXPECTED_SLOT_FIELDS↔information_schema 자동) | -| `find_allocatable_slot`(idle∪closed, entry_count→updated_at→slot_id) | tests/domain/slot/test_slot_entry_count.py::TestAllocatableSelector(none/idle-only/closed 포함/오름차순) | -| flat slot 매핑(+entry_count) round-trip | tests/adapters/persistence/test_migration_and_drift.py(test DSN) + in-memory selector 단위 | - -### REQ-007 (`_admit_buy` 일반화, change-req) -| AC | Evidence | -|----|----------| -| idle 소진 시 closed 슬롯으로 admit | tests/application/test_buy_admission_reallocation.py::test_admits_into_closed_slot_when_no_idle_remains | -| idle 우선(closed 보다) | ::test_prefers_idle_over_closed | -| idle·closed 모두 소진 → no_idle_slot 거부 | ::test_rejects_when_no_idle_and_no_closed | -| 마지막 슬롯 경합 동시성 회귀 | tests/application/test_intent_concurrency.py 전체 | - -### REQ-009 (baseline 문서 정합, change-req) -| AC | Evidence | -|----|----------| -| runtime-flows admission selector/transit entry_count step | docs/runtime-flows.md(코드-문서 diff 대조) | -| data-model daily_slot entry_count/lifecycle/state rules | docs/data-model.md(코드-문서 diff 대조) | - -## Evidence -- Test: - - 단위 전체 회귀: `366 passed / 35 skipped / 1 deselected`(신규 entry_count 12 + reallocation 3 포함; `::test_sandbox_token_and_submit` deselect — LIVE `.env` KIS URL 환경 의존, 본 batch 무관). - - `ruff check trex_core tests` → All checks passed. -- Logs: `366 passed, 35 skipped, 1 deselected in 1.13s`. - -## 성공 기준(dcy-019) 대응 -- S-1(idle 없고 closed 있을 때 closed 로 admit, 이전엔 no_idle_slot 거부): test_admits_into_closed_slot_when_no_idle_remains. -- S-2(`entry_count` 최소 선택 + tie-break 결정적): TestAllocatableSelector::test_orders_by_entry_count_ascending + test_prefers_idle_over_closed. -- S-3(할당 전이 시 정확히 +1, 동시 경합 중복 없음, `daily_symbol_entries` 회귀 green): TestEntryCount::test_increments_on_each_pending_buy + test_intent_concurrency.py + test_service.py::TestClosedToIdleRecovery(daily_symbol_entries 보존). - -## Carried-Over / 비차단 -- PG 게이트(마이그레이션 008 round-trip + 드리프트)는 라이브 runtime DSN 미사용(파괴적 truncate 회피) → test DSN 환경에서 자동 검증. 마이그레이션은 004 의 검증된 additive·멱등 패턴과 동일하고, 드리프트 가드는 `EXPECTED_SLOT_FIELDS`(entry_count 포함)↔information_schema 로 강제되어 코드 단위로 동등 보장. -- 실 데몬 + 실 PG full-stack 의 closed 재할당 e2e 는 운영 배포 검증(후속 REL)에서 확인 — 단위(in-memory repo + IntentService)로 admission/전이/카운트 커버. -- snapshot `available_slots`(=idle_count) 는 dcy-019 범위 밖(§5 제외)이라 closed 가용분을 보수적으로 under-report — admission 자체는 closed 재할당을 수행. 필요 시 후속 change-req 후보. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. entry_count 필드/closed→pending_buy 전이/selector/admission 일반화 전부 단위 green(366 passed 회귀, flat 매핑·계약 가드 무누락, 동시성 경합 회귀 보존). PG 게이트는 test DSN 자동 검증(라이브 DSN 비파괴 정책). LIVE full-stack 은 운영 배포 검증 비차단. diff --git a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/index.md b/docs/batches/bat-026_20260609_config-dir-env-and-config-path/index.md deleted file mode 100644 index e61e562..0000000 --- a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-026: config 디렉터리 기반 설정 관리 — `--config-path`/`--env-file` + config-dir `.env` 공용화 (trex-orb-v1 정합) - -- Status: released -- Profile: batch-lite -- Source Discovery: 없음 (trex-orb-v1 설정 관리 정합 — change-req 주도) -- Included REQ: REQ-048 (CHG-20260609-01), REQ-010 (CHG-20260609-01) -- Excluded REQ: 없음 - -## Summary -- trex-orb-v1의 config 디렉터리 기반 설정 관리(`--config ` + `--config-path ` + config-dir `.env`)를 trex-core 에 이식한다. -- 핵심: `resolve_config_toml(name, config_path)`(이름→`/.toml`, `.toml` 자동 부착, **슬래시 포함 값은 하위호환 명시 경로**)(REQ-048), `.env` 를 config-dir 기준 결정적 해석(`--env-file` 우선 → `/.env`)으로 변경 + `config/.env.example` + `.gitignore` `config/*` 허용목록(REQ-010). 데몬·운영자 CLI 양쪽에 `--config-path`/`--env-file` 적용. -- 안전: `from_env()` 의 legacy `.env` 로드(루트 `.env` 폴백)를 유지 → config/.env 부재 시에도 기존 루트 `.env` 로 동작(완전 하위호환). config/.env 존재 시 먼저 로드돼 우선(`override=False`). - -## Scope -- In Scope: - - `resolve_config_toml` + `DEFAULT_CONFIG_DIR` (REQ-048) - - `_load_dotenv(config_path, env_file)` config-dir 결정적 해석 + legacy 폴백 (REQ-010) - - 데몬 `main` argparse `--config-path`/`--env-file` + `from_config(toml_path, env_file)` - - 운영자 CLI `--config-path`/`--env-file` + `_resolve_instance` 통일 - - `.gitignore` `config/*` 허용목록, `config/.env.example` 신규 -- Out of Scope: - - 실제 `.env` → `config/.env` 물리 이동(운영 배포 절차 — 루트 `.env` 폴백으로 무중단) - - TOML/env 레이어링 정책(키 우선순위) 변경 — 기존 유지 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite with no architecture impact) -- Implementation: [verification.md](./verification.md) 에 구현 요약 포함 (batch-lite) -- Verification: [verification.md](./verification.md) - -## Notes -- Profile **batch-lite**: 국소 변경(config.py/daemon.py/cli.py + .gitignore + config 예제). 신규 런타임 흐름·계약 필드 없음(인자·해석 규칙 추가). `--config` 의미 확장은 인터페이스 영향이라 정식 change-req(REQ-048/010 CHG-20260609-01) 경유 → **Design Gate: no**. -- REQ-048/010 은 CHG-20260609-01 로 **Approved**(신규 AC 미구현). 본 batch release-candidate + confirm-req-implemented 로 **Implemented** 복귀. -- 발견 근거: 사용자 요청(trex-orb-v1 설정 관리 형태 정합). diff --git a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/planning.md b/docs/batches/bat-026_20260609_config-dir-env-and-config-path/planning.md deleted file mode 100644 index 8242805..0000000 --- a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/planning.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-026 Planning - -- Status: confirmed -- Batch: BAT-026 -- Profile: batch-lite - -## Included REQ -- REQ-048 (CHG-20260609-01) — `--config` 이름 해석 + `--config-path` + `resolve_config_toml` -- REQ-010 (CHG-20260609-01) — config-dir `.env` 결정적 해석 + `--env-file` + `config/.env.example` + `.gitignore` - -## Delivery Plan -1. `config.py`: `DEFAULT_CONFIG_DIR` + `resolve_config_toml(name, config_path)`(슬래시 포함=명시 경로 하위호환). `_load_dotenv(config_path=None, env_file=None)` — `--env-file` 우선 → `/.env` → (config 컨텍스트 없으면) legacy `find_dotenv(usecwd=True)` 폴백. `from_config(toml_path, env_file=None)` 가 config-dir `.env` 선로드 후 `from_env()`. -2. `daemon.py main`: `--config-path`/`--env-file` argparse + `resolve_config_toml` + `from_config(path, env_file)`. -3. `cli.py`: `_add_common` 에 `--config-path`/`--env-file`; `_resolve_instance` 가 `resolve_config_toml` 경유; `main` 이 from_env 전 config-dir `.env` 선로드. -4. `.gitignore` `config/*` 허용목록(`*.example.toml`/`.env.example`), `config/.env.example` 신규. -5. 테스트: resolve_config_toml(이름/.toml/config-path/경로형), _load_dotenv(env-file/config-dir/override/부재/CWD무관), daemon·cli 인자. - -## Dependencies -- Internal: `from_env()` 의 legacy `.env` 로드 유지가 하위호환의 핵심. -- External: 없음. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 국소 변경(3개 런타임 파일 + .gitignore + config 예제). 신규 계약 필드/런타임 흐름 없음. `--config` 의미 확장은 change-req(CHG-20260609-01)로 이미 정합. - -## Milestones -1. config.py/daemon.py/cli.py + .gitignore + config/.env.example 구현. -2. 단위(resolve/dotenv/인자) green + 전체 회귀 + ruff. - -## Risks -- R-1: config/.env 부재 시 키 미로드로 기동 실패. 대응: `from_env()` legacy 루트 `.env` 폴백 유지 → 무중단(완전 하위호환). -- R-2: `--config` 경로형 사용처 회귀. 대응: 슬래시 포함 값 명시 경로 폴백 + 회귀 테스트. diff --git a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/verification.md b/docs/batches/bat-026_20260609_config-dir-env-and-config-path/verification.md deleted file mode 100644 index 0278243..0000000 --- a/docs/batches/bat-026_20260609_config-dir-env-and-config-path/verification.md +++ /dev/null @@ -1,45 +0,0 @@ -# BAT-026 Verification - -- Status: approved -- Batch: BAT-026 -- Profile: batch-lite - -## Acceptance Mapping - -### REQ-048 (CHG-20260609-01) — `--config` 이름 해석 + `--config-path` -| AC | Evidence | -|----|----------| -| AC-7: `--config [--config-path ]` → `/.toml`(.toml 자동 부착) | `tests/runtime/test_config_resolution.py::test_resolve_name_default_dir` / `::test_resolve_appends_toml` / `::test_resolve_with_config_path` → PASS | -| AC-8: 슬래시 포함(경로형) 값은 명시 경로로 그대로 사용(하위호환) | `::test_resolve_path_form_passthrough` → PASS. 라이브: `trex-core universe --config config/test.toml` 정상(name 형태와 동일 결과) | -| AC-9: 운영자 CLI 도 `--config-path` 수용, `_resolve_instance` 동일 규칙 | `::test_cli_resolve_instance_honors_config_path` → PASS. 라이브: `--config test --config-path config` → instance=test 라우팅 | - -### REQ-010 (CHG-20260609-01) — config-dir `.env` + `--env-file` -| AC | Evidence | -|----|----------| -| AC-1(갱신): `/.env`(또는 `--env-file`) 로드 → dsn 반영 | `::test_dotenv_loaded_from_config_dir_cwd_independent` → PASS | -| AC-2: OS env 우선(`override=False`) | `::test_dotenv_os_env_takes_precedence` + 기존 `test_config_dotenv.py::test_os_env_takes_precedence_over_dotenv` → PASS | -| AC-3: `.env` 부재 시 회귀 없음 | `::test_dotenv_absent_is_noop` + 기존 `test_no_dotenv_uses_os_env_only` → PASS | -| AC-4: `.gitignore` `config/*` 무시 + `*.example.toml`/`.env.example` 추적 | `test_instance_config.py::test_gitignore_excludes_instance_toml`(갱신) → PASS | -| AC-6: CWD 무관 결정적 로드 | `::test_dotenv_loaded_from_config_dir_cwd_independent`(다른 CWD 로 chdir 후 config-dir `.env` 로드) → PASS | -| AC-7: `--env-file` 우선 | `::test_dotenv_env_file_wins` → PASS | - -## 하위호환 (R-1/R-2) -- `from_env()` 의 legacy `.env` 로드(루트 `.env` CWD 폴백) 유지 → config/.env 부재에도 기존 루트 `.env` 로 동작. 기존 `test_config_dotenv.py` 6건 전부 green(무회귀). -- 경로형 `--config` 무중단: 라이브 스모크에서 `--config config/test.toml` 와 `--config test --config-path config` 가 **byte 동일 스냅샷** 반환(instance=test). - -## 검증 명령/결과 -- 단위(config): `pytest tests/runtime/test_config_resolution.py test_config_dotenv.py test_instance_config.py -q` → **23 passed**. -- 전체 회귀: `pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → **375 passed, 35 skipped, 1 deselected**. -- lint: `ruff check`(config.py/daemon.py/cli.py + 신규/갱신 테스트) → All checks passed. -- 라이브 스모크: 실행 중 test 데몬에 `trex-core universe --config test --config-path config --json` 와 `--config config/test.toml --json` → 동일 응답(instance=test, 라우팅·.env 폴백 정상). - -## Baseline 문서 동기화 -- `config/.env.example` 신규(공유 인프라 키 + config-dir 해석 헤더). `docs/project-structure.md` 설정 계층 서술은 후속 정합(흐름 변화 없음 — 인자/해석 규칙 추가) — 구조 영향 없음(Design Gate: no). - -## 결과 요약 -- REQ-048 AC-7/8/9 + REQ-010 AC-1/2/3/4/6/7 전부 단위 PASS + 라이브 스모크 무회귀. -- **Ready for Release: true.** - -## 미검증/한계 -- 데몬 진입점(`daemon.py:main`)은 `# pragma: no cover`(프로세스 진입점) — `resolve_config_toml`/`from_config(env_file)` 단위 + CLI 라이브 스모크로 동등 경로 커버. -- 실제 `.env` → `config/.env` 물리 이동은 운영 배포 절차(루트 `.env` 폴백으로 무중단) — 본 batch 범위 밖. diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md deleted file mode 100644 index 0c5c51c..0000000 --- a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-027 Design - -- Status: confirmed (안 B 확정 — 2026-06-10, 사용자 결정) -- Batch: BAT-027 - -## 설계 목표 -- 익명 풀 슬롯 도메인을 자산 1:1 영구 슬롯의 데이터 모델 + 상태머신 토대로 재작성하되, **소비처 연쇄(R-cascade)를 통제**해 각 단계에서 단위 green 을 유지한다. 라이브 데이터는 건드리지 않는다. - -## R-cascade — 실측 영향 범위 (근거) -저장소 grep 기준, 본 배치가 바꾸는 3개 축의 소비처: -- **`idle`/`closed` 상태 참조**: `application/execution.py`, `adapters/persistence/ops_checks.py`, `runtime/cli.py`, `adapters/persistence/postgresql.py`, `sql/001` -- **`entry_count`**: `application/intents.py`, `domain/slot/*`, `adapters/persistence/postgresql.py`, `sql/001`, `sql/008` -- **`SLOT_ADVISORY_LOCK_ID`(전역 락)**: `application/execution.py`, `application/intents.py`, `domain/slot/*`, `adapters/persistence/postgresql.py` -- **슬롯 `trading_date`**: `preallocation_scheduler`, `safety_sell`, `messaging_handlers`, `intents`, `killswitch`, `daemon`, `portfolio`, `config`, `cli` (광범위 — 일일 회계·키 양쪽) - -→ "스키마+상태머신만" 깨끗이 격리되지 않는다. 특히 `idle/closed`→`inactive` 합침과 전역→인스턴스 락은 execution/intents 를 동시 수정해야 컴파일·테스트가 선다. - -## 접근안 (택1 — 체크포인트 결정 필요) - -### 안 A — Big-bang 토대 (모델+상태+persistence+소비처 동시) -- BAT-027 에서 모델/상태머신/persistence + 모든 idle·closed·entry_count·락 소비처를 한 번에 새 모델로 전환. -- 장점: 깨끗한 최종 상태, 임시 이중 모델 없음. 단점: 단일 배치가 매우 큼, 회귀 위험 집중, intents/execution(=admission·집행)까지 본 배치로 끌려와 BAT-β 경계 흐려짐. - -### 안 B — 점진(additive-first) + 단계 green ★권장 -- **BAT-027(본 배치)**: (1) 스키마 **additive**(instance_key·retired_at 추가, `UNIQUE(instance_key, portfolio_id)` 도입) — `entry_count`·`trading_date` 컬럼은 **이번엔 유지**(drop 은 cutover BAT-δ로 미뤄 cascade 축소). (2) 상태머신: `inactive` 를 **신규 표준 상태로 추가**하고 `idle`/`closed` 를 **`inactive` 의 별칭/매핑**으로 흡수(전이표는 inactive 기준 재정의, `closed→idle` 제거). (3) advisory lock 을 인스턴스 스코프 2-int 로 전환(전역 락 호출부 일괄 치환). (4) holding 로컬 정본 헬퍼. (5) 소비처는 **상태 별칭·락 시그니처**만 정합(idle/closed 리터럴 → inactive 매핑 유지로 execution/cli/ops_checks 최소 수정). -- **후속 배치**에서 `entry_count`/`trading_date` 컬럼 drop·idle/closed 별칭 완전 제거를 cutover(BAT-δ)와 함께 마무리. -- 장점: 각 단계 단위 green 유지, cascade 분할, BAT-β/γ 경계 보존. 단점: 한시적 별칭/잔존 컬럼(임시 복잡도) — 단, drop 시점(BAT-δ)이 문서로 고정됨. - -### 안 C — 최소(스키마 additive만) -- instance_key·retired_at 만 additive 추가, 상태머신은 손대지 않음. 단점: REQ-057(상태머신) 미충족이라 BAT-α 의도 미달 → 부적합. - -## 권장 = 안 B -점진(additive-first). 이유: R-cascade·R-trading-date 를 분할해 단위 green 을 유지하고, 라이브 영향(컬럼 drop)을 cutover(BAT-δ)로 모아 안전. REQ-056/057 의 "목표 스키마·상태머신"은 충족(별칭·잔존 컬럼은 후속 drop 으로 수렴). - -## 상태머신 (안 B 기준) -- 표준 상태: `inactive, pending_buy, holding, pending_sell, error`. `idle`/`closed` 는 입력 호환 별칭 → `inactive` 로 매핑(전이 검증·저장은 inactive). -- 허용 전이: `inactive→pending_buy, pending_buy→holding, pending_buy→inactive, holding→pending_sell, pending_sell→inactive, pending_sell→holding, *→error, error→{inactive,holding}`. (`closed→idle` 제거.) -- holding = 로컬 정본: pending_buy→holding 에서 set, pending_sell→inactive(매도 체결)에서 clear. - -## advisory lock (REQ-057 / NFR-1) -- `SLOT_ADVISORY_LOCK_ID` 전역 상수 → 인스턴스 스코프 키: `pg_advisory_xact_lock(SLOT_LOCK_ID, hash32(instance_key))`. in-memory repo 는 (lock_id, instance_key) 별 락. 호출부(execution/intents/service/postgresql) 시그니처에 instance_key 전달. - -## 스키마 (안 B / 마이그레이션 009) -- `ALTER TABLE daily_slot ADD COLUMN instance_key TEXT NOT NULL DEFAULT 'default'` (cutover 전 단일 인스턴스 호환), `ADD COLUMN retired_at TIMESTAMPTZ`. -- `UNIQUE(instance_key, portfolio_id)` — 단, portfolio_id 가 nullable·풀 모델 잔존 행과 충돌 가능 → 본 배치에선 **부분 UNIQUE(WHERE portfolio_id IS NOT NULL)** 또는 cutover 후 전체 UNIQUE. (구현 시 확정.) -- `entry_count`/`trading_date` 컬럼 유지(후속 drop). `EXPECTED_SLOT_FIELDS` 는 +instance_key·+retired_at(−entry_count 는 후속). - -## 테스트 설계 -- test_state: 전이 집합(inactive 기준, closed→idle 부재), idle/closed 별칭 매핑. -- test_contract: 필드셋(+instance_key·retired_at). -- test_service: holding set/clear, 인스턴스 락 직렬화/비교차. -- PG 게이트(test DSN): 마이그레이션 009 round-trip + 드리프트. -- 회귀: 소비처 연쇄 정합 후 전체 단위 green. - -## 체크포인트 결정 (2026-06-10, 사용자 확정) -1. **접근안 = 안 B(점진/additive-first).** -2. `idle/closed` 별칭 유지 → **BAT-δ(cutover)에서 완전 제거**. (수용) -3. `UNIQUE` 는 **부분 인덱스 `UNIQUE(instance_key, portfolio_id) WHERE portfolio_id IS NOT NULL`** — 풀 모델 잔존 행(portfolio_id NULL)과 충돌 회피. cutover 후 전체 UNIQUE 승격. -4. `instance_key` 컬럼 **`NOT NULL DEFAULT 'default'`** — 단일 인스턴스/기존 행 호환. cutover 에서 실제 instance_key 태깅(BAT-δ). diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md deleted file mode 100644 index b39aa5b..0000000 --- a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-027 Implementation - -- Status: completed (M1·M2·M3 완료) -- Batch: BAT-027 - -## 구현 요약 -- 안 B(점진/additive-first). M1(additive 모델+마이그레이션+persistence)·M2(상태머신 inactive 별칭+인스턴스 스코프 lock)·M3(소비처 cascade — runtime 무변경, idle/closed 별칭으로 흡수) 완료. 전체 375 passed + PG-gated 8 passed, ruff clean. - -## 변경 내역 - -### M1 — REQ-056 additive (완료) -- `trex_core/domain/slot/models.py`: `Slot` 에 `instance_key: str = "default"`·`retired_at: Optional[datetime] = None` 추가, `EXPECTED_SLOT_FIELDS` +instance_key/+retired_at. (entry_count/trading_date 는 안 B로 유지 — drop 은 BAT-δ) -- `trex_core/adapters/persistence/sql/009_add_slot_instance_key_and_retired_at.sql`(신규): instance_key NOT NULL DEFAULT 'default', retired_at, 부분 UNIQUE(instance_key, portfolio_id) WHERE portfolio_id IS NOT NULL. additive·멱등. -- `trex_core/adapters/persistence/postgresql.py`: `_SLOT_COLUMNS` +instance_key/retired_at(row[19]/[20]), `_row_to_slot` 매핑, `update_slot` SET. pre_allocate INSERT 는 DEFAULT 의존(변경 없음). - -### M2 — REQ-057 상태머신 + 인스턴스 lock (완료) -- `trex_core/domain/slot/state.py`: `SlotState.INACTIVE` 추가 + `idle`/`closed` 를 별칭으로 `normalize_state()` 매핑. `_BASE_TRANSITIONS` 를 inactive 기준으로 재정의(`inactive↔pending_buy↔holding↔pending_sell`), **`closed→idle` 복구 제거**(inactive self-loop). `is_allowed_transition` 가 양쪽 정규화. `slot_advisory_lock_id(instance_key)` 헬퍼(전역 base ⊕ crc32(instance_key)). -- `trex_core/domain/slot/service.py`: `SlotService(repository, instance_key="default")` + advisory lock 을 `self._lock_id`(인스턴스 스코프)로 3개소 치환. holding 은 status 로컬 정본(불변). -- `trex_core/domain/slot/__init__.py`: `normalize_state`/`slot_advisory_lock_id` export. `trex_core/runtime/daemon.py`: `SlotService(..., instance_key=config.instance_key)`. - -### M3 — 소비처 cascade (완료, runtime 무변경) -- idle/closed 가 inactive 별칭으로 흡수되어 **execution/cli/ops_checks/postgresql 의 idle·closed 사용은 그대로 유효**(별칭 정규화). closed→idle 은 runtime 미사용이라 무파손. → 별도 소비처 코드 변경 없음. (실제 inactive 전면 전환·컬럼 drop 은 BAT-δ.) -- 테스트 갱신: test_state(새 전이/멤버/락), test_service(closed→idle 제거→reallocation), test_slot_entry_count, test_postgresql_repository(PG). - -## 테스트 -- 단위 전체: 375 passed / 35 skipped / 1 deselected. -- PG-gated(실 trex_core_test): `test_migration_and_drift`(마이그레이션 009 + `EXPECTED_SLOT_FIELDS`↔컬럼 드리프트, instance_key/retired_at 포함) + `test_postgresql_repository`(lifecycle + closed→idle 거부 + closed→pending_buy reallocation) → **8 passed**. -- `ruff check trex_core tests` All passed. - -## 검증 실행 -- `TREX_BROKER=paper pytest -q --deselect …test_sandbox…` → 375 passed / 35 skipped / 1 deselected. -- `TREX_POSTGRESQL_TEST_DSN=…(trex_core_test) pytest tests/adapters/persistence/test_migration_and_drift.py test_postgresql_repository.py` → 8 passed. -- `ruff check trex_core tests` → All checks passed. diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md deleted file mode 100644 index dbcf7c4..0000000 --- a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-027: 자산 영구 슬롯 토대 — 스키마 + 상태머신 재정의 (dcy-020 BAT-α) - -- Status: released -- Profile: standard -- Source Discovery: dcy-020 -- Included REQ: REQ-056 (영구 슬롯 스키마+마이그레이션), REQ-057 (상태머신 재정의 + 인스턴스 스코프 advisory lock) - -## Summary -- dcy-020(자산 중심 영구 슬롯 모델)의 **첫 구현 배치(BAT-α)**. 익명 풀 슬롯 도메인을 자산 1:1 영구 슬롯의 **데이터 모델 + 상태머신 토대**로 재작성한다. 외부 전략 계약 변경(BAT-γ)·라이브 cutover(BAT-δ)는 포함하지 않는다. -- 핵심: `Slot` 모델/`EXPECTED_SLOT_FIELDS` 재정의(portfolio_id 영구키·`instance_key`·`retired_at`, `entry_count`/슬롯 `trading_date` 제거), 멱등 마이그레이션 009(`UNIQUE(instance_key, portfolio_id)`), 상태머신 `inactive↔pending_buy↔holding↔pending_sell`(+error) 으로 재정의(`closed→idle` 복구 제거), advisory lock 인스턴스 스코프화. - -## Scope -- In Scope: - - `Slot` 모델·`EXPECTED_SLOT_FIELDS` 영구 슬롯 재정의 (REQ-056) - - 마이그레이션 009 (instance_key·retired_at·UNIQUE 복합, entry_count/trading_date 제거) + persistence flat 매핑 (REQ-056) - - 상태머신 재정의(inactive 합침·closed→idle 제거) + holding 로컬 정본 + 인스턴스 스코프 advisory lock (REQ-057) - - `data-model.md` 슬롯 스키마/상태 부분 갱신 (REQ-065 부분) - - 상태/계약 단위테스트 재작성 -- Out of Scope: - - universe reconcile lifecycle (REQ-058 → BAT-β) - - admission/capacity·intent 계약 (REQ-059/061 → BAT-β/γ) - - 수량/불일치 해소 (REQ-062 → BAT-γ) - - 라이브 cutover (REQ-064 → BAT-δ) - - 운영자 slots 단일 뷰 (REQ-063 → 후속) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-020). 본 배치는 dcy-020 의 토대 배치로, 후속 BAT-β(reconcile·admission)/BAT-γ(intent·수량)/BAT-δ(cutover)의 선행. -- **핵심 리스크(R-cascade)**: `Slot` 모델·상태머신 변경은 intents.py·safety_sell·snapshot_handlers·execution 등 다수 소비처로 연쇄된다. design.md 에서 cascade 격리 전략(소비처 동시 수정 범위)을 확정한 뒤 구현한다 — **design 체크포인트 필수**. -- 라이브 데이터는 본 배치에서 건드리지 않는다(스키마는 신규 마이그레이션으로 정의만; 기존 행 전환은 REQ-064/BAT-δ). diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md deleted file mode 100644 index 668f4b0..0000000 --- a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-027 Planning - -- Status: confirmed -- Batch: BAT-027 -- Profile: standard - -## Included REQ -- REQ-056 (Data, Approved): 영구 슬롯 스키마 — portfolio_id 영구키·instance_key·retired_at·`UNIQUE(instance_key, portfolio_id)`, entry_count/슬롯 trading_date 제거 + 마이그레이션 009. -- REQ-057 (Interface, Approved): 상태머신 재정의(inactive↔pending_buy↔holding↔pending_sell, closed→idle 제거) + holding 로컬 정본 + 인스턴스 스코프 advisory lock. - -## Delivery Plan -1. REQ-056 도메인 모델: `models.py` `Slot` 재정의 — `instance_key`(str) 추가, `retired_at: Optional[datetime]` 추가, `entry_count` 제거, `trading_date` 의미 재정의(슬롯 키 아님; 호환 위해 last_activity 성격으로 둘지 design 에서 확정). `EXPECTED_SLOT_FIELDS` 정합. -2. REQ-056 persistence: 마이그레이션 009(`ALTER`/신규 — instance_key NOT NULL·retired_at·UNIQUE(instance_key, portfolio_id), entry_count drop), `postgresql.py` `_SLOT_COLUMNS`/`_row_to_slot`/`update_slot`/insert 매핑 정합. 기존 행 전환은 본 배치 범위 밖(REQ-064). -3. REQ-057 상태머신: `state.py` 상태 집합 `{inactive, pending_buy, holding, pending_sell, error}` + 허용 전이 재정의(`closed`/`idle` → `inactive`, `closed→idle` 제거). `SLOT_ADVISORY_LOCK_ID` → 인스턴스 스코프 키(2-int advisory lock) 헬퍼. -4. REQ-057 서비스: `service.py` holding 로컬 set/clear, transit 인스턴스 스코프 lock 사용. 점유 필드 리셋 의미 재정의(종료=inactive 복귀). -5. 소비처 연쇄 정합(R-cascade): intents.py·safety_sell·snapshot_handlers·execution·reconciliation 등 `idle/closed`·`entry_count`·전역 lock 참조를 새 모델로 정합(컴파일·테스트 green 유지 범위). -6. 테스트: test_state(전이 집합)·test_contract(필드셋)·test_service 재작성 + 인스턴스 락 단위. -7. `data-model.md` 슬롯 스키마/상태 부분 갱신(REQ-065 부분). -8. 단위 회귀 + ruff (+ test DSN 마이그레이션/드리프트). - -## Dependencies -- Internal: dcy-001/BAT-001 슬롯 도메인(재작성 대상), dcy-002/BAT-002 persistence(재작성 대상), dcy-019/BAT-025 entry_count(제거 대상). -- External: 없음(본 배치는 내부 도메인/스키마). 외부 전략 계약은 BAT-γ. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) `Slot` 모델·상태머신 변경이 intents/safety_sell/snapshot/execution/reconciliation 으로 **연쇄(R-cascade)** — 한 배치에서 어디까지 동시 수정해 green 을 유지할지 경계를 설계에서 확정해야 한다. (b) `idle`+`closed`→`inactive` 합침과 `closed→idle` 제거가 기존 lifecycle(재진입·점유 리셋·daily_symbol_entries)과 교차 — 전이/리셋 의미를 못박아야 안전. (c) advisory lock 전역→인스턴스 스코프 전환은 단일 writer 불변식(REQ-002 NFR-1) 재정의라 락 키 설계 필요. (d) `trading_date` 컬럼 drop vs 재의미·entry_count drop 의 스키마/계약 가드 영향. → design.md 를 채우고 **구현 전 사용자 체크포인트**. - -## Milestones -1. M1 — REQ-056 모델+마이그레이션+persistence 매핑, 계약/드리프트 green -2. M2 — REQ-057 상태머신+인스턴스 락, 전이/락 단위 green -3. M3 — 소비처 연쇄 정합(전체 단위 green 회복) -4. M4 — data-model.md 부분 갱신 + 회귀/ruff - -## Risks -- R-cascade (High): 모델/상태머신 변경의 소비처 연쇄로 광범위 회귀. 대응: design 에서 동시 수정 범위 확정, 단계별 단위 green 게이트, 기존 테스트 대량 갱신 인지. -- R-trading-date (Medium): trading_date 슬롯 키 제거가 일일 회계·kill-switch day-scope·transition_log 와 얽힘. 대응: design 에서 drop vs 재의미 결정, 회계 경로는 transition_log/ledger 로 이전. -- R-lock (Medium): 인스턴스 스코프 락 전환이 동시성 회귀. 대응: 2-int advisory lock + 경합 회귀 테스트. - -## Notes -- 라이브 데이터 불변(본 배치는 목표 스키마/모델 정의 + 신규 마이그레이션). 기존 행 cutover 는 BAT-δ(REQ-064). -- 본 배치 green 기준: 단위 전체 회귀(소비처 연쇄 정합 무누락). PG 마이그레이션/드리프트는 test DSN. diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md deleted file mode 100644 index f307791..0000000 --- a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-027 Verification - -- Status: verified (배치 범위 — 안 B 토대) -- Batch: BAT-027 - -## Acceptance Mapping - -### REQ-056 (영구 슬롯 스키마, 안 B 부분) -| AC | 상태 | Evidence | -|----|------|----------| -| instance_key·retired_at·`UNIQUE(instance_key, portfolio_id)` 존재 | ✅ | 마이그레이션 009 + test_migration_and_drift(PG) green | -| `EXPECTED_SLOT_FIELDS` ↔ daily_slot 컬럼 일치(+instance_key/retired_at) | ✅ | test_contract + test_migration_and_drift(PG) | -| 중복 (instance_key, portfolio_id) 거부 | ✅ | 부분 UNIQUE 인덱스(WHERE portfolio_id NOT NULL) | -| `entry_count`/슬롯 `trading_date` 컬럼 **부재** | ⏳ 이월(BAT-δ) | 안 B로 유지 — cutover(REQ-064)에서 drop | - -### REQ-057 (상태머신 재정의 + 인스턴스 lock) -| AC | 상태 | Evidence | -|----|------|----------| -| 전이 집합 inactive 기준(closed→idle 제거) | ✅ | test_state(allowed/disallowed/size 12) | -| 종료 시 inactive 복귀(idle/closed 별칭) | ✅(별칭) | test_state·test_service. 엄밀 literal `inactive` 산출 전면 전환은 BAT-δ | -| 인스턴스별 락 비교차 | ✅ | test_state::test_slot_advisory_lock_id_is_instance_scoped | -| 동일 슬롯 직렬화(단일 writer) | ✅ | test_service Advisory lock 직렬화 | -| holding 로컬 정본 | ✅ | status 로컬(hub 미의존) — 수량 정본 분리는 REQ-062/BAT-γ | - -## Evidence -- 단위 전체: `375 passed / 35 skipped / 1 deselected`(KIS sandbox live-env 1건 deselect). -- PG-gated(실 trex_core_test): `8 passed` — 마이그레이션 009 적용 + 드리프트 가드(instance_key/retired_at 포함) + lifecycle/closed→idle 거부/closed→pending_buy reallocation. -- `ruff check trex_core tests` All passed. - -## Carried-Over (REQ 완전 충족은 후속 배치) -- REQ-056 `entry_count`/`trading_date` 컬럼 drop → **BAT-δ(cutover, REQ-064)**. -- REQ-057 literal `inactive` 전면 산출(별칭 제거) → BAT-δ. -- admission/capacity(REQ-059)·reconcile(REQ-058)·intent 자산 키(REQ-061)·수량/불일치(REQ-062)·운영자 뷰(REQ-063) → BAT-β/γ. -- 따라서 **REQ-056/057 은 본 배치에서 Implemented 로 확정하지 않고 Approved 유지**(토대만 충족; 전체 AC 는 dcy-020 후속 배치 완료 시 confirm-req-implemented). - -## Result -- Ready for Release: true (BAT-030(δ) 완성·검증(2026-06-10)으로 'dcy-020 전체 delivery 미완' 보류 해소 — confirm-batch-verification 승격) -- Blocking Issues: 없음(배치 범위 단위/PG green). 단, dcy-020 REQ 완전 충족은 BAT-β/γ/δ 필요. diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md deleted file mode 100644 index fb37cb2..0000000 --- a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md +++ /dev/null @@ -1,46 +0,0 @@ -# BAT-028 Design - -- Status: confirmed (D1/D2/D3 확정 — 2026-06-10, 사용자 결정) -- Batch: BAT-028 - -## 설계 목표 -- 슬롯 lifecycle 을 universe reconcile 로, admission 을 자산 중심 FCFS+M 캡으로 전환하되, **라이브 데몬 무중단**을 위해 config 게이트로 도입하고 legacy pool 경로를 보존한다(안 B). - -## 핵심 결정 — config 게이트 이중 경로 (권장) -- `DaemonInstanceConfig.max_active_slots: Optional[int]`. - - **설정됨 → 자산 중심 모드**: 슬롯 생성=universe reconcile, admission=자산 슬롯 활성화+M 캡. - - **미설정 → legacy pool 모드**: 기존 preallocation + find_allocatable_slot (현행 그대로). -- 운영 데몬은 cutover(BAT-δ) 전까지 `max_active_slots` 미설정 → **legacy 유지, 무중단**. 자산 중심은 테스트/단계 전환에서 활성. -- 대안(전면 switch)은 라이브 admission 을 한 번에 바꿔 위험 → 안 B에서 비채택. - -## REQ-058 reconcile -- repo 계약 추가: `reconcile_universe(instance_key, trading_date, universe_portfolio_ids: set[str]) -> ReconcileResult`. - - universe 에 있고 슬롯 없음(instance_key, portfolio_id) → 자산 슬롯 생성(inactive, portfolio_id NOT NULL, 멱등). - - 슬롯 있고 universe 에서 빠짐 + status==inactive(idle/closed 별칭) + retired_at IS NULL → `retired_at=now` (soft-retire). - - 빠짐 + holding/pending → **tombstone**(retired_at 미설정, 유지; 신규 활성 후보 제외는 admission/조회에서 retired_at·universe 기준). -- 호출: `SnapshotScheduler.run_once()` 의 `store.swap(items)` 직후, **자산 중심 모드일 때만**. degrade(get_portfolios 실패) → reconcile 건너뜀. **대량소멸 가드**: `len(universe) == 0` 또는 급감(예: 직전 대비 50%↓) 시 retire 보류 + 경고(생성은 진행). -- 단일 writer: reconcile 쓰기는 인스턴스 스코프 advisory lock 안에서. - -## REQ-059 admission (자산 중심 분기) -- `IntentService._admit_buy`: 모드 분기. - - 자산 중심: `repo.find_slot_by_portfolio(instance_key, trading_date, portfolio_id)` 로 그 자산의 슬롯을 잡는다. - - 슬롯 없음(universe 밖) → reject `unknown_portfolio`. - - 슬롯이 이미 활성(pending_buy/holding/pending_sell) → reject `already_active`(멱등). - - 활성 수 계산 `count_active(instance_key, trading_date)` ≥ M → reject `capacity_full`(무치환). - - else inactive→pending_buy 활성화(인스턴스 lock + 재시도). over-cap(M 축소)도 같은 경로로 신규만 차단. - - legacy: 기존 find_allocatable_slot 경로. -- **"활성(active)" 정의(M 캡 대상)**: `holding + pending_buy + pending_sell`(= inactive/error 아닌 포지션 점유 상태). pending_sell 은 청산 진행 중이나 아직 포지션 보유 → 캡에 포함. (체크포인트 D2) - -## REQ-060 config -- `max_active_slots` per-instance TOML 로드(REQ-048 계열). 자산 중심 모드에서 admission 이 M 으로 사용. legacy 모드는 무시. - -## 테스트 설계 -- reconcile: 생성 멱등 / 비활성 soft-retire / 보유·pending tombstone / degrade·대량소멸 보류 (in-memory + PG). -- admission(자산 중심): 활성화 / capacity_full / already_active / over-cap / 동시성 경합 (인스턴스 lock). -- config: max_active_slots 로드 / 모드 게이트. -- legacy 회귀: max_active_slots 미설정 시 기존 pool admission/preallocation 무파손(기존 테스트 green). - -## 체크포인트 결정 (2026-06-10, 사용자 확정) -1. **D1 = config 게이트 이중 경로.** `max_active_slots` 설정 시 자산 중심, 미설정 시 legacy pool. 운영 무중단(cutover 전 pool 유지). -2. **D2 = "활성"(M 캡) = `holding + pending_buy + pending_sell`** (inactive/error 아닌 포지션 점유 상태 전부). -3. **D3 = N→0 + 급감 경고.** universe 가 0이면 retire 보류; 직전 대비 급감(50%↓)도 retire 보류 + 경고(생성은 진행). diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md deleted file mode 100644 index e46cd70..0000000 --- a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md +++ /dev/null @@ -1,27 +0,0 @@ -# BAT-028 Implementation - -- Status: completed -- Batch: BAT-028 - -## 구현 요약 -- config 게이트 이중 경로(D1). `max_active_slots` 설정 시 자산 중심(reconcile + FCFS/M admission), 미설정 시 legacy pool. 운영 데몬은 cutover 전까지 pool 무중단. - -## 변경 내역 -### M1 — REQ-060 config (max_active_slots) -- `runtime/config.py`: `DaemonInstanceConfig.max_active_slots`/`DaemonConfig.max_active_slots`(Optional[int]) + from_toml(>0 검증) + from_config 전파. - -### M2 — REQ-058 universe reconcile -- `domain/slot/repository.py`(protocol)·`in_memory_repository.py`·`adapters/persistence/postgresql.py`: `find_slot_by_portfolio(instance_key, portfolio_id)`, `count_active(instance_key)`(holding+pending_buy+pending_sell, D2), `reconcile_universe(instance_key, trading_date, universe, *, allow_retire=True)` — 신규 자산 생성(멱등; PG ON CONFLICT 부분 unique), 비활성 soft-retire, 보유/pending tombstone. PG insert status='idle'(별칭). -- `application/snapshot_scheduler.py`: `run_once` swap 직후 `_reconcile_slots`(reconcile_enabled 시) — 빈 universe skip, 급감(≥50%↓) 시 allow_retire=False+경고(D3). instance_key/trading_date_fn 주입. - -### M3 — REQ-059 자산 중심 admission -- `application/intents.py`: `IntentService(instance_key, max_active_slots)` + `_admit_buy` 모드 분기 + `_admit_buy_asset_centric`(자산 슬롯 활성화, already_active/capacity_full/unknown_portfolio/over-cap, 인스턴스 lock 재시도). -- `runtime/daemon.py`: IntentService/SnapshotScheduler 에 instance_key·max_active_slots·reconcile_enabled·trading_date_fn 배선. `datetime` import 추가. - -## 테스트 -- 신규: tests/domain/slot/test_universe_reconcile.py(6), tests/application/test_asset_centric_admission.py(6), test_postgresql_repository.py::test_universe_reconcile_pg(PG). -- legacy 회귀: max_active_slots 미설정 시 기존 pool 경로 무파손. - -## 검증 실행 -- 단위 전체: 387 passed / 35 skipped / 1 deselected. ruff clean. -- PG-gated(trex_core_test): persistence 17 passed(reconcile ON CONFLICT 부분 인덱스 + find_by_portfolio/count_active + soft-retire 포함). diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md deleted file mode 100644 index be11ace..0000000 --- a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-028: universe reconcile + 활성 capacity admission (dcy-020 BAT-β) - -- Status: released -- Profile: standard -- Source Discovery: dcy-020 -- Included REQ: REQ-058 (universe reconcile lifecycle), REQ-059 (활성 capacity admission FCFS·M·over-cap), REQ-060 (max_active_slots per-instance TOML) - -## Summary -- dcy-020 의 **2번째 구현 배치(BAT-β)**. 슬롯 lifecycle 을 일일 preallocation 에서 **universe reconcile**(자산 1:1 생성/soft-retire/tombstone)로, buy admission 을 pool(find_allocatable_slot)에서 **자산 중심 FCFS + 동시 활성 M 캡**으로 전환한다. M 은 per-instance TOML(`max_active_slots`). -- **안 B 핵심(라이브 안전)**: 자산 중심 경로를 **config 게이트(`max_active_slots` 설정 시 활성)**로 도입하고, 미설정 시 기존 pool 동작 유지. 운영 데몬은 cutover(BAT-δ) 전까지 pool 모드로 무중단 — 신규 경로는 테스트/단계 전환용. - -## Scope -- In Scope: - - REQ-060: `DaemonInstanceConfig.max_active_slots`(per-instance TOML) — config 게이트 신호 - - REQ-058: `reconcile_slots(universe)` — 자산 1:1 생성(멱등)·비활성 soft-retire·보유/pending tombstone·degrade/대량소멸 보류. 스냅샷 refresh 경로에 결합 - - REQ-059: 자산 중심 admission — 자산 슬롯 활성화(inactive→pending_buy), 활성(holding+pending) M 캡·`capacity_full` 거부·무치환·over-cap·already_active, 인스턴스 스코프 lock 경합 - - runtime-flows.md 부분 갱신(REQ-065 부분) -- Out of Scope: - - 기존 pool 경로 제거(legacy 유지 — 제거는 BAT-δ cutover) - - intent 자산 키 통일·sell-by-portfolio_id (REQ-061 → BAT-γ) - - 수량/불일치(REQ-062), 운영자 뷰(REQ-063) → BAT-γ - - 라이브 cutover·컬럼 drop·literal inactive 전면화 (REQ-064 → BAT-δ) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-020). BAT-027(토대) 후속, BAT-γ/δ 선행. -- **핵심 리스크**: admission/슬롯 생성은 **라이브 데몬 동작**이라, config 게이트로 pool 모드 기본 유지(운영 무중단)하고 자산 중심은 신규 경로로 도입 — design 에서 게이트 경계 확정 후 구현(design 체크포인트). diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md deleted file mode 100644 index c96a1e3..0000000 --- a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md +++ /dev/null @@ -1,41 +0,0 @@ -# BAT-028 Planning - -- Status: confirmed -- Batch: BAT-028 -- Profile: standard - -## Included REQ -- REQ-060 (Configuration, Approved): `max_active_slots` per-instance TOML — 자산 중심 모드 게이트 + M 값. -- REQ-058 (Integration, Approved): universe reconcile lifecycle(생성/soft-retire/tombstone, degrade 보류). -- REQ-059 (Interface, Approved): 자산 중심 admission(FCFS·M캡·무치환·over-cap·already_active). - -## Delivery Plan -1. REQ-060: `DaemonInstanceConfig.max_active_slots: Optional[int]`(config/*.toml). 설정 시 자산 중심 모드, 미설정 시 legacy pool. `instance.example.toml`/`config/.env.example` 주석. -2. REQ-058: `SlotRepository.reconcile_universe(instance_key, trading_date, universe_portfolio_ids)` + in-memory/PG 구현 — 신규 portfolio_id → 자산 슬롯 생성(멱등, inactive, portfolio_id NOT NULL), universe 에서 빠진 비활성 → retired_at set, 보유/pending → tombstone(retired_at 보류). degrade/대량소멸(N→0) 가드는 호출측(scheduler). `SnapshotScheduler.run_once` 의 swap 직후 reconcile 훅(자산 중심 모드일 때만). -3. REQ-059: `IntentService._admit_buy` 자산 중심 분기 — 자산 중심 모드면 `find_slot_by_portfolio(instance_key, trading_date, portfolio_id)` 로 그 자산 슬롯을 잡아 활성화, 활성(holding+pending) 수 < M 이면 inactive→pending_buy, ==M 이면 `capacity_full`, 이미 활성이면 `already_active`. 무치환·over-cap(신규만 차단). legacy 모드면 기존 find_allocatable_slot 경로 유지. -4. daemon 배선: 자산 중심 모드면 preallocation 스레드 대신 reconcile(스냅샷 경로), admission 모드 플래그 전달. -5. runtime-flows.md 부분 갱신(reconcile/admission 흐름, 모드 게이트). -6. 단위(reconcile/admission/capacity/over-cap) + 회귀 + ruff (+ test DSN reconcile). - -## Dependencies -- Internal: BAT-027(영구 슬롯 스키마·instance_key·inactive 상태머신·인스턴스 lock) 선행 완료. dcy-005/BAT-008 SnapshotScheduler·universe read. dcy-014/BAT-018 preallocation(legacy 유지). -- External: portfolio-hub universe(get_portfolios) — 이미 동작. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) admission·슬롯 생성은 **라이브 데몬 동작** 변경이라, 자산 중심 신규 경로와 legacy pool 경로의 **게이트 경계**(config 기준)를 설계에서 확정해 운영 무중단을 보장해야 한다. (b) reconcile 의 tombstone/soft-retire 가 보유 포지션·safety-sell 와 얽히므로 repo 계약·degrade 가드를 못박아야 안전. (c) M 캡 카운팅(활성=holding+pending)·over-cap·동시성(인스턴스 lock)·already_active 규칙을 admission 분기에서 정확히. → design.md 확정 후 **구현 전 사용자 체크포인트**. - -## Milestones -1. M1 — REQ-060 config(게이트) + 단위 -2. M2 — REQ-058 reconcile(repo + scheduler 훅) + 단위/ PG -3. M3 — REQ-059 admission 자산 중심 분기 + capacity/over-cap 단위 -4. M4 — daemon 배선(모드) + runtime-flows 부분 + 회귀/ruff - -## Risks -- R-mode (High): legacy/자산 중심 이중 경로 혼선·라이브 회귀. 대응: config 게이트 단일 신호(max_active_slots), 기본 legacy, 신규 경로 테스트 격리. -- R-reconcile (Medium): tombstone/soft-retire 오판으로 보유 포지션 은퇴. 대응: 보유/pending tombstone·degrade 보류·대량소멸 가드, 회귀 테스트. -- R-capacity (Medium): M 캡 동시성 경합으로 초과 활성. 대응: 인스턴스 스코프 lock + 재시도, 경합 테스트. - -## Notes -- 라이브 데몬은 cutover(BAT-δ) 전까지 legacy pool 모드 유지(무중단). 자산 중심 default 전환은 BAT-δ. -- 본 배치 green 기준: 단위 전체 회귀(legacy 경로 무파손) + 신규 자산 중심 단위 + test DSN reconcile. diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md deleted file mode 100644 index 961757a..0000000 --- a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-028 Verification - -- Status: verified (배치 범위) -- Batch: BAT-028 - -## Acceptance Mapping - -### REQ-060 (max_active_slots config) -| AC | 상태 | Evidence | -|----|------|----------| -| TOML 로드 + admission M 사용 | ✅ | config from_toml + test_asset_centric_admission(M 캡) | -| 미설정 → legacy pool | ✅ | test_legacy_pool_mode_unaffected | -| >0 검증 | ✅ | from_toml raise (단위) | - -### REQ-058 (universe reconcile) -| AC | 상태 | Evidence | -|----|------|----------| -| 신규 자산 생성(멱등) | ✅ | test_universe_reconcile(in-mem) + test_universe_reconcile_pg(ON CONFLICT) | -| 비활성 soft-retire(행 보존, 재등장 재활성) | ✅ | test_soft_retire_* + PG | -| 보유/pending tombstone(은퇴 보류) | ✅ | test_tombstone_holding_* | -| degrade/대량소멸 보류 | ✅ | snapshot_scheduler `_reconcile_slots`(빈 universe skip, ≥50%↓ allow_retire=False) + test_allow_retire_false | - -### REQ-059 (자산 중심 admission) -| AC | 상태 | Evidence | -|----|------|----------| -| 자산 슬롯 활성화(inactive→pending_buy) | ✅ | test_activates_asset_slot | -| capacity_full at M(무치환) | ✅ | test_capacity_full_at_M_no_eviction | -| already_active | ✅ | test_already_active_is_rejected | -| over-cap(신규만 차단·기존 유지) | ✅ | test_over_cap_blocks_new_keeps_existing | -| unknown_portfolio(자산 슬롯 부재) | ✅ | test_unknown_portfolio_when_no_asset_slot | - -## Evidence -- 단위 전체: `387 passed / 35 skipped / 1 deselected`(신규 12 포함). ruff clean. -- PG-gated(실 trex_core_test): persistence `17 passed` — reconcile(ON CONFLICT 부분 unique)·find_slot_by_portfolio·count_active·soft-retire + 마이그레이션 009 드리프트. - -## Carried-Over -- 라이브 데몬 default 는 legacy pool(max_active_slots 미설정) — 자산 중심 default 전환·컬럼 drop·literal inactive 전면화는 **BAT-δ(cutover)**. -- intent 자산 키 통일(sell-by-portfolio_id)·수량/불일치·운영자 뷰 → **BAT-γ**. -- M 캡 strict 동시성은 단일 intent 스레드 + 인스턴스 advisory lock 전제(production). 실 데몬 자산 중심 e2e 는 BAT-γ/δ run-the-app 에서. -- 따라서 REQ-058/059/060 은 본 배치에서 **Approved 유지**(자산 중심 경로 동작 검증; 전체 dcy-020 완성·default 전환은 후속). - -## Result -- Ready for Release: true (BAT-030(δ) 완성·검증(2026-06-10)으로 'dcy-020 전체 delivery 미완' 보류 해소 — confirm-batch-verification 승격) -- Blocking Issues: 없음(단위 387 + PG 17 green, legacy 무파손). diff --git a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/design.md b/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/design.md deleted file mode 100644 index c41663e..0000000 --- a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/design.md +++ /dev/null @@ -1,40 +0,0 @@ -# BAT-029 Design - -- Status: confirmed (D1=slot_id 계약 / D2=3자 확장 — 2026-06-10, 사용자 결정) -- Batch: BAT-029 - -> **D1 재정의(2026-06-10)**: 계약 키 = **slot_id**(buy·sell 공통, FR-7 재정의 / REQ-061 CHG-20260610-03). 매도는 이미 slot_id(불변); 매수가 portfolio_id→slot_id 이동(레거시 portfolio_id **dual-accept**, 폐기 BAT-δ). 아래 REQ-061 절은 slot_id 기준으로 읽는다. -> **D2 확정**: REQ-062 = reconcile 3자 확장 + A=error/B·C=warning + 청산 브로커 정본. - -## 설계 목표 -- intent 를 자산 키로 통일하되 외부 전략 모듈 무중단(dual-accept), 수량 정본을 hub+미러·청산을 브로커로, 코어 holding↔broker/hub 비정상 불일치를 자동 청산 없이 표면화. 운영자 slots 단일 뷰. - -## REQ-061 — sell 자산 키 (dual-accept) -- `SellIntent`: `slot_id: Optional[UUID]` 로 완화 + `portfolio_id: Optional[str]` 추가. 둘 중 하나 필수. -- messaging_handlers sell 파싱: `portfolio_id` 있으면 자산 키, 없으면 `slot_id`(레거시). 둘 다 없으면 reject. -- `_admit_sell`: portfolio_id 면 `find_slot_by_portfolio(instance_key, portfolio_id)` → holding 이면 `holding→pending_sell`, 아니면 `not_holding`. slot_id 면 기존 경로(fetch_slot). 멱등(intent_id) 유지. -- CLI `exit`: 인자를 portfolio_id 로 받되 UUID 형태면 slot_id 로 해석(호환). -- **`slot_id` 폐기는 BAT-δ**(전략 모듈 portfolio_id 전환 후). - -## REQ-062 — 수량 정본 + 불일치 해소 -- 청산 수량 = **브로커 잔고 정본**: safety_sell/execution 의 매도 수량 조회 경로가 broker.get_balance 기반인지 확인하고, 아니면 정합(미러 stale 무관 정확). -- `reconcile_positions`(REQ-031) 확장: 기존 hub↔broker(symbol 합산) 비교에 **코어 슬롯 holding** 추가. - - A. 코어 holding / broker 0 → 슬롯 `error`(reconcile_mismatch) + WARN. (운영자 resolve) - - B. 코어 flat / broker 보유 → WARN/alert (운영자 결정; 자동 편입·청산 금지). - - C. 둘 다 보유, 수량만 → 미러 갱신 + warning(기존 set_warning). - - D. hub 만 차이 → 기존 write-back(dcy-018) 경로. -- **자동 청산/자동 편입 금지(issue #15)** — 표면화(warning/error)·운영자 게이트만. - -## REQ-063 — 운영자 slots 단일 뷰 -- snapshot envelope slot dict 에 `universe_detached`(retired_at not null)·`symbol`(이미 있을 수 있음) 노출. -- `trex-core slots`: 자산 포지션(slot=자산) 단일 뷰 — status·holding·universe_detached 표시. (BAT-022 slots[] 확장; 자산 중심 의미.) - -## 테스트 설계 -- REQ-061: sell by portfolio_id(holding→pending_sell)/not_holding/ dual-accept(slot_id 레거시)/ legacy 회귀. -- REQ-062: reconcile A(error)/B(warning)/C(미러)/D 분기 단위; 청산 수량 브로커 정본. -- REQ-063: slots 뷰 universe_detached 표시. -- 회귀: 기존 sell(slot_id)·reconcile_positions·snapshot 무파손. - -## 체크포인트 결정 필요 -1. **D1 — sell 계약**: dual-accept(slot_id 레거시 + portfolio_id 신규, 폐기 BAT-δ)(권장) vs hard-switch(즉시 portfolio_id 전용). -2. **D2 — REQ-062 본 배치 범위**: (i) reconcile 3자 확장 + A=error/B·C=warning + 청산 브로커 정본(권장) vs (ii) 탐지+warning 만(error/operator 라우팅은 최소). diff --git a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/implementation.md b/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/implementation.md deleted file mode 100644 index 7cd89a7..0000000 --- a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/implementation.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-029 Implementation - -- Status: completed (M2(b) 청산 브로커 정본은 BAT-δ carry-over) -- Batch: BAT-029 - -## 구현 요약 -- M1(REQ-061 slot_id 계약)·M2(a)(REQ-062 divergence reconcile)·M3(REQ-063 slots 단일 뷰) 구현. M2(b)(execution dispatch 매도 수량 브로커 정본)는 라이브 집행 변경이라 BAT-δ 로 이월. - -## 변경 내역 -### M1 — REQ-061 intent 계약 slot_id (커밋 별도) -- BuyIntent slot_id 추가(portfolio_id Optional), `_admit_buy` 자산 중심 우선 분기, `_admit_buy_asset_centric` slot_id(canonical)/portfolio_id(legacy dual-accept) 슬롯 해소 + symbol resolve, messaging buy 파싱 slot_id 수용. - -### M2(a) — REQ-062 divergence reconcile -- `domain/slot/repository.py`·in_memory·postgresql: `list_holding_slots(instance_key)`. -- `application/reconciliation.py`: `reconcile_core_holdings(slot_repo, slot_service, broker, instance_key)` — A(코어 holding/broker 0)=슬롯 error+WARN, C(수량 불일치)=WARN(상태 불변), B(broker 보유/코어 없음)=WARN. 자동 청산 없음(issue #15). -- `application/snapshot_scheduler.py`: run_once 에서 자산 중심 모드 + slot_service 시 reconcile_core_holdings 호출(best-effort). daemon: slot_service 배선. - -### M3 — REQ-063 slots 단일 뷰 -- `application/portfolio.py` `slot_to_dict`: `universe_detached`(retired_at not null) 추가 → `trex-core slots`/envelope 자산 포지션 단일 뷰에 tombstone/soft-retire 표시. - -### M2(b) — carry-over (BAT-δ) -- execution dispatch 매도 수량 `int(slot.qty)` → 브로커 잔고 정본: **라이브 집행 루프 동작 변경**이라 cutover(BAT-δ, 자산 중심 default 전환)와 같은 게이트에서 처리. - -## 테스트 -- 신규: test_holding_divergence(4: A/C/ok/B), test_asset_centric_admission(slot_id 매수·unknown_slot 포함 10). -- 회귀: 기존 sell(slot_id)·reconcile_positions·snapshot 무파손. - -## 검증 실행 -- 단위 전체: 393 passed / 36 skipped / 1 deselected. ruff clean. -- PG-gated(trex_core_test): persistence 18 passed(list_holding_slots/reconcile 포함). diff --git a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/index.md b/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/index.md deleted file mode 100644 index 81948f3..0000000 --- a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# BAT-029: intent 자산 키 + 수량/불일치 해소 + 운영자 단일 뷰 (dcy-020 BAT-γ) - -- Status: released -- Profile: standard -- Source Discovery: dcy-020 -- Included REQ: REQ-061 (intent 자산 키 통일), REQ-062 (수량 hub 정본+미러·불일치 해소), REQ-063 (운영자 slots 단일 뷰) - -## Summary -- dcy-020 의 **3번째 구현 배치(BAT-γ)**. 매수/매도 intent 를 자산(portfolio_id) 키로 통일하고, 수량 정본을 hub+미러로·청산 수량을 브로커 정본으로 두며 코어 holding↔broker/hub 비정상 불일치를 표면화한다. 운영자 `slots` 를 자산 포지션 단일 뷰로. -- **외부 계약 영향(NFR-4)**: sell 이 현재 `slot_id` 필수 → 자산 키(`portfolio_id`)로 전환. 라이브 전략 모듈을 깨지 않기 위해 **dual-accept**(slot_id 레거시 + portfolio_id 신규)로 도입하고, `slot_id` 완전 폐기는 BAT-δ. - -## Scope -- In Scope: - - REQ-061: sell intent 에 `portfolio_id` 수용(자산 중심 모드) + already_active/not_holding 규칙. slot_id 는 레거시 dual-accept(폐기 BAT-δ). - - REQ-062: `reconcile_positions` 확장 — 코어 슬롯 holding ↔ broker/hub 3자 비교, 불일치 A/B/C/D 표면화(warning/error). 청산 수량 = 브로커 잔고 정본. - - REQ-063: snapshot envelope slots[] 에 `universe_detached`/`retired`·symbol, `trex-core slots` 자산 포지션 단일 뷰. - - runtime-flows.md 부분 갱신. -- Out of Scope: - - sell `slot_id` 완전 폐기·literal inactive 전면화·컬럼 drop·자산 중심 default → BAT-δ - - 라이브 cutover (REQ-064 → BAT-δ) - - 자동 청산/자동 편입(불일치 해소는 표면화·운영자 게이트만, issue #15) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-020). BAT-027/028 후속, BAT-δ 선행. -- **핵심 리스크**: REQ-061 은 외부 전략 모듈 계약 변경(NFR-4) → dual-accept 로 무중단 도입. REQ-062 불일치 해소는 자동 청산 금지(issue #15). design 체크포인트에서 계약 호환·divergence 범위 확정 후 구현. diff --git a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/planning.md b/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/planning.md deleted file mode 100644 index cbd07a0..0000000 --- a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/planning.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-029 Planning - -- Status: confirmed -- Batch: BAT-029 -- Profile: standard - -## Included REQ -- REQ-061 (Interface, Approved): intent 자산(portfolio_id) 키 통일 + sell-by-slot_id 폐기(점진) + already_active/not_holding. -- REQ-062 (Integration, Approved): 수량 hub 정본+미러·청산=브로커·holding↔broker/hub 불일치 해소. -- REQ-063 (Interface, Approved): 운영자 slots 자산 포지션 단일 뷰. - -## Delivery Plan -1. REQ-061: `SellIntent.portfolio_id: Optional[str]` 추가(slot_id Optional 로 완화). messaging_handlers sell 파싱이 portfolio_id|slot_id dual-accept. `_admit_sell` 자산 중심 분기 — portfolio_id 면 `find_slot_by_portfolio` → holding 이면 pending_sell, 아니면 `not_holding`. legacy slot_id 경로 유지. CLI `exit` 가 portfolio_id 수용(+slot_id 호환). -2. REQ-062: `reconcile_positions` 확장 — 코어 슬롯 holding 집합을 broker/hub 와 3자 비교. A(코어 holding/broker 0)=슬롯 error+warning, B(코어 flat/broker 보유)=warning/alert(운영자), C(수량만)=미러 갱신+warning, D(hub만)=기존 write-back. 청산 경로(safety_sell/execution)가 매도 수량을 브로커 잔고에서 조회(이미 그렇다면 확인). 자동 청산/편입 금지. -3. REQ-063: snapshot envelope slot dict 에 `universe_detached`(retired_at not null)·symbol 추가, `trex-core slots` 단일 뷰(자산 포지션·tombstone 표시). -4. runtime-flows.md 부분(intent 자산 키·불일치 해소). -5. 단위(sell 자산 키/dual-accept/divergence/slots 뷰) + 회귀 + ruff (+ test DSN). - -## Dependencies -- Internal: BAT-028(find_slot_by_portfolio·count_active·reconcile), BAT-027(상태머신). dcy-005 reconcile_positions(REQ-031)·snapshot envelope. -- External: 전략 모듈(외부 repo) — dual-accept 로 무중단, portfolio_id 전환은 전략 측 후속. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) sell 계약 변경은 **외부 전략 모듈** 영향(NFR-4) — dual-accept vs hard-switch 를 설계에서 확정해 무중단 보장. (b) divergence 해소(A/B/C/D)는 기존 reconcile_positions·error 상태·safety-sell 과 교차하고 자동 청산 금지 경계를 못박아야 안전. (c) 청산 수량 브로커 정본 경로가 이미 그러한지/바꿔야 하는지 확인. → design.md 확정 후 구현 전 체크포인트. - -## Milestones -1. M1 — REQ-061 sell 자산 키 dual-accept + admission 분기 + CLI -2. M2 — REQ-062 reconcile divergence 확장 + 청산 브로커 정본 -3. M3 — REQ-063 slots 단일 뷰 + runtime-flows + 회귀/ruff - -## Risks -- R-contract (High): sell 계약 변경으로 라이브 전략 매도 불능. 대응: dual-accept(slot_id 유지), 전환은 BAT-δ. -- R-divergence (Medium): 불일치 해소가 자동 청산/오격리. 대응: 표면화만(warning/error/operator), 자동 청산 금지(issue #15), 회귀 테스트. - -## Notes -- 자산 중심 모드(max_active_slots 설정)에서 자산 키 admission 활성; legacy 모드는 기존 slot_id sell 유지. -- 본 배치 green 기준: 단위 전체 회귀(legacy sell 무파손) + 신규 자산 키 sell/divergence/slots 단위. diff --git a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/verification.md b/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/verification.md deleted file mode 100644 index 7ef7dbf..0000000 --- a/docs/batches/bat-029_20260610_asset-keyed-intent-and-divergence/verification.md +++ /dev/null @@ -1,43 +0,0 @@ -# BAT-029 Verification - -- Status: verified (배치 범위; M2(b) BAT-δ 이월) -- Batch: BAT-029 - -## Acceptance Mapping - -### REQ-061 (intent 계약 slot_id) -| AC | 상태 | Evidence | -|----|------|----------| -| 매수 slot_id 활성화 + symbol 슬롯 resolve | ✅ | test_activates_by_slot_id | -| 이미 활성 → already_active | ✅ | test_already_active_is_rejected | -| 보유 아닌 슬롯 매도 거부 | ✅ | 기존 sell(_admit_sell slot_not_holding) 회귀 | -| 레거시 portfolio_id dual-accept | ✅ | test_activates_asset_slot(portfolio_id) | -| intent_id 멱등 | ✅ | 기존 intent dedup 회귀 | - -### REQ-062 (수량/불일치 해소) -| AC | 상태 | Evidence | -|----|------|----------| -| A 코어 holding/broker 0 → error | ✅ | test_A_core_holding_broker_flat_goes_error | -| B 코어 flat/broker 보유 → warning | ✅ | test_B_broker_holds_unmanaged_warns | -| C 수량만 → 미러 갱신/warning(상태 불변) | ✅ | test_C_qty_mismatch_warns_no_state_change | -| 청산 수량 = 브로커 정본 | ⏳ 이월(BAT-δ) | execution dispatch qty 변경은 라이브 집행 — cutover 게이트 | - -### REQ-063 (운영자 slots 단일 뷰) -| AC | 상태 | Evidence | -|----|------|----------| -| slots 자산 포지션 단일 뷰 | ✅ | slot_to_dict(기존 envelope) | -| universe_detached(tombstone/soft-retire) 표시 | ✅ | slot_to_dict universe_detached + 회귀 | - -## Evidence -- 단위 전체: `393 passed / 36 skipped / 1 deselected`. ruff clean. -- PG-gated(실 trex_core_test): persistence `18 passed`(list_holding_slots·reconcile·드리프트 포함). - -## Carried-Over -- **REQ-062 청산 수량 브로커 정본**: execution dispatch 매도 수량(`int(slot.qty)`) → 브로커 잔고 정본 변경은 라이브 집행 동작 → **BAT-δ**(cutover, 자산 중심 default 전환)와 같은 게이트. -- sell `slot_id` 완전 폐기·literal inactive·컬럼 drop·자산 중심 default → BAT-δ. -- 실 데몬 자산 중심 e2e(reconcile→slot_id 매수→divergence) run-the-app → BAT-δ confirm-release 전. -- 따라서 REQ-061/062/063 은 본 배치에서 **Approved 유지**(경로 동작 검증; 전체 완성·default 전환은 BAT-δ). - -## Result -- Ready for Release: true (BAT-030(δ) 완성·검증(2026-06-10)으로 'dcy-020 전체 delivery 미완' 보류 해소 — confirm-batch-verification 승격) -- Blocking Issues: 없음(단위 393 + PG 18 green, legacy 무파손). diff --git a/docs/batches/bat-030_20260610_asset-centric-cutover/design.md b/docs/batches/bat-030_20260610_asset-centric-cutover/design.md deleted file mode 100644 index c2ac6f4..0000000 --- a/docs/batches/bat-030_20260610_asset-centric-cutover/design.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-030 Design - -- Status: confirmed (D1~D4 확정 — 2026-06-10, 사용자 결정) -- Batch: BAT-030 - -> **체크포인트 결정(2026-06-10)**: D1=**전체 rebuild**(보유는 브로커 잔고 기준 carry-over) / D2=**전부 완성**(literal inactive + 별칭 제거 + entry_count·trading_date drop) / D3=**집행 매도 qty 브로커 정본**(미러 fallback 없음, 0이면 skip+divergence) / D4=**실 cutover 는 운영자(REL 단계, 리허설 후)**. 아래 §1 을 전체 rebuild 로 읽는다. - - -> **2026-06-10 갱신(CHG-20260610-03)**: D1(데이터 보존 cutover) 폐기 — 실 DB 점검에서 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 중복 데이터에 적용 불가한 결함 확인. **DB wipe + 자산 중심 재초기화**(`trex-core-reset`)로 대체. §1 의 cutover(전체 rebuild·보유 carry-over·promote-unique)는 미적용; 보유는 재초기화 후 universe reconcile + reconcile_core_holdings(REQ-062)로 운영자 재편입. D2/D3 는 유효. - -## 설계 목표 -- 자산 중심 모델을 완성하고 라이브로 안전 전환. 보유 포지션 무손실·집행 정확·비가역 변경 통제. - -## 1. cutover (REQ-064) — 전체 rebuild (D1) -- **전체 rebuild**: 해당 instance_key 의 daily_slot 을 비우고 universe + 브로커 잔고 기준으로 재생성한다. **보유 carry-over 의 정본 = 브로커 잔고**(D3 와 일관). -- 절차(cutover 스크립트, Python — broker + universe 필요): - 1. 사전: 거래 세션 외, in-flight(pending_*) 0 확인(아니면 수렴 대기). 백업. - 2. 해당 instance_key 슬롯 삭제(또는 신규 테이블 swap). - 3. universe(get_portfolios) 의 모든 자산 → 영구 슬롯 생성(`inactive`). - 4. 브로커 잔고(get_balance)의 각 보유 (symbol, qty) → universe 로 symbol→portfolio_id 매핑 → 해당 자산 슬롯을 `holding`(qty=브로커 수량, avg_price=브로커/원장) 으로 설정. - 5. instance_key 태깅, 부분 UNIQUE → 전체 UNIQUE 승격. -- 매핑 실패(브로커 보유 symbol 이 universe 에 없음) → 중단/경고(운영자 판단; 자동 청산 없음). -- **실 cutover 는 운영자 실행**(D4) — 본 배치는 cutover 스크립트 + test DB 리허설 제공. - -## 2. 집행 매도 수량 브로커 정본 (REQ-062 M2(b)) -- `execution.py` dispatch: 매도 시 `qty = int(slot.qty)` → `broker.get_balance()` 의 해당 symbol `hldg_qty`. 미러 stale 무관 정확. 브로커 0 이면 매도 skip + divergence(REQ-062 A) 경로로 표면화. - -## 3. literal inactive 전면화 (REQ-057 잔여) -- `state.py`: `idle`/`closed` 별칭 제거(또는 deprecated 유지하되 코드 산출은 inactive). 전이/리셋이 `inactive` 산출. -- `execution.py`: PENDING_SELL→`INACTIVE`(매도 체결), PENDING_BUY→`INACTIVE`(롤백). postgresql/cli/ops_checks 의 `'idle'`/`'closed'` → `'inactive'`(쿼리/필터). -- **마이그레이션 010(CHECK)**: status CHECK 에 `'inactive'` 허용 + 기존 `idle`/`closed` 행 → `inactive` UPDATE. - -## 4. 컬럼 drop (REQ-056 잔여) -- `entry_count` drop(미사용). 슬롯 `trading_date`: drop 또는 `opened_at` 재의미(이벤트 시간 속성으로). 모델/`EXPECTED_SLOT_FIELDS`/persistence/드리프트 가드 정합. legacy preallocation 경로 제거. -- 비가역 → drop 전 코드 미참조화 선행, 백업, 롤백(직전 리비전+컬럼 복원). - -## 5. default 전환 -- 코드 default 변경 아님 — **운영 인스턴스 TOML 에 `max_active_slots` 설정**으로 자산 중심 활성(config 게이트). 배포 단계 운영자 작업. - -## 6. baseline 마감 (REQ-065) -- data-model/runtime-flows/project-structure 를 최종 자산 중심으로 갱신, dcy-001/014/019·daily_symbol_entries 폐기 확정. - -## 롤백 -- 직전 released 리비전 + (필요시) 역마이그레이션(inactive→idle/closed, 컬럼 복원). 보유 행은 portfolio_id 보존이라 구 코드와 공존(부분). 컬럼 drop 은 백업 복원. - -## 체크포인트 결정 필요 -1. **D1 — cutover 방식**: 보유 행 재사용 + vestigial idle 삭제 + reconcile(권장) vs 전체 rebuild. -2. **D2 — 본 배치 완성 범위**: literal inactive + 별칭 제거 + entry_count/trading_date drop **전부 완성**(권장) vs 일부 유지. -3. **D3 — 집행 매도 qty**: 브로커 정본(미러 fallback 없음, 0이면 skip+divergence)(권장) 확인. -4. **D4 — 실 cutover 실행 주체**: 운영자(REL 단계, 리허설 후)(권장) — 본 세션은 마이그레이션+코드+test DB 리허설까지. diff --git a/docs/batches/bat-030_20260610_asset-centric-cutover/implementation.md b/docs/batches/bat-030_20260610_asset-centric-cutover/implementation.md deleted file mode 100644 index 9715908..0000000 --- a/docs/batches/bat-030_20260610_asset-centric-cutover/implementation.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-030 Implementation - -- Status: done -- Batch: BAT-030 -- 일자: 2026-06-10 - -## 구현 요약 - -design.md 확정 결정(D2=전부 완성 / D3=매도 qty 브로커 정본) 구현 완료. M1~M5 수행. **단, D1(데이터 보존 cutover)은 폐기** — 실 DB 점검 중 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 데이터(같은 자산이 closed+재진입 holding 으로 portfolio_id 중복)와 충돌해 cutover/데몬 부팅이 모두 막히는 결함 발견. 사용자 결정으로 데이터 보존 마이그레이션을 버리고 **DB wipe + 자산 중심 재초기화**(`trex-core-reset`)로 대체(REQ-064 CHG-20260610-03). 빈 테이블에는 009 인덱스가 정상 생성되고 자산 중심에서 (instance_key,portfolio_id)는 설계상 유일. - -- **M1 — literal inactive 전면화 (REQ-057 잔여)**: `SlotState` 5-state(inactive/pending_buy/holding/pending_sell/error) — `IDLE`/`CLOSED` 별칭·`normalize_state` 완전 제거, 전이 12종(6 base + 4 to-error + 2 resolve). 마이그레이션 **010**: 기존 idle/closed 행 → inactive UPDATE + status CHECK 교체(drop-and-add 멱등). `execution.py` 매도 체결 `PENDING_SELL→INACTIVE`, 매수 롤백 `PENDING_BUY→INACTIVE`. `cli.py` buyable 필터 `inactive`+`universe_detached` 제외. `ops_checks.py` ledger-gap 검출을 `slot_transition_log`(reason=`sell_filled`/`sell_partial_cancel`) 기반으로 재작성(`closed` 상태 소멸 대응). -- **M2 — 집행 매도 수량 브로커 정본 (REQ-062 M2(b), D3)**: dispatch 매도 qty = `broker.get_balance()` 의 해당 symbol `hldg_qty`(tick 당 1회 lazy fetch). 미러 `slot.qty` fallback 없음. 브로커 0 → 주문 미제출 + WARN + `ExecutionResult.sell_skipped_no_balance` 카운터(divergence 는 reconcile/운영자 경로로 표면화). skip 매도가 큐 head 를 막지 않도록 단건 claim(`find_pending_for_dispatch`) → **리스트 기반**(`list_undispatched_pending(instance_key, limit)`) dispatch 로 전환. -- **M3 — 컬럼 drop + legacy pool 제거 (REQ-056 잔여)**: 마이그레이션 **011**: `daily_slot.entry_count`·`trading_date` DROP(IF EXISTS 멱등). 글롭 재적용 정합: 001 의 `(trading_date,status)` 인덱스 → `(status)` 교체, 008(entry_count ADD)은 no-op 화(매 boot 컬럼 부활 방지). `Slot` 모델/`EXPECTED_SLOT_FIELDS`/`_SLOT_COLUMNS`/`_row_to_slot` 정합(19 컬럼). repo 계약 개편: `pre_allocate_slots`/`find_idle_slot`/`find_allocatable_slot`/`increment_symbol_entry`/`get_symbol_entry_count` 제거, `list_slots(instance_key)`/`count_slots_by_status(instance_key)`/`reconcile_universe(instance_key, universe)` 로 전환, 집행 큐(`list_undispatched_pending`/`list_dispatched_pending`)도 **instance 스코프**(공유 DB 의 두 데몬이 서로의 주문을 집행하지 않음 — REQ-056). `preallocation_scheduler.py` 삭제 + 데몬 7 스레드. admission 은 자산 중심 단일 경로(slot_id 정본/portfolio_id dual-accept; `max_active_slots` 미설정 = 무캡 — design §5: 운영 TOML 로 M 부여). `BuyIntent.trading_date`·intent/이벤트/슬롯 뷰 payload 의 `trading_date` 키 제거. capacity 를 `SlotCapacity(inactive/holding/pending/active/available/total/scarcity)` 자산 중심 재정의(available = max(0, M−active)). `daily_symbol_entries` 코드 경로 제거(테이블 잔존, baseline 에 폐기 명시). config 에서 `total_slots`/`daily_preallocation_time` 제거. SnapshotScheduler reconcile 은 slot_repo 주입 시 항상 on. -- **M4 — DB 재초기화 도구 (REQ-064, CHG-20260610-03)**: `trex_core/runtime/reset.py` + console script `trex-core-reset`(기본 dry-run, `--yes` 적용). 절차: 대상 DB 확인 → slot-core 7개 테이블 중 존재하는 것 행수 집계(dry-run 은 여기서 보고만) → `--yes` 면 `TRUNCATE ... RESTART IDENTITY CASCADE` → `apply_migrations`(빈 테이블이라 009 유니크 인덱스 정상 생성·010 inactive CHECK·011 컬럼 drop) → `daily_slot` 컬럼 == EXPECTED_SLOT_FIELDS 검증. 파괴적·**DB 전역**(모든 instance). 데이터 보존(보유 carry-over)은 하지 않음 — 재초기화 후 universe reconcile 이 자산 슬롯 생성, 보유는 `reconcile_core_holdings`(REQ-062)로 운영자 재편입. (구 `cutover.py`·`trex-core-cutover`·`test_cutover_rehearsal.py` 삭제.) -- **M5 — baseline 마감 (REQ-065)**: data-model/runtime-flows/project-structure/interface-contract 4문서 자산 중심 최종 갱신(daily_symbol_entries·dcy-014 preallocation·풀 admission 폐기 확정, slot_id 계약·5-state·M 캡·브로커 정본 반영). `config/instance.example.toml` 에 `max_active_slots` 안내. `.claude/skills/run-trex-core-daemon`(SKILL.md+smoke.py) 자산 중심으로 갱신. - -## 변경 내역 (Changed Files) - -- trex_core/domain/slot/: state.py(5-state) · models.py(컬럼 drop+SlotCapacity 재정의) · service.py(preallocate/카운터 제거, INACTIVE 리셋=점유 초기화+자산 정체성 보존, capacity 자산 중심) · repository.py · in_memory_repository.py(+add_slot 테스트 헬퍼) · __init__.py -- trex_core/adapters/persistence/: postgresql.py · ops_checks.py(재작성) · sql/001(인덱스 교체)·008(no-op 화)·**010_literal_inactive_status.sql**(신규)·**011_drop_pool_model_columns.sql**(신규) -- trex_core/application/: intents.py(단일 자산 중심 admission) · execution.py(M2+inactive 전이+instance 스코프) · messaging_handlers.py · portfolio.py · safety_sell.py · killswitch.py · snapshot_scheduler.py · **preallocation_scheduler.py 삭제** -- trex_core/runtime/: daemon.py(7 스레드) · config.py · cli.py · **reset.py(신규)** · ~~cutover.py(삭제)~~ -- pyproject.toml: `trex-core-reset` console script (구 `trex-core-cutover` 제거) -- docs: data-model.md · runtime-flows.md · project-structure.md · interface-contract.md (자산 중심 마감) -- config/instance.example.toml · .claude/skills/run-trex-core-daemon/{SKILL.md,smoke.py} -- tests: 도메인/애플리케이션/어댑터/런타임 전면 정합(test_state·test_service 재작성, test_slot_entry_count·test_buy_admission_reallocation·test_preallocation_scheduler 삭제, test_execution 에 M2 3건 신설, **tests/runtime/test_reset.py 신규 5건(PG-gated)**, test_postgresql_repository 자산 중심 재작성+dispatch 인스턴스 격리, test_ops_checks 재작성) - -## 테스트 - -- 단위 전체: **367 passed** (PG/RabbitMQ/KIS unset 시 40 skipped), ruff clean -- PG-gated(trex_core_test): **26 passed** — 마이그레이션 010/011 round-trip + 멱등 재적용, daily_slot 컬럼 == EXPECTED_SLOT_FIELDS 드리프트 가드(컬럼 drop 후 정합), reconcile ON CONFLICT, dispatch 인스턴스 격리, ops_checks 재작성, **reset 5건**(wipe+자산 중심 스키마 착지·dry-run 무변경·멱등·wipe 후 즉시 admission 가능·slot-core 테이블 커버리지) -- RabbitMQ-gated: 21 passed (실 브로커) -- 통합(PG+RabbitMQ enabled): **411 passed, 1 skipped**(KIS sandbox — 기존 환경 이슈, 본 배치 무관) - -## 검증 실행 (run-the-app, 자산 중심 e2e) - -test DB(trex_core_test) + 실 RabbitMQ + paper broker + stub universe(2 자산) + `instance_key=e2etest`, `max_active_slots=1` 로 데몬 기동: -1. boot 직후 universe reconcile `{'created': 2}` — 영구 슬롯 자동 생성 -2. snapshot RPC: slots=2(inactive), `available_slots=1`(M−active) -3. **buy by slot_id** admitted(REQ-061) → 4. 동일 intent_id 중복 → identical 응답(전이 1회) -5. M=1 에서 2번째 자산 buy → `capacity_full`(REQ-059) → 6. paper fill → holding, available=0 -7. 동일 슬롯 재매수 → `already_active` → 8. sell by slot_id → 체결 → **inactive**(qty=None, portfolio_id 보존, available=1) -9. **동일 영구 슬롯 재활성**(re-entry) admitted -- DB 최종: `('e2etest','pf-000660','inactive',None)`, `('e2etest','pf-005930','holding',3.0)`; transition_log reason {buy_intent×2, buy_filled×2, discretionary_sell×1, sell_filled×1}; ledger_outbox completed×3; 데몬 로그 ERROR/Traceback 0, Normal shutdown. - -## Remaining Risks / Blockers - -- **실 DB 재초기화 미실행(의도)** — 운영자가 `trex-core-reset --config --yes` 실행(파괴적·DB 전역). 사전 백업 필수. 재초기화라 롤백 = 백업 복원. -- 데이터 보존 cutover 폐기로 **기존 보유 포지션은 재초기화 시 소실** — 재초기화 후 브로커 보유는 universe reconcile + `reconcile_core_holdings`(REQ-062)에서 unmanaged 로 표면화되어 운영자가 재편입. 실거래 보유가 있는 DB 라면 재편입 절차를 release 노트에 포함. -- 마이그레이션 009 부분 유니크 인덱스는 legacy 풀 중복 데이터에 적용 불가(이 결함이 cutover→reset 전환의 직접 원인). 자산 중심 DB(신규/재초기화 후)에서는 정상. -- 전략 모듈(trex-orb) 계약 전환: buy `slot_id` 정본(snapshot slots[] round-trip), `trading_date` 키 소멸, 이벤트 payload 변경 — portfolio_id dual-accept 로 무중단이나 전략 측 전환 권고(REL 노트 대상). -- `max_active_slots` 미설정 = 무캡 — 운영 TOML 에 반드시 설정(배포 체크리스트, design §5). diff --git a/docs/batches/bat-030_20260610_asset-centric-cutover/index.md b/docs/batches/bat-030_20260610_asset-centric-cutover/index.md deleted file mode 100644 index d597285..0000000 --- a/docs/batches/bat-030_20260610_asset-centric-cutover/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-030: 자산 중심 cutover — 라이브 전환·집행 정본·모델 완성 (dcy-020 BAT-δ) - -- Status: released -- Profile: standard -- Source Discovery: dcy-020 -- Included REQ: REQ-064 (풀→영구 cutover), REQ-062 (잔여: 청산 수량 브로커 정본), REQ-056/057/065 (잔여: entry_count/trading_date drop·literal inactive·baseline 마감) - -## Summary -- dcy-020 의 **마지막 배치(BAT-δ)** — 자산 중심 모델을 **완성하고 라이브로 전환**한다. 가장 위험: 라이브 트레이딩 데이터 cutover + 집행 동작 변경 + 모델 완성(별칭/컬럼 정리). -- 핵심: (1) 라이브 cutover(풀 행→영구 자산 행, 보유 carry-over, instance_key 태깅), (2) 집행 dispatch 매도 수량 = **브로커 잔고 정본**(REQ-062 잔여, M2(b)), (3) literal `inactive` 전면화 + `idle`/`closed` 별칭 제거 + CHECK 제약 갱신, (4) `entry_count`/슬롯 `trading_date` 컬럼 drop, (5) 자산 중심 default 운영 전환(max_active_slots), (6) baseline 문서 마감(REQ-065). - -## Scope -- In Scope: - - REQ-064: cutover 마이그레이션 + 절차 + 롤백(라이브는 운영자 실행) - - REQ-062 잔여: execution dispatch 매도 수량 브로커 잔고 정본(M2(b)) - - REQ-056/057 잔여: literal inactive(+CHECK 마이그레이션), idle/closed 별칭 제거, entry_count/trading_date 컬럼 drop - - REQ-065: baseline 3문서 마감 + dcy-001/014/019 폐기 확정 - - 자산 중심 default 전환(운영 config) + run-the-app(자산 중심 e2e) -- Out of Scope: - - 포지션 사이징(issue #1, OQ-4 분리 — 후속 Discovery) - - 신규 기능(본 배치는 모델 완성·전환만) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Source discovery 단일(dcy-020). BAT-027/028/029 후속, dcy-020 최종. -- **최고 위험**: 라이브 데이터·집행 전환. **실 cutover 는 운영자 실행**(본 배치는 마이그레이션·코드·리허설 제공). design 체크포인트에서 cutover 절차·롤백·운영자 입회·전환 순서 확정 후 구현. -- 본 배치 완료 후 REQ-056~065 confirm-req-implemented → REL. -- 2026-06-10 (CHG-20260610-03): 데이터 보존 cutover → DB 재초기화(`trex-core-reset`)로 전환 (마이그레이션 009 legacy 충돌 결함). 폴더명의 'cutover' 는 역사적 표기로 유지. diff --git a/docs/batches/bat-030_20260610_asset-centric-cutover/planning.md b/docs/batches/bat-030_20260610_asset-centric-cutover/planning.md deleted file mode 100644 index f86b6a1..0000000 --- a/docs/batches/bat-030_20260610_asset-centric-cutover/planning.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-030 Planning - -- Status: confirmed -- Batch: BAT-030 -- Profile: standard - -## Included REQ -- REQ-064 (Migration, Approved): 풀→영구 자산 슬롯 cutover + 롤백. -- REQ-062 (Integration, Approved 잔여): 청산 수량 브로커 정본(M2(b), execution dispatch). -- REQ-056/057 (잔여): entry_count/trading_date 컬럼 drop, literal inactive + 별칭 제거. -- REQ-065 (Documentation): baseline 마감. - -## Delivery Plan (전환 순서 — 안전 우선) -1. **literal inactive 전면화**: `state.py` IDLE/CLOSED 별칭 제거(또는 deprecate), 코드가 `inactive` 산출. `execution.py` PENDING_SELL→`INACTIVE`·PENDING_BUY→`INACTIVE`. postgresql/cli/ops_checks 의 'idle'/'closed' → 'inactive'. CHECK 제약 마이그레이션(010: 'inactive' 허용 + 기존 idle/closed→inactive UPDATE). -2. **M2(b) 집행 매도 수량 브로커 정본**: dispatch 매도 `int(slot.qty)` → `broker.get_balance()` 해당 symbol hldg_qty. 미러 stale 무관 정확. -3. **컬럼 drop**: `entry_count`·슬롯 `trading_date`(또는 opened_at 재의미) drop 마이그레이션 + 모델/EXPECTED_SLOT_FIELDS/persistence 정합. (legacy preallocation 경로도 제거.) -4. **cutover 마이그레이션(REQ-064)**: 기존 보유(holding, portfolio_id 보유) 행은 그대로 자산 슬롯; vestigial idle 풀 행(portfolio_id NULL) 정리; instance_key 태깅(009 default 'default'); UNIQUE 부분→전체 승격 검토. -5. **default 전환**: 운영 인스턴스 TOML 에 `max_active_slots` 설정(자산 중심 활성) — 운영자 배포 단계. -6. **baseline 마감(REQ-065)** + run-the-app 자산 중심 e2e(test DB). - -## Dependencies -- Internal: BAT-027/028/029(스키마·상태머신·reconcile·admission·divergence). 전 단계 green. -- External: 전략 모듈 slot_id 계약 전환(BAT-029 dual-accept 로 무중단; portfolio_id 매수 폐기 시 전략 전환 필요). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (a) **라이브 데이터 cutover** — 보유 포지션 무손실·롤백·in-flight(pending) 처리·운영자 입회 절차를 설계에서 확정. (b) literal inactive 전환은 BAT-027 별칭의 역-cascade(코드+CHECK 마이그레이션+다수 테스트). (c) 집행 dispatch 매도 수량 변경은 라이브 주문 동작. (d) 컬럼 drop 은 비가역 — 순서·롤백 필수. → design.md 확정 후 구현 전 체크포인트. - -## Milestones -1. M1 — literal inactive + CHECK 마이그레이션(010) + 별칭 제거 + 테스트 -2. M2 — 집행 매도 수량 브로커 정본 -3. M3 — entry_count/trading_date drop 마이그레이션 + 정합 -4. M4 — cutover 마이그레이션(REQ-064) + 리허설(test DB) -5. M5 — baseline 마감 + run-the-app 자산 중심 e2e - -## Risks -- R-live (High): 라이브 cutover 로 보유 포지션 손실/집행 중단. 대응: 보유 행 보존(이미 portfolio_id 보유)·거래 세션 외 실행·롤백 스크립트·운영자 입회·리허설(test DB) 선행. -- R-irreversible (High): 컬럼 drop 비가역. 대응: drop 전 백업, 단계적(코드 미참조화 → drop), 롤백은 직전 리비전+컬럼 복원. -- R-cascade (Medium): literal inactive 역전환의 테스트/코드 cascade. 대응: BAT-027 별칭 지점 일괄 전환 + 회귀 green 게이트. -- R-exec (Medium): 집행 매도 qty 변경 회귀. 대응: 단위+리허설, 미러 fallback 없이 broker 권위. - -## Notes -- 실 cutover 는 운영자 실행(본 배치는 마이그레이션·코드·리허설 제공). confirm-release 전 cutover 리허설 + run-the-app evidence 필수. -- green 기준: 단위 전체 회귀(literal inactive·drop 무파손) + cutover 리허설(보유 보존·롤백) + 자산 중심 e2e. - -## 갱신 (2026-06-10, CHG-20260610-03) -- M4 의 데이터 보존 cutover 도구(`trex-core-cutover`)는 폐기되고 **DB 재초기화 도구**(`trex-core-reset`: slot-core wipe + 마이그레이션 재적용)로 대체. 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 중복 데이터에 적용 불가한 결함이 직접 원인. 보유 carry-over 불요(재초기화 후 reconcile 재편입). diff --git a/docs/batches/bat-030_20260610_asset-centric-cutover/verification.md b/docs/batches/bat-030_20260610_asset-centric-cutover/verification.md deleted file mode 100644 index 7deb1d2..0000000 --- a/docs/batches/bat-030_20260610_asset-centric-cutover/verification.md +++ /dev/null @@ -1,66 +0,0 @@ -# BAT-030 Verification - -- Status: approved (confirm-batch-verification, 2026-06-10) -- Batch: BAT-030 -- 일자: 2026-06-10 -- 검증 환경: Python 3.12 / PostgreSQL `trex_core_test`(실 DB) / 실 RabbitMQ / paper broker - -## Acceptance Mapping - -### REQ-056 (Data — 잔여: trading_date/entry_count drop, 스키마 마감) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 스키마: portfolio_id·instance_key·부분 UNIQUE(instance_key,portfolio_id)·retired_at·trading_date/entry_count 부재 | pass | 마이그레이션 009/010/011 + `test_daily_slot_columns_match_expected_contract`(drop 후 컬럼셋 == EXPECTED_SLOT_FIELDS) + `test_reset_wipes_and_lands_asset_centric`(유니크 인덱스·inactive CHECK 존재 확인). ※ portfolio_id NOT NULL 승격은 데이터 보존 cutover 와 함께 폐기 — 자산 중심에서 universe reconcile 이 항상 portfolio_id 를 채우므로 부분 유니크로 충분(CHG-20260610-03) | -| AC-2 EXPECTED_SLOT_FIELDS ↔ information_schema 일치 | pass | `tests/adapters/persistence/test_migration_and_drift.py` 3건 PG 통과 | -| AC-3 (instance_key, portfolio_id) 중복 거부 | pass | partial UNIQUE(009) + `test_universe_reconcile_pg`(ON CONFLICT 멱등, created=0) | -| AC-4 인스턴스 간 행 미혼합 | pass | `test_dispatch_queue_instance_scoped`(집행 큐 격리) + `test_instance_isolation`(reconcile) | - -### REQ-057 (Interface — 잔여: literal inactive + 별칭 제거) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 허용 전이 집합 정확 일치(12종) | pass | `tests/domain/slot/test_state.py`(5 members·alias ValueError·allowed/disallowed 파라미터라이즈·set size 12) | -| AC-2 매도 체결 → inactive 복귀(closed 없음)·점유 clear | pass | `test_sell_fill_returns_to_inactive_and_clears_position`, `test_full_lifecycle_returns_to_inactive_and_reuses`(PG), e2e 7단계(status=inactive, qty=None) | -| AC-3 인스턴스별 락 비차단 | pass | `slot_advisory_lock_id` 인스턴스 해시(BAT-027) + `test_slot_advisory_lock_id_is_instance_scoped` | -| AC-4 동일 슬롯 동시 전이 직렬화 | pass | `TestAdvisoryLockSerialization`(1 success/1 mismatch) | - -### REQ-062 (Integration — 잔여: M2(b) 매도 수량 브로커 정본) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 매도 수량 = 브로커 잔고(미러 stale 무관) | pass | `test_sell_qty_is_broker_balance_not_slot_mirror`(미러 7 vs 브로커 10 → 주문 10) + `test_sell_skipped_when_broker_flat`(브로커 0 → skip+카운터, 주문 0) + `test_skipped_sell_does_not_block_other_dispatches` | -| AC-2 core holding/브로커 0 → error 격리 | pass (BAT-029 기구현) | `tests/application/test_holding_divergence.py::test_A_*` — 본 배치에서 회귀 green 재확인 | -| AC-3 core flat/브로커 보유 → WARN·운영자 결정 | pass (BAT-029 기구현; 슬롯 부재로 '격리' 대신 unmanaged WARN — issue #15 무자동조치 결정) | `test_B_broker_holds_unmanaged_warns` | -| AC-4 수량 불일치 → 상태 전이 없음·warning 노출 | pass (BAT-029 기구현; 미러 자동 갱신 없이 WARN — 보수 구현) | `test_C_qty_mismatch_warns_no_state_change` | - -### REQ-064 (Migration → **DB 재초기화로 재정의**, CHG-20260610-03) -원 AC(데이터 보존 cutover)는 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 중복 데이터에 적용 불가한 결함으로 폐기되고, **wipe + 자산 중심 재초기화**(`trex-core-reset`)로 재정의됨. 재정의된 AC 로 매핑: -| AC(재정의) | 판정 | Evidence | -|---|---|---| -| AC-1' 재초기화 시 slot-core 테이블 wipe + 자산 중심 스키마 착지 | pass | `test_reset_wipes_and_lands_asset_centric`(2행 → 0행, 컬럼==EXPECTED, 유니크 인덱스+inactive CHECK 존재) | -| AC-2' 재초기화 후 데몬 회귀 없음(reconcile·admission) | pass | `test_reset_usable_for_admission_after_wipe`(reset 후 reconcile+활성 동작) + run-the-app e2e(아래) | -| AC-3' 안전성: dry-run 무변경 + 멱등 | pass | `test_dry_run_reports_without_wiping`(행 보존) + `test_reset_is_idempotent`(2회차 0행·schema_ok) | -| AC-4' 데이터 보존(보유 carry-over) 폐기 — 보유는 reconcile 로 재편입 | pass(의도된 범위 축소) | CHG-20260610-03; 재초기화 후 브로커 보유는 `reconcile_core_holdings`(REQ-062) unmanaged 경로 | -| (참고) 백업/복원 | 운영 절차 | reset 은 재초기화라 롤백 = 백업 복원. 실행 전 백업 필수(release 게이트) | - -### REQ-065 (Documentation — baseline 마감) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 data-model.md 코드 일치 | pass | commit 7d72782 — 영구 슬롯 스키마·5-state·trading_date 강등(전이 로그/원장으로)·INACTIVE 리셋 규칙 반영 | -| AC-2 runtime-flows.md 일치 + preallocation 서술 제거 | pass | 데몬 7스레드·universe reconcile 흐름(preallocation 대체)·admission slot_id·매도 브로커 정본·transit 12전이 갱신 | -| AC-3 폐기 범위 명시(dcy-001/014/019·daily_symbol_entries) | pass | data-model Model Summary/Gaps + Change Log; project-structure·interface-contract 도 동기화(추가) | - -## Evidence - -1. **단위 회귀**: `pytest tests` → **367 passed**, ruff `All checks passed` (commit 7d72782 시점). -2. **PG-gated**(실 PostgreSQL `trex_core_test`): **26 passed** — 마이그레이션 001~011 전체 재적용 멱등(`test_apply_migrations_is_idempotent`; 010 CHECK drop-and-add·011 DROP IF EXISTS·008 no-op 가 매 boot 재실행에 안전함을 모든 PG 테스트의 pg_clean 반복 적용으로 입증), 컬럼 드리프트 가드, reconcile/admission/lifecycle, dispatch 인스턴스 격리, ops_checks(transition_log 기반) 3건, **reset 5건**(wipe+자산 중심 착지·dry-run 무변경·멱등·wipe 후 admission·테이블 커버리지). -3. **RabbitMQ-gated**(실 브로커): 21 passed — slot_id 계약 포함 intent RPC e2e dedup. -4. **run-the-app e2e**(test DB + 실 RabbitMQ + paper broker, instance=e2etest, M=1): boot reconcile created=2 → snapshot(slots=2 inactive, available=1) → buy by slot_id admitted → 중복 intent identical → 2번째 자산 capacity_full → fill→holding(available=0) → 동일 슬롯 already_active → sell→fill→**inactive**(qty=None·portfolio_id 보존·available=1) → 동일 영구 슬롯 재매수 admitted. DB 최종행·transition_log·ledger_outbox(completed×3) 일치, 데몬 로그 ERROR/Traceback 0, SIGTERM Normal shutdown. (전 과정 implementation.md 검증 실행 절 기록) -5. **baseline 동기화**(Architecture Impact 有): data-model/runtime-flows/project-structure/interface-contract 4문서 + run-trex-core-daemon 스킬 + instance.example.toml 갱신 — commit 7d72782 포함. - -## Blocking Issues - -- 없음 (코드/테스트 범위). 단, **실 운영 DB 재초기화는 본 배치 산출물 범위 밖** — release(REL) 단계에서 운영자가 백업→`trex-core-reset --config --yes`→운영 TOML `max_active_slots` 설정 순으로 수행하고, 그 운영 증거는 release 검증에 귀속된다. 데이터 보존 cutover 폐기로 기존 보유는 재초기화 후 운영자가 재편입한다(REQ-062 경로). - -## Result - -- 포함 REQ AC 전 항목 evidence 연결 완료. REQ-064 는 데이터 보존 cutover → **DB 재초기화로 재정의**(CHG-20260610-03, 마이그레이션 009 결함 + dev/페이퍼 컨텍스트)되어 재정의 AC 로 검증. REQ-062 AC-3/4 는 issue #15 무자동조치 결정에 따른 보수 구현(의도된 편차). -- **Ready for Release: true** (배치 산출물 기준 — 코드·마이그레이션·reset 도구·테스트·baseline. 실 DB 재초기화 실행은 REL 단계 운영자 작업으로 release 게이트에 위임) diff --git a/docs/batches/bat-031_20260610_position-sizing/design.md b/docs/batches/bat-031_20260610_position-sizing/design.md deleted file mode 100644 index 608b785..0000000 --- a/docs/batches/bat-031_20260610_position-sizing/design.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-031 Design - -- Status: confirmed (D1~D4, 2026-06-10) -- Batch: BAT-031 - -## 설계 원칙 -- 수량 정본 = **브로커 잔고**(REQ-062). 슬롯 `qty` 는 (a) pending 동안 = **주문 수량**, (b) 체결/롤백 후 = **브로커 잔고(보유)** 로 재동기화한다. core 는 "의도 수량"을 별도 저장하지 않는다(dcy-021 OQ-1). -- avg_price 는 증액 시 가중평균, 매도(부분/전량) 시 cost basis 유지(dcy-021 OQ-3). -- 모든 전이는 dcy-020 자산 중심 모델에 additive(무회귀, NFR-2). - -## D1 — 증액: 주문 수량 vs 보유 수량 (브로커 정본 재동기화) -- `HOLDING→PENDING_BUY`(증액): `slot.qty := intent.qty`(추가 주문 수량), `avg_price` **보존**(가중평균용), `limit_price := price`. (generic 전이 규칙이 caller None 일 때 avg 보존 → 별도 service 분기 불요) -- dispatch 매수: `qty = slot.qty`(주문 수량) 그대로. -- `PENDING_BUY→HOLDING`(체결): 보유 = **브로커 잔고 total**, avg_price = 가중평균. - - `old_qty = max(0, broker_total − filled)`, `old_avg = slot.avg_price or 0`, `new_avg = (old_qty×old_avg + filled×fill_price)/broker_total`(broker_total 0 이면 fill_price). - - 초기 매수도 동일 공식(avg None→0, old_qty=0 → new_avg=fill_price) — 통일. - -## D2 — 초기 매수 vs 증액 판별 -- `INACTIVE→PENDING_BUY`(초기): slot 이 inactive 라 avg_price=None. -- `HOLDING→PENDING_BUY`(증액): avg_price 보존(>0). -- 체결/롤백 핸들러는 **브로커 잔고**로 판별(잔고>0 → 증액/보유 잔존, 0 → 초기/미보유). avg_price 보존은 가중평균 계산에만 사용. - -## D3 — 증액/매도 롤백 -- 매수 거부 `_rollback`: 브로커 잔고>0 → `PENDING_BUY→HOLDING`(qty=브로커 잔고, 증액 실패해도 기존 보유 유지) / 잔고 0 → `PENDING_BUY→INACTIVE`(초기 매수 실패). -- 매도 거부: `PENDING_SELL→HOLDING`(qty=브로커 잔고) (기존). - -## D4 — 부분 매도 + 전량 매도 구분 + 부분 체결 -- pending_sell 의 `slot.qty` 의미: **부분 매도** intent(SellIntent.qty 지정) → `slot.qty = 주문 수량`; **전량 매도**(svc.sell/safety-sell/kill-switch, qty 미지정) → `slot.qty = None`("전량" sentinel). → `_apply_transition_fields` 의 PENDING_SELL 진입 규칙: `new_qty = caller qty`(None 보존, mirror 안 씀). -- dispatch 매도: `qty = 브로커잔고 if slot.qty is None else min(int(slot.qty), 브로커잔고)`. 0 이면 skip+WARN(divergence, REQ-062). → 전량은 항상 브로커 정본(stale mirror 무관), 부분은 min(주문, 잔고)·잔고 이상이면 전량. -- `PENDING_SELL→체결`: 체결 후 **브로커 잔고**로 분기 — 잔고>0 → `HOLDING`(qty=잔고, avg=cost basis 유지) / 0 → `INACTIVE`. (기존 항상 INACTIVE 에서 변경 — 부분 매도 잔량 holding 지원) -- 부분 체결: 실제 체결분만 ledger(기존 `_record_ledger` = `fs.filled_qty`), 미체결 잔량 미추적. dcy-013 `_on_partial_cancel` 도 브로커 정본 resync 로 정합. - -## D-impl — 브로커 잔고 조회 -- `_poll` tick 시작 시 `broker.get_balance()` 1회 fetch → symbol→hldg_qty 맵을 fill/rollback 핸들러에 전달(체결당 중복 호출 회피). dispatch 의 매도 잔고 맵과 동일 패턴(BAT-030 M2). - -## intent (REQ-061) -- 매수 admission: 대상 슬롯이 `INACTIVE` → 초기 매수(M 캡 적용); `HOLDING` → **증액**(M 캡 미적용 — active 수 불변); `pending_*` → `already_active`. (증액은 별도 intent 타입 없이 holding 슬롯 buy 로 검출) -- 매도 admission: `SellIntent.qty`(선택) 추가 — 지정 시 부분 매도(slot.qty=order), 미지정 시 전량(slot.qty=None). - -## 상태머신 (REQ-057) -- `_BASE_TRANSITIONS` 에 `(HOLDING, PENDING_BUY)` 추가 → 13 transitions. - -## 롤백/회귀 -- 전부 additive. 기존 초기 매수/전량 매도/safety-sell/kill-switch/reconcile 경로 무회귀(전량 매도 slot.qty=None·dispatch 브로커 정본은 기존 동작과 동일 결과). - -## 체크포인트 결정 -- D1~D4 확정(브로커 정본 재동기화로 "주문 수량 vs 보유 수량" 충돌 해소, 별도 필드 없음). 구현 진행. diff --git a/docs/batches/bat-031_20260610_position-sizing/implementation.md b/docs/batches/bat-031_20260610_position-sizing/implementation.md deleted file mode 100644 index a0ab835..0000000 --- a/docs/batches/bat-031_20260610_position-sizing/implementation.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-031 Implementation - -- Status: done -- Batch: BAT-031 -- 일자: 2026-06-10 - -## 구현 요약 -design.md D1~D4 그대로 구현. 수량 정본=브로커 잔고 재동기화로 "주문 수량 vs 보유 수량" 충돌을 별도 필드 없이 해소. - -- **M1 (REQ-057)**: `state.py` `_BASE_TRANSITIONS` 에 `(HOLDING, PENDING_BUY)` 추가 → 13 전이(증액). -- **M2 (REQ-066/062)**: `service.py` `_apply_transition_fields` — PENDING_SELL 진입 시 `qty = caller as-is`(None=전량 sentinel, 값=부분 주문 qty). 증액 pending(HOLDING→PENDING_BUY)은 generic 분기가 avg_price 보존(caller None)·qty=주문으로 처리(별도 분기 불요). -- **M3 (REQ-061)**: `intents.py` — `_admit_buy` 가 HOLDING 슬롯 매수를 **증액**으로 검출(M 캡 미적용, `position_increase` reason), PENDING_* 만 `already_active`. `SellIntent.qty`(부분) 추가, `_admit_sell` 가 qty 를 transit 에 전달. `messaging_handlers` sell 파서에 `qty` 추출. -- **M4 (REQ-062/066)**: `execution.py` — `_balance_map()` 헬퍼. dispatch 매도 `qty = 브로커잔고 if slot.qty is None else min(int(slot.qty), 브로커잔고)`(0이면 skip+WARN). `_on_filled`: 매수 체결 → 보유=브로커 잔고·avg 가중평균; 매도 체결 → 브로커 잔고>0 `pending_sell→holding`(cost basis) / 0 `→inactive`. `_on_partial_cancel` 도 브로커 정본 resync. `_rollback`: 매수 거부 시 브로커>0 `→holding`(증액 실패, 기존 보유 유지) / 0 `→inactive`. -- **M5 (REQ-067)**: data-model/runtime-flows/interface-contract/project-structure 4문서 사이징 정합. - -## 변경 내역 (Changed Files) -- trex_core/domain/slot/state.py — holding→pending_buy 전이 -- trex_core/domain/slot/service.py — PENDING_SELL qty sentinel -- trex_core/application/intents.py — 증액 admission, SellIntent.qty, partial sell -- trex_core/application/execution.py — _balance_map, 부분/전량 매도 dispatch, 매수 체결 가중평균, 브로커 정본 resync, 증액 롤백 -- trex_core/application/messaging_handlers.py — sell qty 파싱 -- docs: data-model/runtime-flows/interface-contract/project-structure (REQ-067) -- tests: test_state(13 전이), test_service(TestPositionSizing 3), test_execution(전량/부분 매도·증액·capacity 5신규), test_execution_cancel(브로커 정본 잔량) - -## 테스트 -- 단위 전체: **378 passed** (45 skipped), ruff clean -- 통합(PG+RabbitMQ): **422 passed, 1 skipped**(KIS sandbox, 환경 무관) - -## 검증 실행 (run-the-app, 사이징 e2e) -라이브 trex_core(reset 후) + 실 RabbitMQ + paper + instance=test, M=5: -1. 초기 매수 qty=10 @1000 → holding 10 @1000 -2. **증액 +5 @1200**(holding 슬롯 매수, admitted) → holding **15 @1066.6667**(가중평균) -3. **부분 매도 qty=6** → holding **9 @1066.6667**(cost basis 유지) -4. **전량 매도**(qty 미지정) → **inactive**(qty None) -- 데몬 로그 ERROR/Traceback 0. messaging sell qty 갭은 e2e 에서 발견·수정(부분 매도가 전량으로 가던 버그) 후 재검증 green. - -## Remaining Risks / Blockers -- 전략 모듈(trex-orb) 사이징 intent 발행 전환 필요 — 기존 buy/sell 와 병존(additive)이라 무중단. -- 미체결 pending 고착 처분은 본 배치 범위 밖(issue #21 별도 Discovery). diff --git a/docs/batches/bat-031_20260610_position-sizing/index.md b/docs/batches/bat-031_20260610_position-sizing/index.md deleted file mode 100644 index 1c20159..0000000 --- a/docs/batches/bat-031_20260610_position-sizing/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-031: 보유 자산 포지션 사이징 (dcy-021) - -- Status: released -- Profile: standard -- Source Discovery: dcy-021 -- Included REQ: REQ-066 (사이징 도메인·end-to-end), REQ-067 (baseline 정합), REQ-057 (CHG-03 상태머신 holding→pending_buy), REQ-061 (CHG-04 사이징 intent), REQ-062 (CHG-03 부분 매도 집행) - -## Summary -- dcy-021: 자산 영구 슬롯(dcy-020)의 수량 가변 전제 위에 **보유 포지션 수량 조정**(증액·부분 감액)을 구현한다. 명령형 계약(전략이 cash+quantity 로 qty+price 계산·전송), core 는 실행 substrate(target 미저장·미추적), 수량 정본 = 브로커 잔고. - -## Scope -- In: 증액(holding→pending_buy→holding, avg_price 가중평균), 부분 감액(부분 매도 min(intent,잔고), 잔량 holding/전량 inactive, cost basis), 부분 체결 실제만 반영·미추적, 사이징 intent + 멱등, baseline 4문서. -- Out: 능동 capacity 배분, 자동 리밸런싱 스케줄러, 미체결 pending 고착 처분(issue #21). - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 집행 핫패스(라이브 주문) 변경 — additive·paper 검증. design.md 의 D1~D4 확정 후 구현. -- 본 배치 완료 후 REQ-057/061/062/066/067 confirm-req-implemented. diff --git a/docs/batches/bat-031_20260610_position-sizing/planning.md b/docs/batches/bat-031_20260610_position-sizing/planning.md deleted file mode 100644 index 0d31e5d..0000000 --- a/docs/batches/bat-031_20260610_position-sizing/planning.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-031 Planning - -- Status: confirmed -- Batch: BAT-031 -- Profile: standard - -## Included REQ -- REQ-066 (Interface, Approved): 사이징 도메인 수량/avg_price 재계산 + end-to-end. -- REQ-067 (Documentation, Approved): baseline 4문서 정합. -- REQ-057 (CHG-20260610-03, Approved): 상태머신 `holding→pending_buy` 전이. -- REQ-061 (CHG-20260610-04, Approved): 사이징 intent(증액/부분 매도, 명령형 qty+price). -- REQ-062 (CHG-20260610-03, Approved): 부분 매도 집행 `min(intent,잔고)`·부분 체결 실제만. - -## Delivery Plan (마일스톤) -1. **M1 (REQ-057)**: `state.py` 전이 집합에 `HOLDING→PENDING_BUY` 추가(증액). test_state 갱신(13 transitions). -2. **M2 (REQ-066)**: `service.py` 전이 필드 규칙 — 증액 pending(avg_price 보존), 매수 체결 avg_price 가중평균 + 수량 = 브로커 정본 resync. -3. **M3 (REQ-061)**: `intents.py` 사이징 admission — 증액 매수(holding 슬롯 buy, `already_active` 예외)·부분 매도(SellIntent qty). messaging_handlers payload(qty/price). -4. **M4 (REQ-062)**: `execution.py` — 부분 매도 dispatch qty = `min(intent, 브로커 잔고)`, 매수/매도 체결 시 broker 정본 resync·avg 가중평균, 증액 롤백은 holding 복귀. -5. **M5 (REQ-067)**: baseline 4문서 정합 + run-the-app e2e. - -## Dependencies -- Internal: dcy-020(자산 영구 슬롯·REQ-062 브로커 정본·집행 루프) 전 단계 green. -- External: 전략 모듈(trex-orb)이 사이징 intent 발행 — 기존 buy/sell 와 병존(additive)이라 무중단. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: M3/M4 는 **라이브 주문 집행 동작 변경**(증액 매수·부분 매도 dispatch). 증액 시 "주문 수량 vs 보유 수량" 충돌·avg_price 가중평균·증액 롤백·부분 체결 처리는 잘못 구현 시 과매수/과매도/평단 오류 → design.md 에서 확정. - -## Milestones -1. M1 상태머신 + test -2. M2 service avg/qty -3. M3 intent -4. M4 execution -5. M5 baseline + e2e - -## Risks -- R-exec (High): 증액/부분 매도 집행 회귀(과매수·과매도·avg drift). 대응: 브로커 정본 resync(REQ-062), 멱등, 단위+paper e2e. -- R-statemachine (Medium): 전이 추가 회귀. 대응: additive + test_state 회귀 가드. -- R-strategy (Medium): trex-orb 계약 전환. 대응: 기존 intent 병존. - -## Notes -- green 기준: 단위 전체 회귀(증액/부분 매도/부분 체결/avg 가중평균) + paper run-the-app e2e + ruff. diff --git a/docs/batches/bat-031_20260610_position-sizing/verification.md b/docs/batches/bat-031_20260610_position-sizing/verification.md deleted file mode 100644 index c9e68cf..0000000 --- a/docs/batches/bat-031_20260610_position-sizing/verification.md +++ /dev/null @@ -1,51 +0,0 @@ -# BAT-031 Verification - -- Status: approved (confirm-batch-verification, 2026-06-10) -- Batch: BAT-031 -- 검증 환경: Python 3.12 / PostgreSQL trex_core_test / 실 RabbitMQ / paper broker - -## Acceptance Mapping - -### REQ-066 (포지션 사이징 end-to-end) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 증액 가중평균 | pass | `test_increase_accumulates_qty_and_weights_avg_price`(10@1000 +5@1200 → 15@1066.67) + e2e 2단계 | -| AC-2 부분 매도 잔량 holding·cost basis | pass | `test_partial_sell_qty_is_min_intent_broker`(잔량 6, avg 1000) + e2e 3단계(9 @1066.67) | -| AC-3 전량 매도 → inactive | pass | `test_full_sell_qty_is_broker_balance`·`test_partial_sell_caps_at_broker_balance` + e2e 4단계 | -| AC-4 부분 체결 실제만 반영·미추적 | pass | `test_execution_cancel`(부분 체결→브로커 정본 잔량) + _on_filled/_on_partial_cancel 브로커 resync | -| AC-5 무회귀 | pass | 단위 378 / 통합 422 passed | - -### REQ-057 (CHG-03 상태머신 holding→pending_buy) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 전이 집합(13) | pass | `test_state.py`(holding→pending_buy allowed, size 13) | -| AC-5 증액 전이·복귀 | pass | `test_holding_to_pending_buy_increase_preserves_avg` + e2e | - -### REQ-061 (CHG-04 사이징 intent) -| AC | 판정 | Evidence | -|---|---|---| -| AC-6 증액 매수(holding) | pass | `test_increase_*` + e2e 2단계(admitted) | -| AC-7 부분 매도 qty·명령형·core 미저장 | pass | `test_partial_sell_*` + messaging sell qty 파싱 + e2e 3단계 | - -### REQ-062 (CHG-03 부분 매도 집행) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1 매도 = min(intent, 브로커)·이상이면 전량 | pass | `test_partial_sell_qty_is_min_intent_broker`·`test_partial_sell_caps_at_broker_balance` | -| AC-5 부분 체결 실제만·미추적 | pass | `test_execution_cancel` + _balance_map resync | - -### REQ-067 (baseline 정합) -| AC | 판정 | Evidence | -|---|---|---| -| AC-1~3 data-model/runtime-flows/interface-contract/project-structure 정합 | pass | commit (사이징 흐름·13 전이·intent qty·가중평균 반영) | - -## Evidence -1. 단위: `pytest tests` → **378 passed**, ruff clean. -2. 통합(PG `trex_core_test` + 실 RabbitMQ): **422 passed, 1 skipped**(KIS sandbox 환경 이슈, 무관). -3. **run-the-app e2e**(라이브 trex_core + 실 MQ + paper, M=5): 초기 매수 10@1000 → 증액 +5@1200 → holding 15@1066.67(가중평균) → 부분 매도 6 → holding 9@1066.67(cost basis) → 전량 매도 → inactive. 데몬 ERROR 0. -4. baseline 동기화: 4문서 + (run-the-app e2e 가 증액·부분/전량 매도 흐름 실증). - -## Blocking Issues -- 없음. 전략 모듈(trex-orb) 사이징 전환은 병존(additive)이라 무중단; 미체결 pending 처분은 issue #21(별도) 범위. - -## Result -- 포함 REQ AC 전부 evidence 연결. **Ready for Release: true** (코드·단위·통합·e2e·baseline). 실 운영 전환은 전략 모듈 사이징 발행 시 활성(계약 병존). diff --git a/docs/batches/bat-032_20260610_execution-policy/design.md b/docs/batches/bat-032_20260610_execution-policy/design.md deleted file mode 100644 index 1c5b25a..0000000 --- a/docs/batches/bat-032_20260610_execution-policy/design.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-032 Design - -- Status: approved -- Batch: BAT-032 - -## 설계 결정 - -### D1: 실행 정책 필드를 슬롯 컬럼으로 저장 (timeout_sec, slippage_pct) - -- 결정: `Slot` 모델에 `timeout_sec: Optional[int]`, `slippage_pct: Optional[float]` 컬럼 추가. PENDING_* 진입 시 set, 탈출(→HOLDING/→INACTIVE) 시 clear. -- 근거: 집행 루프가 dispatch/poll 시 슬롯만 읽으면 되어 별도 캐시/in-memory 구조 불필요. limit_price 와 동일한 패턴(set on pending entry, cleared on exit). -- 대안: in-memory dict 에 별도 저장 → 재시작 시 유실, 재기동 reconciliation 에서 timeout 추적 불가. 기각. - -### D2: updated_at 을 dispatch 시각 proxy 로 사용 (기존 stale watchdog 방법 계승) - -- 결정: timeout 경과 시간 = `(now - slot.updated_at).total_seconds()`. mark_dispatched 가 updated_at 을 갱신하므로 dispatch 시각과 일치. -- 근거: `_check_stale_pending`(REQ-014) 이 이미 동일 방법 사용 → 일관성, 추가 필드 불필요. -- 한계: PENDING_* 상태에서 `update_thresholds` 가 호출되면 updated_at 이 리셋될 수 있으나, `update_thresholds`는 HOLDING 슬롯에만 작동(REQ-040) → 실질적 충돌 없음. - -### D3: cancel_order best-effort, 즉시 롤백 없음 (race 안전) - -- 결정: `_try_timeout_cancel` 에서 cancel_order 송신만 하고 슬롯 상태를 변경하지 않는다. 다음 poll 틱에서 is_terminal_reject/is_partial_cancel 로 수렴. -- 근거: 취소-체결 race(cancel 수락 직전 체결) 시 다음 poll 이 is_filled 경로로 정상 수렴 → 임의 롤백 없음. dcy-013 부분 취소 선례 재사용. - -### D4: cancel CLI → control RPC → ExecutionService.cancel_pending_slot - -- 결정: CLI는 `trex.control.req` 채널로 `{"action": "cancel_pending", "slot_id": ...}` 를 전송. 제어 핸들러가 `ExecutionService.cancel_pending_slot(slot_id)` 를 호출해 cancel_order 를 송신. 슬롯 전이 없음. -- 근거: 기존 kill_switch·pause 와 동일한 경로(단일 writer 불변식 보존). execution_service 를 control 핸들러에 주입하는 방식으로 커플링 최소화. -- 대안: intent RPC 채널 재사용 → intent 계약(buy/sell)과 의미 혼용. 기각. - -## 컴포넌트 다이어그램 (텍스트) - -``` -CLI: trex-core cancel - → control RPC (trex.control.req) cancel_pending - → make_control_rpc_handler (execution_service.cancel_pending_slot) - → broker.cancel_order(broker_order_id) - → [다음 tick _poll] is_terminal_reject / is_partial_cancel - → rollback / partial-preserve (기존 경로) -``` - -``` -Intent: {intent_type: "buy", timeout_sec: 60, slippage_pct: 0.1} - → messaging_handlers (파싱 → BuyIntent) - → IntentService._admit_buy (effective_timeout 해결 → transit_slot) - → SlotService (slot.timeout_sec=60, slot.slippage_pct=0.1 set) - → [_dispatch] slippage 실효 지정가 → OrderRequest.limit_price - → [_poll] timeout 초과 → _try_timeout_cancel → cancel_order - → [다음 tick] is_terminal_reject → rollback -``` diff --git a/docs/batches/bat-032_20260610_execution-policy/implementation.md b/docs/batches/bat-032_20260610_execution-policy/implementation.md deleted file mode 100644 index e4d09e1..0000000 --- a/docs/batches/bat-032_20260610_execution-policy/implementation.md +++ /dev/null @@ -1,62 +0,0 @@ -# BAT-032 Implementation - -- Status: done -- Batch: BAT-032 - -## M1 — 실행 정책 계약·파싱·슬롯 저장 (REQ-068, REQ-061 CHG-05) - -**변경 파일** - -| 파일 | 내용 | -|---|---| -| `trex_core/domain/slot/models.py` | `Slot` dataclass 에 `timeout_sec: Optional[int]`, `slippage_pct: Optional[float]` 추가. `EXPECTED_SLOT_FIELDS` 갱신. | -| `trex_core/adapters/persistence/sql/012_add_execution_policy_fields.sql` | `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS timeout_sec INTEGER; … slippage_pct FLOAT;` | -| `trex_core/domain/slot/service.py` | `transit_slot`·`_apply_transition_fields` kwargs 확장. PENDING_* 진입 시 set, HOLDING/INACTIVE/ERROR 진입 시 clear (limit_price 동일 생애주기). | -| `trex_core/adapters/persistence/postgresql.py` | `_SLOT_COLUMNS`에 col 19·20 추가. `update_slot` SQL 갱신. `_row_to_slot` 인덱스 추가. | -| `trex_core/application/intents.py` | `BuyIntent`·`SellIntent` 에 `timeout_sec`·`slippage_pct` 추가. `IntentService.__init__` 에 `default_order_timeout_sec`. `_admit_buy`·`_admit_sell` 에서 effective_timeout 해석. | -| `trex_core/application/messaging_handlers.py` | buy/sell 핸들러에서 `_opt_int`·`_opt_float` 파싱 추가. | -| `trex_core/runtime/config.py` | `DaemonInstanceConfig.default_order_timeout_sec`. | -| `trex_core/runtime/daemon.py` | `IntentService` wiring 에 `default_order_timeout_sec` 전달. | - -## M2 — slippage 보호 지정가 dispatch (REQ-070, REQ-013 CHG-03 partial) - -**변경 파일** - -| 파일 | 내용 | -|---|---| -| `trex_core/application/execution.py` | `_dispatch`: limit 계산 후 slippage 블록 추가. buy: `ceil(limit*(1+pct/100))`, sell: `floor(limit*(1-pct/100))`. `import math` 추가. | - -## M3 — timeout 집행 + race 처리 (REQ-069, REQ-013 CHG-03 partial) - -**변경 파일** - -| 파일 | 내용 | -|---|---| -| `trex_core/application/execution.py` | `ExecutionResult.timeout_cancelled` 추가. `_poll` else branch에 `_try_timeout_cancel` 추가. `_try_timeout_cancel` 구현: `slot.updated_at` proxy로 경과 체크 → `cancel_order` best-effort → `r.timeout_cancelled += 1`. | - -## M4 — 운영자 cancel 백스톱 (REQ-071) - -**변경 파일** - -| 파일 | 내용 | -|---|---| -| `trex_core/application/execution.py` | `cancel_pending_slot(slot_id)` 메서드: PENDING_* slot → `cancel_order` 송신 → "ok"/"not_pending"/"slot_not_found"/"not_dispatched"/"cancel_failed" 반환. 슬롯 전이 없음 (단일 writer 보존). | -| `trex_core/application/messaging_handlers.py` | `make_control_rpc_handler` 에 `cancel_pending_fn` 파라미터. `cancel_pending` 액션 라우팅. `_handle_cancel_pending` 구현. | -| `trex_core/runtime/cli.py` | `cancel ` 서브커맨드. `_cmd_cancel`. `_add_intent_guards` (--yes/--dry-run/--live). control RPC 경유. | -| `trex_core/runtime/daemon.py` | `make_control_rpc_handler(cancel_pending_fn=self.execution.cancel_pending_slot)` 배선. | - -## M5 — baseline 문서 정합 (REQ-072) - -**변경 파일** - -| 파일 | 내용 | -|---|---| -| `docs/interface-contract.md` | buy/sell payload에 `timeout_sec`·`slippage_pct` 필드 기술. 하위 호환 주석 추가. cancel CLI 참조. | -| `docs/runtime-flows.md` | dispatch 스텝: slippage 지정가 주석. poll 스텝: timeout 자동 취소 흐름. 실행 정책 생애주기. "운영자 cancel 백스톱" 섹션 추가. | - -## 설계 결정 근거 (design.md 요약) - -- **D1**: `timeout_sec`/`slippage_pct` 를 slot 컬럼으로 저장 — `limit_price` 와 동일 생애주기(PENDING 진입 시 set, 탈출 시 clear). -- **D2**: `slot.updated_at` 을 dispatch 시각 proxy로 사용 — 기존 stale watchdog(REQ-014)과 동일 방법, 별도 컬럼 불필요. -- **D3**: `cancel_order` 송신만, 즉시 롤백 없음 — cancel-fill race 안전. 다음 poll의 `is_terminal_reject`/`is_partial_cancel` 경로 재사용. -- **D4**: cancel CLI → control RPC → `ExecutionService.cancel_pending_slot` — 단일 writer 불변식(A) 보존. diff --git a/docs/batches/bat-032_20260610_execution-policy/index.md b/docs/batches/bat-032_20260610_execution-policy/index.md deleted file mode 100644 index 9623e23..0000000 --- a/docs/batches/bat-032_20260610_execution-policy/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-032: 주문 실행 정책 — timeout·slippage·운영자 cancel (dcy-022) - -- Status: released -- Profile: standard -- Source Discovery: dcy-022 -- Included REQ: REQ-068 (실행 정책 계약), REQ-069 (timeout 집행·race), REQ-070 (slippage dispatch), REQ-071 (운영자 cancel 백스톱), REQ-072 (baseline 정합), REQ-061 (CHG-05), REQ-013 (CHG-03) - -## Summary -- dcy-022: dcy-021(포지션 사이징) 전제 위에 **주문 실행 정책**(timeout_sec·slippage_pct)을 intent 계약에 추가하고 집행 루프에 배선한다. 미체결 주문은 timeout 초과 시 자동 취소·롤백(고착 예방), slippage 보호 지정가로 과도한 불리 체결을 방지. 잔여 고착 백스톱으로 운영자 cancel CLI 를 추가한다. - -## Scope -- In: timeout_sec/slippage_pct intent 계약·파싱·슬롯 저장, timeout 집행 (cancel_order 배선·race 처리·부분 보존), slippage 실효 지정가 dispatch, 운영자 cancel 백스톱(trex-core cancel), per-instance TOML default_order_timeout_sec, baseline 2문서. -- Out: IOC/FOK 네이티브 TIF, 자동 리밸런싱, 전략 모듈(orb-v1) 발신 계약 전환(외부 repo, 별도 합의). - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 집행 핫패스(라이브 주문) 확장 — additive(기존 경로 보존). design.md D1~D4 확정 후 구현. -- 본 배치 완료 후 REQ-013/061/068/069/070/071/072 confirm-req-implemented. diff --git a/docs/batches/bat-032_20260610_execution-policy/planning.md b/docs/batches/bat-032_20260610_execution-policy/planning.md deleted file mode 100644 index 95f22f7..0000000 --- a/docs/batches/bat-032_20260610_execution-policy/planning.md +++ /dev/null @@ -1,64 +0,0 @@ -# BAT-032 Planning - -- Status: done -- Batch: BAT-032 - -## Milestones - -| ID | 목표 | 연결 REQ | -|---|---|---| -| M1 | 실행 정책 계약·파싱·슬롯 저장 | REQ-068, REQ-061(CHG-05) | -| M2 | slippage 보호 지정가 dispatch | REQ-070, REQ-013(CHG-03 partial) | -| M3 | timeout 집행 + race 처리 | REQ-069, REQ-013(CHG-03 partial) | -| M4 | 운영자 cancel 백스톱 CLI + control RPC | REQ-071 | -| M5 | baseline 문서 정합 | REQ-072 | - -## M1 상세 (실행 정책 계약·파싱·슬롯 저장) - -- `Slot` 모델: `timeout_sec: Optional[int]`, `slippage_pct: Optional[float]` 필드 추가. `EXPECTED_SLOT_FIELDS` 갱신. -- Migration 012: `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS timeout_sec INTEGER; ADD COLUMN IF NOT EXISTS slippage_pct FLOAT;` -- `BuyIntent`/`SellIntent`: `timeout_sec`/`slippage_pct` 추가. -- `IntentService`: `__init__` 에 `default_order_timeout_sec: Optional[int] = None` 추가. `_admit_buy`/`_admit_sell` 에서 effective_timeout = intent.timeout_sec ?? config_default → transit_slot 에 전달. -- `SlotService.transit_slot` kwargs: `timeout_sec`/`slippage_pct` 전달. `_apply_transition_fields` 에서 PENDING_* 진입 시 set, 탈출(→HOLDING/→INACTIVE) 시 clear. -- `messaging_handlers.py`: `timeout_sec=_opt_int(body.get("timeout_sec"))`, `slippage_pct=_opt_float(body.get("slippage_pct"))` 파싱 추가. -- `DaemonInstanceConfig`: `default_order_timeout_sec: Optional[int] = None` 추가, from_toml 파싱. -- `DaemonConfig` + daemon runtime 배선: IntentService 에 default_order_timeout_sec 전달. - -## M2 상세 (slippage 보호 지정가 dispatch) - -- `execution._dispatch`: limit 계산 후 slippage 적용: - ```python - import math - if limit is not None and slot.slippage_pct: - factor = 1 + slot.slippage_pct/100 if side == "buy" else 1 - slot.slippage_pct/100 - limit = int(math.ceil(limit * factor)) if side == "buy" else int(math.floor(limit * factor)) - ``` -- `_log.debug(...)` 로 slippage 적용 기록. - -## M3 상세 (timeout 집행) - -- `ExecutionResult`: `timeout_cancelled: int = 0` 추가. -- `_poll` 의 else branch(`_check_stale_pending` 호출)에서 timeout 체크 추가: - ```python - self._check_stale_pending(slot, now, r) # Alert-only (REQ-014, no change) - self._try_timeout_cancel(slot, now, r) # NEW: cancel if timeout_sec exceeded - ``` -- `_try_timeout_cancel`: `slot.timeout_sec` 있고 경과 시간 초과 시 → `broker.cancel_order(...)` best-effort → `r.timeout_cancelled += 1` → WARNING 로그. 실패 시 WARNING, 상태 불변. - -## M4 상세 (운영자 cancel 백스톱) - -- `messaging_handlers.make_control_rpc_handler`: `cancel_pending` 액션 추가. `cancel_pending_fn` 콜백(slot_id → bool) 주입. -- `execution.ExecutionService.cancel_pending_slot(slot_id)`: 슬롯 조회 → PENDING_* 이면 `broker.cancel_order` 송신. 슬롯 직접 전이 없음. -- `cli.py`: `cancel ` 서브커맨드 추가. `--dry-run`/`--yes`/`--live` 안전장치. control RPC 경유. -- daemon.py 배선: make_control_rpc_handler 에 cancel_pending_fn 전달. - -## M5 상세 (baseline 문서) - -- `docs/interface-contract.md`: buy/sell payload 에 `timeout_sec`/`slippage_pct` 추가. -- `docs/runtime-flows.md`: 집행 루프·dispatch 섹션에 timeout 취소·slippage·cancel 백스톱 흐름 추가. - -## 위험 요소 - -- **R-1 (취소-체결 race)**: cancel_order 송신 후 즉시 롤백하지 않고 다음 poll 수렴 → 기존 is_terminal_reject/is_partial_cancel 경로 재사용 완화. -- **R-2 (부분 체결 보존)**: timeout cancel → is_partial_cancel → 기존 `_on_partial_cancel` 선례(dcy-013) 재사용. -- **R-3 (단일 writer)**: cancel CLI 는 cancel_order 만 송신; 슬롯 전이는 집행 루프 단독 → 불변식 보존. diff --git a/docs/batches/bat-032_20260610_execution-policy/verification.md b/docs/batches/bat-032_20260610_execution-policy/verification.md deleted file mode 100644 index ce4275c..0000000 --- a/docs/batches/bat-032_20260610_execution-policy/verification.md +++ /dev/null @@ -1,69 +0,0 @@ -# BAT-032 Verification - -- Status: approved -- Batch: BAT-032 - -## Test Evidence - -단위 테스트 425 passed, 45 skipped (통합 제외). 신규 71 tests. - -``` -.venv/bin/python -m pytest tests/ -q --ignore=tests/integration -425 passed, 45 skipped in 1.18s -``` - -## 신규 테스트 파일 - -| 파일 | 커버리지 | -|---|---| -| `tests/domain/slot/test_execution_policy_lifecycle.py` | REQ-068: PENDING 진입 시 set, HOLDING/INACTIVE 탈출 시 clear (10 cases) | -| `tests/application/test_execution_policy.py` | REQ-069/070/071: slippage dispatch (buy ceil, sell floor, market=skip, no-slippage=unchanged, zero) + timeout cancel (exceeded→cancel sent, not exceeded→no cancel, no timeout_sec→no cancel, exception best-effort, state unchanged, cancel-fill race) + cancel_pending_slot (ok, not_pending, slot_not_found, not_dispatched, cancel_failed, state unchanged) | -| `tests/application/test_execution_policy_handler.py` | REQ-068/071 핸들러: buy/sell intent payload 파싱 (timeout_sec/slippage_pct), default_order_timeout_sec 계층적 해석, control cancel_pending 액션 (ok/not_pending/slot_not_found/no-fn/cancel_failed/missing-slot_id) | -| `tests/runtime/test_cli_operator.py` (기존 확장) | REQ-071 cancel CLI: 확인 없이 중단, --yes 제출, --dry-run 스킵, --json 출력, ignored/not_found 응답 처리 | - -## REQ 별 AC 검증 - -### REQ-068 (실행 정책 계약) -- AC-1 (timeout_sec 파싱·저장): `test_buy_timeout_sec_parsed_and_stored_on_slot`, `test_timeout_sec_set_on_pending_buy_entry` -- AC-2 (slippage_pct 파싱·저장): `test_buy_slippage_pct_parsed_and_stored_on_slot`, `test_slippage_pct_set_on_pending_buy_entry` -- AC-3 (config default): `test_config_default_applied_when_intent_has_no_timeout` -- AC-4 (하위 호환): `test_buy_policy_fields_absent_means_none`, `test_policy_fields_default_to_none_when_not_given` -- AC-5 (per-intent override): `test_per_intent_timeout_overrides_config_default` -- AC-6 (cleared on exit): `test_policy_cleared_on_transition_to_holding`, `test_policy_cleared_on_transition_to_inactive` - -### REQ-069 (timeout 집행·race) -- AC-1 (timeout 초과 → cancel_order): `test_timeout_exceeded_sends_cancel_order` -- AC-2 (is_terminal_reject → rollback): race 수렴은 기존 `test_full_external_cancel_returns_slot_to_holding` 재사용 -- AC-3 (is_partial_cancel → 부분 보존): 기존 `test_partial_fill_then_cancel_records_and_keeps_remainder` 재사용 -- AC-4 (cancel 후 fill race → is_filled 경로): `test_timeout_cancel_then_fill_race_handled` -- AC-5 (timeout_sec 없으면 cancel 없음): `test_no_timeout_sec_no_cancel` -- AC-6 (exception best-effort): `test_timeout_cancel_best_effort_on_exception` - -### REQ-070 (slippage 지정가) -- AC-1 (buy ceil): `test_buy_slippage_ceil_applied_to_limit` -- AC-2 (sell floor): `test_sell_slippage_floor_applied_to_limit` -- AC-3 (market = ignore): `test_no_slippage_when_market_order_no_entry_price` -- AC-4 (slippage 없으면 기존 동작): `test_no_slippage_when_slippage_pct_is_none` -- AC-5 (0% = 기본 가격): `test_zero_slippage_uses_base_price` -- AC-6 (분수 입력 ceil/floor): `test_buy_slippage_fractional_ceil` - -### REQ-071 (운영자 cancel 백스톱) -- AC-1 (PENDING_BUY cancel): `test_ok_sends_cancel_and_returns_ok` -- AC-2 (루프 수렴): 단일 writer 설계로 execution loop 수렴 보장 (`test_cancel_does_not_change_slot_state`) -- AC-3 (PENDING_SELL same): cancel_pending_slot 구현에 PENDING_SELL 포함 (코드 검증) -- AC-4 (HOLDING ignored): `test_not_pending_returns_not_pending` -- AC-5 (not_found): `test_slot_not_found_returns_slot_not_found` -- AC-6 (--dry-run): `test_cancel_dry_run_does_not_submit` - -### REQ-072 (baseline 정합) -- AC-1~4: docs/interface-contract.md, docs/runtime-flows.md 수동 확인 완료 (M5). - -## 기존 테스트 회귀 없음 - -전체 425 passed — REQ-013/061 기존 AC 포함 회귀 없음. - -| 항목 | 결과 | -|---|---| -| 단위 테스트 (non-integration) | 425 passed, 45 skipped | -| 신규 tests | 71 | -| 회귀 | 없음 | diff --git a/docs/batches/bat-033_20260610_enter-qty-validation/implementation.md b/docs/batches/bat-033_20260610_enter-qty-validation/implementation.md deleted file mode 100644 index f585222..0000000 --- a/docs/batches/bat-033_20260610_enter-qty-validation/implementation.md +++ /dev/null @@ -1,38 +0,0 @@ -# BAT-033 Implementation - -- Status: done -- Batch: BAT-033 - -## 변경 파일 - -| 파일 | 변경 유형 | 연결 REQ/AC | -|------|----------|-------------| -| `trex_core/runtime/cli.py` | 수정 | REQ-032 CHG-20260610-06 AC-7 | -| `tests/runtime/test_cli_operator.py` | 수정 | AC-7 검증 + 기존 enter 테스트 --qty 보완 | - -## M1: `enter --qty` 미지정 검증 - -### `trex_core/runtime/cli.py` - -`_cmd_enter` 함수 상단(forced_close_at 검증 앞)에 qty 검증 블록 추가: - -```python -# REQ-032 AC-7 (CHG-20260610-06, issue #23): qty is required — reject before -# RPC so a missing --qty fails immediately instead of admitting a zero-qty order -# that the broker rejects silently after the fact. -if not args.qty: - _fail("--qty is required for enter (no quantity → broker would reject)", args.json) - return 1 -``` - -- `--qty` 없음(None) 또는 0 → 즉시 `rc=1`, RPC 미호출 -- `--dry-run` 경로도 동일하게 걸림 (qty 검증이 `_intent_guard` 진입 전에 위치) -- 기존 `--qty` 있는 경우 동작 불변 - -### `tests/runtime/test_cli_operator.py` - -**신규 테스트 (AC-7):** -- `test_enter_without_qty_errors_before_rpc` — `--qty` 없이 enter → rc=1, rpc.calls==[], stderr에 `--qty` 포함 - -**기존 테스트 수정 (회귀 방지):** -기존 enter 테스트 9개에 `"--qty", "10"` (또는 적절한 값) 추가. qty 검증이 가장 앞에 위치하므로, 각 테스트가 본래 의도한 guard(confirm abort, live guard, json guard, forced_close_at 검증 등)에 도달하도록 보정. diff --git a/docs/batches/bat-033_20260610_enter-qty-validation/index.md b/docs/batches/bat-033_20260610_enter-qty-validation/index.md deleted file mode 100644 index 5d1ff24..0000000 --- a/docs/batches/bat-033_20260610_enter-qty-validation/index.md +++ /dev/null @@ -1,22 +0,0 @@ -# BAT-033: enter --qty 필수 검증 (issue #23) - -- Status: released -- Profile: batch-lite -- Source Discovery: REQ-032 change-req (issue #23) -- Included REQ: REQ-032 (CHG-20260610-06, AC-7) - -## Summary -- `trex-core enter`에 `--qty` 미지정 시 RPC 제출 전 즉시 오류 반환. 기존 `qty=None` → admitted → 집행 루프 KIS 거절 → 롤백이라는 "접수 성공처럼 보이지만 매수 안 됨" 혼동 제거. - -## Scope -- In: `trex_core/runtime/cli.py` `enter` 서브커맨드에 `--qty` 미지정 검증 + 단위 테스트 -- Out: IntentService admission 수량 검증(서비스 레이어), 도메인 변경, DB 스키마 변경 - -## Documents -- Planning: [planning.md](./planning.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- batch-lite minor-change fast path: 구조/인터페이스/런타임 흐름 영향 없음 (CLI argparse 단일 파일). -- Design Gate: design 불필요 (planning.md 참조). diff --git a/docs/batches/bat-033_20260610_enter-qty-validation/planning.md b/docs/batches/bat-033_20260610_enter-qty-validation/planning.md deleted file mode 100644 index 21d53c9..0000000 --- a/docs/batches/bat-033_20260610_enter-qty-validation/planning.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-033 Planning - -- Status: done -- Batch: BAT-033 - -## Milestones - -| ID | 목표 | 연결 REQ | -|---|---|---| -| M1 | `enter --qty` 미지정 검증 + 테스트 | REQ-032 (CHG-20260610-06 AC-7) | - -## M1 상세 (enter --qty 미지정 검증) - -`trex_core/runtime/cli.py` `_cmd_enter` 함수 진입부에 qty 검증 추가: -- `--qty` 가 없거나 `None`이면 즉시 오류 출력 + `return 1` (RPC 호출 없음) -- `--dry-run` 경로에서도 동일하게 미리 검증 -- 기존 `--qty` 있는 경우는 동작 불변 (회귀 없음) - -단위 테스트 추가 (`tests/runtime/test_cli_operator.py`): -- `enter` without `--qty` → rc=1, rpc.calls==[] (AC-7) -- 기존 `test_enter_with_yes_submits_buy_intent` 회귀 없음 확인 - -## Design Gate - -- Design 불필요: CLI argparse 단일 파일 변경. 구조/인터페이스/런타임 흐름 영향 없음. -- 검증: 단위 테스트 + 기존 suite 회귀 없음으로 충분. - -## 위험 요소 - -- R-1 (dry-run 경로 누락): dry-run에서도 qty 체크를 해야 오류 메시지를 미리 볼 수 있음 → 동일 검증 로직 적용으로 해소. diff --git a/docs/batches/bat-033_20260610_enter-qty-validation/verification.md b/docs/batches/bat-033_20260610_enter-qty-validation/verification.md deleted file mode 100644 index 3e296b7..0000000 --- a/docs/batches/bat-033_20260610_enter-qty-validation/verification.md +++ /dev/null @@ -1,21 +0,0 @@ -# BAT-033 Verification - -- Status: approved -- Batch: BAT-033 - -## 검증 항목 - -| AC | 시나리오 | 테스트 | 결과 | -|----|----------|--------|------| -| AC-7 | `enter` without `--qty` → rc=1, rpc.calls==[], stderr에 `--qty` 포함 | `test_enter_without_qty_errors_before_rpc` | PASS | -| AC-7 | `--dry-run` 경로에서도 qty 미지정 → rc=1, rpc.calls==[] | `test_enter_without_qty_errors_before_rpc` (dry-run 미호출이므로 동일 경로) | PASS | -| 회귀 | 기존 `enter` 테스트 9개 — qty 지정 시 동작 불변 | 기존 테스트 전체 | PASS | - -## 실행 결과 - -``` -tests/runtime/test_cli_operator.py — 31 passed (0 failed) -전체 suite — 426 passed, 45 skipped, 0 failed -``` - -커맨드: `python -m pytest tests/runtime/test_cli_operator.py -q` diff --git a/docs/batches/bat-034_20260610_drop-allocated-capital/design.md b/docs/batches/bat-034_20260610_drop-allocated-capital/design.md deleted file mode 100644 index ccaf626..0000000 --- a/docs/batches/bat-034_20260610_drop-allocated-capital/design.md +++ /dev/null @@ -1,50 +0,0 @@ -# BAT-034 Design - -- Status: done -- Batch: BAT-034 - -## D1. 제거 지점 전수 목록 (소스 grep 기준) - -| # | 파일 | 위치 | 제거 내용 | REQ | -|---|------|------|----------|-----| -| 1 | `trex_core/domain/slot/models.py` | `Slot.allocated_capital` 필드 + `EXPECTED_SLOT_FIELDS` 항목 | 도메인 모델·필드셋 계약 | REQ-002 | -| 2 | `trex_core/domain/slot/service.py` | `transit_slot` 파라미터, `_apply_transition_fields` 파라미터·INACTIVE clear·carry-forward `replace(...)` | 설정/전이 경로 | REQ-002 | -| 3 | `trex_core/application/intents.py` | `BuyIntent.allocated_capital` 필드, `submit_buy` dedup payload 키, `_admit_buy`→`transit_slot` 전달 | intent 계약·admission | REQ-007 | -| 4 | `trex_core/application/messaging_handlers.py` | buy payload `_opt_float(body.get("allocated_capital"))` 파싱 | 수신 무시(즉시 제거) | REQ-007 | -| 5 | `trex_core/adapters/persistence/postgresql.py` | `_SLOT_COLUMNS` 문자열, UPDATE 절, `_row_to_slot` 인덱스 매핑(컬럼 제거로 인덱스 시프트 — 주석 포함 재정렬) | flat 매핑 | REQ-006 | -| 6 | `trex_core/adapters/persistence/sql/013_drop_allocated_capital.sql` | 신규 — `ALTER TABLE daily_slot DROP COLUMN IF EXISTS allocated_capital;` | 스키마 | REQ-006 | -| 7 | `trex_core/application/portfolio.py` | `slots[]` 항목의 `"allocated_capital"` 키 | snapshot 노출 | REQ-028 | -| 8 | `trex_core/runtime/cli.py` | `--capital` argparse 인자, `_cmd_enter` summary·payload, `_print_slot_detail` 행 | CLI 표면 | REQ-032 | - -테스트: `tests/` 전수 grep 으로 fixture·assert 의 allocated_capital/`--capital` 참조 정리 (BAT-033 의 AC-7 테스트는 보존). - -## D2. 마이그레이션 013 (비가역 drop) - -```sql --- BAT-034 / dcy-023 / REQ-006 (CHG-20260610-02): drop dead column (비가역). --- allocated_capital 은 사이징·회계·전략 결정 어디에도 소비되지 않는 죽은 메타데이터 --- (dcy-023 §3 전수 추적). 실제 주문 수량은 slot.qty 가 단독 결정(dcy-021 계약). --- Idempotent via IF EXISTS. Rollback = 사전 백업 복원 (BAT-030/011 선례 준용). -ALTER TABLE daily_slot DROP COLUMN IF EXISTS allocated_capital; -``` - -- 적용: 코드 배포와 동시(`apply_migrations` 데몬 기동 시 자동, dcy-023 OQ-2 운영자 결정). -- 롤백: 구 코드 UPDATE 절이 컬럼을 참조하므로 공존 불가 — release 문서에 (1) 적용 전 `daily_slot` 백업, (2) 롤백 시 백업 복원 + 구 리비전 절차 명시. -- 검증: 테스트 DSN(`trex_core_test`)에서만 round-trip — 운영 DB 미사용. - -## D3. 수신-무시 시맨틱 (REQ-007 AC-6) - -- messaging handler 의 파싱 제거가 곧 수신-무시다 — RabbitMQ payload 는 관용적 계약이라 외부 발신자(trex-orb-v1, `intent_builder.py:56`)가 키를 계속 보내도 읽히지 않는 unknown key 가 될 뿐. -- 검증 테스트: `allocated_capital` 키를 포함한 buy payload → 정상 admit + 슬롯·intent_log 에 미반영. -- `submit_buy` dedup payload(intent_log)에서 키 제거 — dedup 은 intent_id 기준이라 동작 영향 없음. - -## D4. 인덱스 시프트 주의 (postgresql.py) - -- `_SLOT_COLUMNS` 에서 `allocated_capital`(6번째) 제거 시 `_row_to_slot` 의 이후 모든 row 인덱스가 1씩 당겨진다 — 위치 주석(`# limit_price(5) allocated_capital(6) order_id(7) ...`)을 실제 순서로 재작성하고, 드리프트 테스트(`EXPECTED_SLOT_FIELDS` ↔ information_schema)가 정합을 강제한다. - -## D5. 변경하지 않는 것 - -- `qty` 기반 집행 경로(`qty = int(slot.qty or 0)`) — 불변 (NFR-3). -- BAT-033 의 `--qty` 필수 guard(AC-7) — 보존. -- 외부 전략 모듈 송신 코드 — 범위 밖(운영자 확인). -- baseline `docs/project-structure.md`/`docs/runtime-flows.md` — allocated_capital 직접 기술 없음 확인(dcy-023 confirm 시점) → 갱신 불요, 검증 단계에서 재확인만. diff --git a/docs/batches/bat-034_20260610_drop-allocated-capital/implementation.md b/docs/batches/bat-034_20260610_drop-allocated-capital/implementation.md deleted file mode 100644 index 630863e..0000000 --- a/docs/batches/bat-034_20260610_drop-allocated-capital/implementation.md +++ /dev/null @@ -1,27 +0,0 @@ -# BAT-034 Implementation - -- Status: done -- Batch: BAT-034 - -## 변경 파일 - -| 파일 | 변경 | Milestone / REQ | -|------|------|----------------| -| `trex_core/domain/slot/models.py` | `Slot.allocated_capital` 필드 + `EXPECTED_SLOT_FIELDS` 항목 제거 | M1 / REQ-002 | -| `trex_core/domain/slot/service.py` | `transit_slot`·`_apply_transition_fields` 파라미터, INACTIVE clear, carry-forward 제거 | M1 / REQ-002 | -| `trex_core/application/intents.py` | `BuyIntent.allocated_capital` 필드, `submit_buy` dedup payload 키, `_admit_buy`→`transit_slot` 전달 제거 | M1 / REQ-007 | -| `trex_core/application/messaging_handlers.py` | buy payload 파싱 제거 + 수신-무시 주석 (AC-6) | M1 / REQ-007 | -| `trex_core/adapters/persistence/postgresql.py` | `_SLOT_COLUMNS`·UPDATE 절·`_row_to_slot`(인덱스 6 제거, 이후 1씩 시프트 + 위치 주석 재작성) | M2 / REQ-006 | -| `trex_core/adapters/persistence/sql/013_drop_allocated_capital.sql` | 신규 — `DROP COLUMN IF EXISTS`(멱등) | M2 / REQ-006 | -| `trex_core/application/portfolio.py` | snapshot `slots[]` 항목의 `allocated_capital` 키 제거 | M3 / REQ-028 | -| `trex_core/runtime/cli.py` | `--capital` argparse 인자, `_cmd_enter` summary/payload, `_print_slot_detail` 행 제거 | M3 / REQ-032 | -| `tests/domain/slot/test_service.py` | `allocated_capital` 인자/assert 제거 | M4 | -| `tests/runtime/test_cli_operator.py` | fixture 키·`--capital` 인자 제거, `test_enter_rejects_capital_option`(AC-8) 신규, `test_enter_with_yes_submits_buy_intent` 에 payload 부재 assert | M4 | -| `tests/application/test_messaging_intent_consumer.py` | `test_buy_payload_allocated_capital_is_ignored`(AC-6 unknown-key 무시) 신규 | M4 | -| `tests/application/test_execution_policy.py` | 잔여 lint 정리(F401/F841 — BAT-032 잔재, 본 배치 변경과 무관) | M4 | - -## 설계 정합 - -- design.md D1 제거 지점 8개 전부 처리. D4 인덱스 시프트는 위치 주석 재작성 + 드리프트 테스트로 검증. -- D3 수신-무시: 파싱 제거 = unknown key 무시. 전용 테스트로 고정. -- D5 불변 확인: `qty` 집행 경로·BAT-033 AC-7 guard·외부 모듈·baseline 문서 모두 미변경. diff --git a/docs/batches/bat-034_20260610_drop-allocated-capital/index.md b/docs/batches/bat-034_20260610_drop-allocated-capital/index.md deleted file mode 100644 index 377859c..0000000 --- a/docs/batches/bat-034_20260610_drop-allocated-capital/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# BAT-034: allocated_capital 죽은 필드 제거 — CLI·intent·도메인·영속·snapshot 정리 (dcy-023) - -- Status: released -- Profile: standard -- Source Discovery: dcy-023 (issue #22) -- Included REQ: REQ-002 (CHG-20260610-02), REQ-006 (CHG-20260610-02), REQ-007 (CHG-20260610-02), REQ-028 (CHG-20260610-01), REQ-032 (CHG-20260610-07) - -## Summary -- dcy-023: 주문 사이징·회계·전략 결정 어디에도 쓰이지 않는 죽은 필드 `allocated_capital` 을 설정(CLI `--capital`)→운반(buy intent)→영속(daily_slot 컬럼)→표시(snapshot `slots[]`·CLI detail) 전 경로에서 제거한다. dcy-021 사이징 계약(전략이 qty 계산, core 미관여) 확정의 귀결로, capital 기반 사이징 오해를 원천 제거한다. - -## Scope -- In: CLI `--capital` argparse 제거 + `slots` detail 행 제거(REQ-032), BuyIntent 필드·admission·payload 파싱 즉시 제거(REQ-007), Slot 모델·`EXPECTED_SLOT_FIELDS`·SlotService 경로 제거(REQ-002), 마이그레이션 013 컬럼 drop + persistence flat 매핑 제거(REQ-006), snapshot `slots[]` 노출 제거(REQ-028), 관련 테스트 정리. -- Out: 외부 전략 모듈(trex-orb-v1) 송신 코드 수정(운영자 확인: 무시 — unknown key 무해), capital 기반 자동 사이징 도입(dcy-021 에서 불채택 확정), 다른 슬롯 필드 정리. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 마이그레이션 013 은 비가역 drop — BAT-030 의 011 선례 준용(코드 배포와 동시 적용, release 문서에 사전 백업·롤백 절차 명시, dcy-023 OQ-2). -- BAT-033(REQ-032 AC-7, release-candidate)과 같은 CLI/테스트 파일을 수정하므로 enter 테스트 정합 유지. -- 본 배치 완료 후 REQ-002/006/007/028/032 confirm-req-implemented. diff --git a/docs/batches/bat-034_20260610_drop-allocated-capital/planning.md b/docs/batches/bat-034_20260610_drop-allocated-capital/planning.md deleted file mode 100644 index 399717f..0000000 --- a/docs/batches/bat-034_20260610_drop-allocated-capital/planning.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-034 Planning - -- Status: done -- Batch: BAT-034 - -## Milestones - -| ID | 목표 | 연결 REQ | -|---|---|---| -| M1 | 도메인·intent 계약 제거 — Slot 모델/EXPECTED_SLOT_FIELDS/SlotService + BuyIntent/admission/payload 파싱 | REQ-002 (CHG-02), REQ-007 (CHG-02) | -| M2 | 영속 제거 — 마이그레이션 013(`DROP COLUMN IF EXISTS`) + postgresql.py flat 매핑 | REQ-006 (CHG-02) | -| M3 | 표면 제거 — snapshot `slots[]` 노출 + CLI `--capital` argparse·summary·payload·`slots` detail 행 | REQ-028 (CHG-01), REQ-032 (CHG-07) | -| M4 | 테스트 정리 + 회귀 — allocated_capital 참조 전수 정리, unknown key 무시 테스트, argparse 거부 테스트, 전체 suite green | 전 REQ | - -## 작업 순서 근거 - -- M1(안쪽 계약)→M2(영속)→M3(바깥 표면) 순서로 제거하면 각 단계에서 컴파일/테스트 오류가 다음 제거 지점을 가리킨다. M4 는 전 단계 누락 검출. -- 모든 변경이 "제거"라 단계 간 의존이 단방향(도메인 필드가 사라지면 매핑·노출이 따라 사라져야 함). - -## Design Gate - -- design 필요: **yes** — 5개 REQ·7개 모듈에 걸친 데이터 계약 변경 + 비가역 마이그레이션. 제거 지점 전수 목록과 마이그레이션/롤백 절차를 design.md 에 고정한다. - -## 위험 요소 - -- R-1 (비가역 drop): 마이그레이션 013 적용 후 구 리비전과 공존 불가(구 코드 UPDATE 절이 컬럼 참조) → release 문서에 사전 백업 + 롤백(컬럼 재추가 또는 백업 복원) 절차 명시. 검증은 테스트 DSN 에서만. -- R-2 (참조 누락): 테스트·fixture 의 allocated_capital 잔존 참조로 suite 실패 → `grep -rn allocated_capital trex_core/ tests/` 전수 0건을 M4 DoD 로 설정. -- R-3 (BAT-033 정합): 같은 CLI/테스트 파일 수정 — enter 테스트의 `--capital` 인자를 제거하면서 AC-7(`--qty` 필수) 테스트는 보존. - -## 검증 계획 (요약) - -- 단위: argparse 거부, payload allocated_capital 부재, unknown key 무시(admit 정상 + 미반영), 필드셋 계약, slots[] 스키마 -- 통합(테스트 DSN): 마이그레이션 013 멱등 적용 + information_schema 대조 + round-trip -- 회귀: 전체 suite green + `grep` 전수 0건 diff --git a/docs/batches/bat-034_20260610_drop-allocated-capital/verification.md b/docs/batches/bat-034_20260610_drop-allocated-capital/verification.md deleted file mode 100644 index c523e53..0000000 --- a/docs/batches/bat-034_20260610_drop-allocated-capital/verification.md +++ /dev/null @@ -1,26 +0,0 @@ -# BAT-034 Verification - -- Status: approved -- Batch: BAT-034 - -## Acceptance Mapping - -| REQ / AC | 검증 | 테스트/Evidence | 결과 | -|----------|------|----------------|------| -| REQ-002 AC-5 | Slot 모델·EXPECTED_SLOT_FIELDS·SlotService 경로에서 allocated_capital 부재 | `tests/domain/slot/test_contract.py`(필드셋 = dataclass fields 정합, 자동 강제) + grep 전수 0건 | PASS | -| REQ-006 AC-5 | 마이그레이션 013 멱등 적용 + 컬럼 부재 + 드리프트 green | 테스트 DSN(trex_core_test)에서 `apply_migrations` 2회 → `013_drop_allocated_capital.sql` 적용·재적용 무해, information_schema 에 컬럼 없음 확인. PG 게이트 suite 21 passed(드리프트 가드 포함) | PASS | -| REQ-007 AC-6 | BuyIntent 필드 부재 + unknown key 무시 | `test_buy_payload_allocated_capital_is_ignored` — allocated_capital 포함 payload 정상 admit + 슬롯 미반영 | PASS | -| REQ-028 AC-5 | snapshot slots[] 에 키 부재 | `build_snapshot_envelope` 매핑 제거 + `test_cli_operator` fixture 정합(snapshot 스키마 테스트 green) | PASS | -| REQ-032 AC-8 | `--capital` argparse 거부 + payload 부재 + detail 행 제거 | `test_enter_rejects_capital_option`(SystemExit 2, RPC 미호출, stderr 에 --capital) + `test_enter_with_yes_submits_buy_intent`(payload 에 키 부재) | PASS | -| 회귀 (NFR-2/3) | 전체 suite + qty 집행 경로 불변 + BAT-033 AC-7 보존 | 전체 **428 passed, 45 skipped**, PG 게이트 **21 passed**, `ruff check` All checks passed. AC-7 테스트(`test_enter_without_qty_errors_before_rpc`) green | PASS | -| S-1 (dcy-023) | 소스/테스트 grep 전수 | `grep -rn allocated_capital trex_core/ tests/` → 수신-무시 주석·전용 테스트 외 0건 | PASS | - -## 환경 - -- 단위/통합: in-memory + 테스트 PostgreSQL `trex_core_test`(TREX_POSTGRESQL_TEST_DSN 파생, 운영 DB 미사용) -- 명령: `python -m pytest -q` (전체), `python -m pytest tests/adapters/persistence/ -q` (PG 게이트) - -## 운영 이월 (release 시 필수) - -- 마이그레이션 013 은 비가역 — 운영 적용 전 `daily_slot` 백업, 롤백 = 백업 복원 + 구 리비전(BAT-030/011 선례). release 문서 Rollout/Rollback 에 명시할 것. -- 운영자 스크립트/습관의 `enter --capital` 사용분은 배포 후 argparse 오류로 즉시 표면화(silent 실패 아님). diff --git a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/design.md b/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/design.md deleted file mode 100644 index 6f58905..0000000 --- a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/design.md +++ /dev/null @@ -1,55 +0,0 @@ -# BAT-035 Design - -- Status: confirmed -- Batch: BAT-035 - -## Architecture Summary -- `trex.snapshot.req` RPC 파이프라인(큐 선언·핸들러·View enum·assets[]/available_slots/watchdog[] 구조)을 제거하고 `trex.slots.req` 단일 RPC로 즉시 교체한다. -- 슬롯 행(slot_to_dict)이 자산·워치독·overlay를 흡수해 소비자 측 배열 조인을 없앤다. -- 변경은 topology → messaging_handlers → portfolio → CLI 레이어 순으로 안쪽부터 바깥으로 진행한다. - -## Changed Areas -- Docs: docs/interface-contract.md (snapshot→slots 계약 교체 + 필드별 정본 출처 표), docs/runtime-flows.md (read 흐름 교체) -- Modules: - - `trex_core/adapters/messaging/topology.py` — SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE, snapshot_queue() → slots_queue() - - `trex_core/application/messaging_handlers.py` — make_slots_rpc_handler 신설, make_snapshot_rpc_handler 제거, _attach_watchdog 제거 - - `trex_core/application/portfolio.py` — slot_to_dict 확장, View enum 제거, build_snapshot_envelope 제거 - - CLI 모듈 — slots.req 사용, spy=true, filter 매핑 -- Tests: - - tests/adapters/messaging/ — topology 단언 - - tests/application/ — slots 핸들러·slot_to_dict·divergence·overlay - -## Key Decisions - -1. **즉시 교체(lockstep)**: snapshot.req 큐·핸들러·legacy stub 경로를 같은 커밋에서 제거한다. 전략 모듈(trex-orb-v1)은 배포 전 lockstep 대응 완료 상태여야 한다(REQ-073, dcy-025 OQ-1). - -2. **slot_to_dict 서명 확장**: `slot_to_dict(s, *, snapshot_store=None, watchdog_store=None, pause_store=None) → dict`. snapshot_store 없으면 asset_quantity=None, cash_balance=None, cash_estimated=False; watchdog_store 없으면 watchdog=None; pause_store 없으면 paused=False. None 필드는 응답에 그대로 포함(소비자가 구분 가능). - -3. **warned 계산**: `warned = (slot.qty != asset_quantity)`. asset_quantity가 None(snapshot 미연결)이면 warned=False(불확정, 조용히 처리). slot.qty는 슬롯 미러 값. - -4. **watchdog 행 흡수**: `watchdog: {expired: bool, last_seen: str|null}`. watchdog_store.get(slot_id) → WatchdogEntry(last_seen_at, timeout_sec). expired = (now - last_seen_at) > timeout_sec (또는 last_seen_at is None). WatchdogStore None 이면 watchdog 필드 전체 None. - -5. **filter 분기**: `filter` → `{all: 전체, inactive: status==inactive, pending: status in (pending_buy, pending_sell), holding: status==holding, error: status==error}`. 미지정/null/"all" = 전체. 미지정 값은 all로 처리(조용히). - -6. **spy 분기**: `spy=true` → holding 슬롯 워치독 미터치. `spy=false` or 미지정 → holding 슬롯 watchdog.update(). REQ-034 CHG-01 시맨틱. - -7. **View enum 제거**: `portfolio.py:65`의 `class View(str, Enum)` 전체 제거. 호출 지점(build_snapshot_envelope, CLI) 정리. - -8. **build_snapshot_envelope 제거**: `portfolio.py:340`의 `build_snapshot_envelope` 함수 전체 제거. messaging_handlers.py 의 snapshot 경로(221~244) 같이 제거. - -9. **REQ-028 Deprecated 전환**: 이번 배치에서 snapshot.req 코드가 제거되면 REQ-028 상태를 Implemented→Deprecated로 전환한다. - -## Edge Cases -- asset_quantity(hub) = 0 이고 qty(slot) = 0 → warned=False (정상 일치) -- asset_quantity(hub) = 0 이고 qty(slot) = 1 → warned=True (divergence) -- snapshot_store 미연결(degraded) → asset_quantity=None, cash_balance=None, cash_estimated=False, degraded=true -- spy=true 요청인데 holding 슬롯 없음 → watchdog 터치 없음, 정상 반환 -- invalid filter 값("buyable" 등) → all 처리 (하위호환, CLI 매핑은 상위 계층에서 처리) - -## Architecture Impact -- runtime-flows: yes (snapshot → slots 흐름 교체) -- project-structure: no (파일 추가 없음, 기존 파일 수정) - -## Reference Doc Update Plan -- docs/interface-contract.md: snapshot 섹션 제거 → slots.req 계약 + 필드별 정본 출처 표 추가 (REQ-075 AC-1) -- docs/runtime-flows.md: read 흐름 snapshot→slots 교체 (REQ-075 AC-2) diff --git a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/implementation.md b/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/implementation.md deleted file mode 100644 index 2c441bd..0000000 --- a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/implementation.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-035 Implementation - -- Status: draft -- Batch: BAT-035 - -## Plan Summary -- topology.py: SLOTS_REQ_QUEUE 신설, SNAPSHOT_REQ_QUEUE 제거, snapshot_queue→slots_queue -- messaging_handlers.py: make_slots_rpc_handler 신설, make_snapshot_rpc_handler + _attach_watchdog 제거 -- portfolio.py: View enum 제거, build_snapshot_envelope 제거, slot_to_dict 확장 -- CLI: slots.req 사용 + spy=true + filter 매핑 -- docs: interface-contract.md, runtime-flows.md 교체 -- tests: topology + slots 핸들러 + slot_to_dict - -## Changed Files -- `trex_core/adapters/messaging/topology.py` -- `trex_core/application/messaging_handlers.py` -- `trex_core/application/portfolio.py` -- CLI 모듈 (slots.req 호출 경로) -- `docs/interface-contract.md` -- `docs/runtime-flows.md` -- `tests/adapters/messaging/test_topology.py` (또는 해당 파일) -- `tests/application/test_messaging_handlers.py` (또는 해당 파일) -- `tests/application/test_portfolio.py` (또는 해당 파일) -- `docs/srs/Interface/req-028_snapshot-read-message-contract-and-envelope.md` (Deprecated 전환) -- `docs/srs/index.md` (REQ-028 Deprecated) - -## Execution Log - -(구현 진행 시 기록) - -## Validation -(구현 후 기록) - -## Remaining Risks -- trex-orb-v1 lockstep 대응 확인 전 배포 불가 diff --git a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/index.md b/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/index.md deleted file mode 100644 index 467c116..0000000 --- a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# BAT-035: 전략 read 표면 재설계 — trex.slots.req + 슬롯 단일 행 스키마 (dcy-025) - -- Status: released -- Profile: standard -- Source Discovery: dcy-025 -- Included REQ: REQ-073, REQ-074, REQ-075, REQ-021 (CHG-20260611-01), REQ-034 (CHG-20260611-01), REQ-063 (CHG-20260611-01) -- Excluded REQ: 없음 - -## Summary -- `trex.snapshot.req` 파이프라인(view/assets[]/available_slots/watchdog[] 구조)을 `trex.slots.req` 단일 RPC로 즉시 교체(과도기 없음)한다. 슬롯 행 하나가 슬롯 상태·자산(hub 정본 + 슬롯 미러)·워치독·overlay 플래그를 모두 운반해 소비자 측 배열 조인을 없앤다. 필터는 status 단일 값, spy=true 시 워치독 무터치. strategy 계열 필드는 전면 제외. - -## Scope -- In: topology.py 큐명 교체(SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE) + snapshot.req 선언 제거, messaging_handlers.py slots 핸들러 신설 + snapshot 핸들러 제거 + `_attach_watchdog` top-level 제거, portfolio.py `View` enum 폐기 + `slot_to_dict` 확장(asset_quantity/qty/warned/cash_balance/cash_estimated/watchdog nested/paused) + `build_snapshot_envelope` 제거, CLI slots.req 사용 + spy=true + --status→filter 매핑, docs(interface-contract.md / runtime-flows.md) snapshot→slots 계약 교체, 테스트(topology/slots 핸들러/filter/spy/응답 키 집합) -- Out: strategy 계열 필드 slots 행 도입(후속 discovery), trex-orb-v1 소비 측 코드 수정(운영자 별도 lockstep 확인), 라이브 브로커 잔고 행 포함, overlay 서버측 필터링 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 즉시 교체(과도기 없음): 배포 시점에 trex-orb-v1 의 `trex.slots.req` 대응 준비 완료 여부를 사전 확인해야 한다(dcy-025 R-1, REQ-073 Notes). -- REQ-028(snapshot 계약 REQ): keep-current(Implemented) 유지, 이번 배치에서 코드 제거 완료 시 Deprecated 전환. -- REQ-062(hub 정본 + 슬롯 미러 + 브로커 ground truth 정합)는 이번 배치 영향 없이 유지 — slot_to_dict 가 그 계약을 행에 표현한다. diff --git a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/planning.md b/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/planning.md deleted file mode 100644 index 7363221..0000000 --- a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/planning.md +++ /dev/null @@ -1,61 +0,0 @@ -# BAT-035 Planning - -- Status: done -- Batch: BAT-035 -- Profile: standard - -## Included REQ -- REQ-073: trex.slots.req RPC 계약 (Interface/High) — 큐 교체, filter/spy, 응답 slots+health only -- REQ-074: 슬롯 단일 행 스키마 (Data/High) — asset_quantity+qty+warned, cash_balance+cash_estimated, watchdog nested, paused -- REQ-075: baseline 문서 정합 (Documentation/Medium) — interface-contract/runtime-flows -- REQ-021 CHG-20260611-01: 토폴로지 큐명 snapshot→slots 교체 -- REQ-034 CHG-20260611-01: watchdog 트리거=slots.req 기본/spy=무터치, 행 흡수 -- REQ-063 CHG-20260611-01: CLI slots.req+spy=true, --status→filter 매핑 - -## Delivery Plan - -### M1: 토폴로지 + 인프라 계약 교체 -- `trex_core/adapters/messaging/topology.py`: SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE (`"trex.slots.req"`), `snapshot_queue()` → `slots_queue()` -- `docs/interface-contract.md`: trex.snapshot.req 섹션 제거, trex.slots.req 계약(요청 {filter, spy}, 응답 {slots[], degraded/reason/message}, 필드별 정본 출처 표) 추가 -- `docs/runtime-flows.md`: read 흐름 snapshot→slots 교체 - -### M2: messaging_handlers — slots 핸들러 신설 + snapshot 핸들러 제거 -- `make_snapshot_rpc_handler` 전체 제거 -- `make_slots_rpc_handler` 신설: filter(str) / spy(bool) 파싱, filter 분기 슬롯 조회, spy=false → holding watchdog 터치, 응답 {slots, degraded, reason, message} only -- `_attach_watchdog` top-level 함수 제거 (watchdog 처리가 핸들러 내부로 흡수) - -### M3: portfolio — slot_to_dict 확장 + View 폐기 + snapshot envelope 제거 -- `portfolio.py`: `View` enum 제거, `build_snapshot_envelope` 제거 -- `slot_to_dict(s, snapshot_store=None, watchdog_store=None, pause_store=None)` 확장: - - 슬롯/코어 정본 필드(기존 유지) - - `asset_quantity` (snapshot_store → hub 정본) + `qty` (슬롯 미러 s.qty) + `warned` (qty≠asset_quantity) - - `cash_balance` + `cash_estimated` (snapshot_store) - - `watchdog: {expired: bool, last_seen: str|null}` nested (watchdog_store) - - `paused: bool` (pause_store) - -### M4: CLI — slots.req 사용 + spy=true + filter 매핑 -- `trex_core/interfaces/cli.py` (또는 해당 모듈): 슬롯 조회 시 slots.req + spy=true, `--status` 인자→filter 매핑 (all/inactive/pending/holding/error, 기존 "buyable" → "inactive") - -### M5: 테스트 + 회귀 -- tests/adapters/messaging/: topology 단언 (SLOTS_REQ_QUEUE 존재, SNAPSHOT_REQ_QUEUE 부재) -- tests/application/: slots 핸들러 filter·spy·응답 키 집합, watchdog 터치/무터치, degraded 신호, portfolio slot_to_dict 확장 필드 + divergence + warned + cash_estimated + paused holding 직교, strategy 필드 부재 -- 전체 suite green - -## Dependencies -- Internal: REQ-062(hub 정본 + 슬롯 미러 정합 — 이번 배치가 그 계약을 표면에 구현), PauseStore, WatchdogStore 기존 API 재사용 -- External: trex-orb-v1 lockstep 대응(배포 전 확인) - -## Design Gate -- Design Required Before Implementation: **yes** -- Reason: topology.py 큐 선언 변경(REQ-021), messaging_handlers.py 핸들러 신설/제거(REQ-073), portfolio.py slot_to_dict 서명 변경 + View 폐기(REQ-074)가 상호 연결된 인터페이스 변경. 제거 지점 전수와 새 핸들러 설계 계약을 design.md에 먼저 고정해 구현 drift를 방지. - -## 위험 요소 -- R-1 (lockstep): trex-orb-v1 이 snapshot.req 를 아직 쓰고 있으면 배포 즉시 read 실패 → 배포 전 trex-orb-v1 의 slots.req 대응 완료 여부 확인 필수 -- R-2 (slot_to_dict 서명 변경): 호출 지점이 다수(messaging_handlers + CLI + 테스트) → grep 으로 호출 지점 전수 확인 후 한 번에 교체 -- R-3 (build_snapshot_envelope 제거): snapshot_store wiring path 삭제 시 CLI / daemon wiring 코드에서 잔존 참조 compile 오류 → M5 회귀로 검출 - -## 검증 계획 (요약) -- topology: SLOTS_REQ_QUEUE 존재, SNAPSHOT_REQ_QUEUE 부재 assert -- slots 핸들러: filter 분기(all/inactive/pending/holding/error), spy 터치/무터치, 응답 키 집합 {slots/degraded/reason/message} only, strategy 필드 부재 -- slot_to_dict: asset_quantity(hub) + qty(mirror) + warned(divergence) + cash_balance + cash_estimated + watchdog:{expired,last_seen} + paused 포함 -- 회귀: 전체 suite green diff --git a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/verification.md b/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/verification.md deleted file mode 100644 index 9f0f4b7..0000000 --- a/docs/batches/bat-035_20260611_strategy-read-surface-slots-req/verification.md +++ /dev/null @@ -1,72 +0,0 @@ -# BAT-035 Verification - -- Status: confirmed -- Batch: BAT-035 - -## Acceptance Mapping - -- REQ-073 AC-1: topology 선언 — `trex.slots.req` 큐 존재, `trex.snapshot.req` 큐·핸들러 부재 -- REQ-073 AC-2: filter 분기 — inactive/pending(buy∪sell)/holding/error/all/invalid 처리 -- REQ-073 AC-3: 응답 키 집합 — {slots/degraded/reason/message} only, assets/available_slots/watchdog(top) 키 부재 -- REQ-073 AC-4: spy=true → last_seen_at 미갱신; spy=false(default) → holding watchdog 갱신 -- REQ-073 AC-5: degraded=true + reason 전파, credentials sanitized -- REQ-074 AC-1: 행 필드 — slot_id/status/avg_price/entry_price/forced_close_at/symbol/portfolio_id/cash_balance/asset_quantity/qty/watchdog:{expired,last_seen}/paused/warned 포함 -- REQ-074 AC-2: qty≠asset_quantity → warned=true, 두 값 모두 노출 -- REQ-074 AC-3: cash_estimated=true 구간 + 수렴 시 false -- REQ-074 AC-4: paused holding → filter=holding 반환, paused=true (직교) -- REQ-074 AC-5: strategy 계열 키 부재 (strategy_id/sell_strategy_id/strategy_params/active_strategy_id) -- REQ-075 AC-1: interface-contract.md 에 slots.req 계약 + 필드 소스 테이블 추가, snapshot.req 현행 계약 섹션 제거 -- REQ-075 AC-2: runtime-flows.md read 흐름 slots.req 기준으로 업데이트 -- REQ-075 AC-3: `trex.snapshot.req` 현행 계약 서술 제거됨 (change log 이력 제외) -- REQ-021 CHG-01 AC: 토폴로지에 slots.req 선언, snapshot.req 없음 (SNAPSHOT_REQ_QUEUE 상수 제거) -- REQ-034 CHG-01 AC: spy=true → watchdog 미갱신; spy=false → holding watchdog 갱신 -- REQ-063 CHG-01 AC: CLI spy=true 전송, --status 인자 server-side filter 매핑 (buyable→inactive) - -## Evidence - -### Tests (420 passed, 45 skipped — 2026-06-11) - -- **REQ-073 AC-1**: `tests/adapters/messaging/test_topology.py::test_topology_constants` - - `topology.SLOTS_REQ_QUEUE == "trex.slots.req"` ✓ - - `not hasattr(topology, "SNAPSHOT_REQ_QUEUE")` ✓ -- **REQ-073 AC-2**: `tests/application/test_slots_handler.py` - - `test_filter_all_returns_all_slots` ✓ - - `test_filter_inactive_returns_inactive_slots` ✓ - - `test_filter_holding_returns_holding_slots` ✓ - - `test_filter_pending_returns_pending_buy_and_sell` ✓ - - `test_invalid_filter_treated_as_all` ✓ -- **REQ-073 AC-3**: `test_response_keys_only_slots_health` ✓ -- **REQ-073 AC-4**: `test_spy_true_no_watchdog_update`, `test_spy_false_updates_watchdog` ✓ -- **REQ-073 AC-5**: `test_degraded_signal_propagated` ✓ -- **REQ-074 AC-1**: `test_slot_row_contains_merged_fields` ✓ -- **REQ-074 AC-2**: `test_warned_true_when_qty_diverges` ✓ -- **REQ-074 AC-3**: `tests/application/test_ledger_option_d.py::test_apply_cash_estimate_overlays_pending_delta` ✓ -- **REQ-074 AC-4**: `test_paused_holding_orthogonal_to_filter` ✓ -- **REQ-074 AC-5**: `test_strategy_fields_absent` ✓ -- **REQ-021 CHG-01**: `test_topology_constants` + `tests/adapters/messaging/test_instance_topology.py::test_named_instance_suffix` ✓ -- **REQ-034 CHG-01**: spy 워치독 억제 tests (위) ✓ -- **REQ-063 CHG-01**: `tests/runtime/test_cli_operator.py` - - `test_universe_uses_slots_queue_with_spy` ✓ - - `test_slots_status_sends_server_side_filter` ✓ - - `test_slots_buyable_maps_to_inactive_filter` ✓ - -### Manual - -- `tests/application/test_killswitch_masking_resume.py`: View/build_snapshot_envelope 의존 제거 후 재작성 완료 -- `tests/application/test_ledger_option_d.py`: _apply_cash_estimate 직접 테스트 → slots handler 경유 재작성 완료 - -### Code Review - -- `trex_core/adapters/messaging/topology.py`: SNAPSHOT_REQ_QUEUE 제거, SLOTS_REQ_QUEUE 추가 ✓ -- `trex_core/application/portfolio.py`: View, build_snapshot_envelope, _asset_to_dict, SnapshotEnvelope 제거 ✓ -- `trex_core/application/messaging_handlers.py`: make_snapshot_rpc_handler 제거, make_slots_rpc_handler 추가 ✓ -- `trex_core/runtime/daemon.py`: _snapshot_rpc_loop → _slots_rpc_loop, make_slots_rpc_handler 연결 ✓ -- `trex_core/runtime/cli.py`: slots_queue + spy=True + server-side filter ✓ -- `docs/interface-contract.md`: slots.req 계약 추가, snapshot.req envelope 섹션 제거 ✓ -- `docs/runtime-flows.md`: 슬롯 RPC flow 추가, snapshot RPC flow 교체 ✓ -- `docs/srs/index.md`: REQ-028 → Deprecated ✓ - -## Result - -- Ready for Release: true -- Blocking Issues: 없음 diff --git a/docs/batches/bat-036_20260611_safety-sell-disable-mode/design.md b/docs/batches/bat-036_20260611_safety-sell-disable-mode/design.md deleted file mode 100644 index 3a35dae..0000000 --- a/docs/batches/bat-036_20260611_safety-sell-disable-mode/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-036 Design - -- Status: confirmed -- Batch: BAT-036 - -## Architecture Summary -- `DaemonInstanceConfig`에 `safety_sell_enabled: bool = True` 필드를 추가하고 per-instance TOML 파싱 경로(`from_toml`)와 `DaemonConfig.from_config()` 전파 경로를 업데이트한다. -- `Daemon.start()` 내부 workers 리스트 구성 시 `safety_sell_enabled` 값을 체크해 False이면 `("trex-safety-sell", self._safety_sell_loop)` 항목을 포함하지 않는다. - -## Changed Areas -- Docs: 없음 (docs 변경 없음) -- Modules: - - `trex_core/runtime/config.py` — DaemonInstanceConfig 필드 + from_toml + from_config 전파 - - `trex_core/runtime/daemon.py` — start() workers 분기 -- Tests: - - config 파싱 단위 테스트 - - daemon workers 구성 테스트 - -## Key Decisions - -1. **absent=true 기본값**: `from_toml()` 내 `data.get("safety_sell_enabled", True)`. TOML 키 부재 시 True → 기존 배포 동작 보존. - -2. **DaemonConfig 전파 경로**: `DaemonConfig.from_config()` → `DaemonInstanceConfig.from_toml()` → `DaemonInstanceConfig.safety_sell_enabled` → `DaemonConfig` 생성자 또는 속성으로 전달. `Daemon.__init__` 에서 `self._config.safety_sell_enabled` 로 참조. - -3. **workers 조건 분기 위치**: `daemon.py:328` workers 리스트 구성 블록 내, `("trex-safety-sell", self._safety_sell_loop)` 항목을 조건부로 append. 리스트 구성 후 순회 루프(`for name, target in workers:`)는 변경 없음. - -4. **로그 메시지**: `logger.warning("safety_sell disabled — trex-safety-sell worker not started. Autonomous safety-sell will NOT run.")`. RUNNING 로그(기존)로 전체 워커 시작 상태를 가시화. - -5. **AC-6 보존**: `_safety_sell_loop` 제거가 아닌 workers 리스트 미포함. `Daemon._safety_sell_loop` 메서드 자체는 삭제하지 않는다(CLI exit/cancel/kill-switch 독립 경로는 이 메서드를 직접 호출하지 않으므로 무관). - -## Edge Cases -- safety_sell_enabled=True (기본): workers에 포함, 기존 동작과 동일 -- safety_sell_enabled=False: workers에 미포함, 경고 로그, 나머지 워커 영향 없음 -- TOML 키 오타(예: "safety_sell_enable"): get() 기본값 True 처리 → 활성화(안전한 방향) - -## Architecture Impact -- runtime-flows: no (daemon 기동 흐름만 경미하게 변경, 문서 갱신 불필요) -- project-structure: no - -## Reference Doc Update Plan -- docs/interface-contract.md: 없음 -- docs/runtime-flows.md: 없음 diff --git a/docs/batches/bat-036_20260611_safety-sell-disable-mode/implementation.md b/docs/batches/bat-036_20260611_safety-sell-disable-mode/implementation.md deleted file mode 100644 index 17bef9e..0000000 --- a/docs/batches/bat-036_20260611_safety-sell-disable-mode/implementation.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-036 Implementation - -- Status: draft -- Batch: BAT-036 - -## Plan Summary -- config.py: DaemonInstanceConfig.safety_sell_enabled 필드 + from_toml 파싱 + from_config 전파 -- daemon.py: start() workers 조건 분기 - -## Changed Files -- `trex_core/runtime/config.py` -- `trex_core/runtime/daemon.py` -- `tests/` (config 파싱 + daemon workers 테스트) - -## Execution Log - -(구현 진행 시 기록) - -## Validation -(구현 후 기록) - -## Remaining Risks -- 없음 diff --git a/docs/batches/bat-036_20260611_safety-sell-disable-mode/index.md b/docs/batches/bat-036_20260611_safety-sell-disable-mode/index.md deleted file mode 100644 index 4a6807f..0000000 --- a/docs/batches/bat-036_20260611_safety-sell-disable-mode/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# BAT-036: safety_sell_enabled TOML 설정 + 조건부 데몬 워커 기동 (dcy-024) - -- Status: released -- Profile: standard -- Source Discovery: dcy-024 -- Included REQ: REQ-048 (CHG-20260611-01), REQ-005 (CHG-20260611-01) -- Excluded REQ: 없음 - -## Summary -- per-instance TOML 에 `safety_sell_enabled: bool = true` 필드를 추가하고, `false` 시 daemon.start() 가 `trex-safety-sell` 워커를 workers 리스트에서 제외해 스레드를 기동하지 않는다. 기동 로그로 상태를 가시화하며, 나머지 실행 경로(ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent)는 이 값과 무관하게 동작한다. - -## Scope -- In: `trex_core/runtime/config.py` — DaemonInstanceConfig에 `safety_sell_enabled: bool = True` 필드 추가 + from_toml/from_config 파싱 + DaemonConfig로 전파, `trex_core/runtime/daemon.py` — start() workers 리스트 조건 분기 (safety_sell_enabled=False → trex-safety-sell 제외 + RUNNING 로그 + 경고 로그), 관련 테스트 -- Out: env 변수 TREX_SAFETY_SELL_ENABLED, CLI 노출, slots/snapshot 응답 노출, 다른 워커 제어 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- absent=true 기본값: TOML에 키 없으면 True(기존 동작 보존). -- 선례: max_active_slots, default_order_timeout_sec 와 동일한 per-instance TOML 경로. -- REQ-005 AC-6: ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent 는 safety_sell_enabled 값과 무관하게 동작 — 이번 배치 구현에서 그 경로를 건드리지 않는 것이 검증 기준. diff --git a/docs/batches/bat-036_20260611_safety-sell-disable-mode/planning.md b/docs/batches/bat-036_20260611_safety-sell-disable-mode/planning.md deleted file mode 100644 index 79ce55c..0000000 --- a/docs/batches/bat-036_20260611_safety-sell-disable-mode/planning.md +++ /dev/null @@ -1,49 +0,0 @@ -# BAT-036 Planning - -- Status: done -- Batch: BAT-036 -- Profile: standard - -## Included REQ -- REQ-048 CHG-20260611-01: DaemonInstanceConfig.safety_sell_enabled: bool=True, from_toml/from_config 파싱, DaemonConfig 전파 (AC-10) -- REQ-005 CHG-20260611-01: daemon.start() 조건부 workers — safety_sell_enabled=False 시 trex-safety-sell 제외 + RUNNING 로그 + 경고 로그 (AC-5/6) - -## Delivery Plan - -### M1: config.py — DaemonInstanceConfig 필드 추가 -- `trex_core/runtime/config.py:DaemonInstanceConfig` 에 `safety_sell_enabled: bool = True` dataclass 필드 추가 -- `from_toml()`: `data.get("safety_sell_enabled", True)` 파싱 -- `DaemonConfig.from_config()`: DaemonConfig 생성 시 inst.safety_sell_enabled 전파 - -### M2: daemon.py — start() 조건부 workers -- `trex_core/runtime/daemon.py:328~337` workers 리스트 구성 부분에서 조건 분기: - ```python - if self._config.safety_sell_enabled: - workers.append(("trex-safety-sell", self._safety_sell_loop)) - else: - logger.warning("safety_sell disabled — trex-safety-sell worker not started") - ``` -- RUNNING 로그(기존 구조 안에서)로 비활성 상태 가시화 - -### M3: 테스트 -- config 파싱: TOML `safety_sell_enabled=false` → False; 키 부재 → True -- daemon start: safety_sell_enabled=False → trex-safety-sell 스레드 미기동 단언 -- AC-6: ExecutionService/exit/cancel/kill-switch 경로는 이번 M1~M2 변경으로 건드리지 않음(회귀 테스트로 확인) - -## Dependencies -- Internal: DaemonConfig 내 DaemonInstanceConfig 전파 경로(from_config → DaemonInstanceConfig.from_toml) -- External: 없음 - -## Design Gate -- Design Required Before Implementation: **yes** -- Reason: DaemonConfig ↔ DaemonInstanceConfig 전파 경로와 daemon.start() workers 리스트 수정 지점을 design.md 에 먼저 확정해 구현 오류를 방지. - -## 위험 요소 -- R-1 (absent=true 누락): from_toml 에서 get 기본값을 False 로 잘못 설정하면 기존 인스턴스 동작 파괴 → 단위 테스트로 absent→True 확인 -- R-2 (DaemonConfig 전파): DaemonConfig 에 safety_sell_enabled 를 전달하지 않으면 daemon.start() 에서 참조 실패 → M1 에서 전파 경로를 먼저 확인 - -## 검증 계획 (요약) -- TOML 파싱: present=false→False, present=true→True, absent→True -- daemon.start() workers: False → trex-safety-sell 제외, True → 포함 -- 다른 워커(trex-execution 등) 영향 없음 -- 전체 suite green diff --git a/docs/batches/bat-036_20260611_safety-sell-disable-mode/verification.md b/docs/batches/bat-036_20260611_safety-sell-disable-mode/verification.md deleted file mode 100644 index 5908259..0000000 --- a/docs/batches/bat-036_20260611_safety-sell-disable-mode/verification.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-036 Verification - -- Status: confirmed -- Batch: BAT-036 - -## Acceptance Mapping - -- REQ-048 CHG-20260611-01 AC-10: Given per-instance TOML safety_sell_enabled(absent=true), When from_toml/from_config, Then 값이 파싱되어 DaemonConfig로 전파된다 - - present=false → DaemonInstanceConfig.safety_sell_enabled=False → DaemonConfig.safety_sell_enabled=False - - present=true → True - - absent → True (기본값) -- REQ-005 CHG-20260611-01 AC-5: Given safety_sell_enabled=False, When daemon.start(), Then trex-safety-sell 스레드 미기동 + WARNING 로그 -- REQ-005 CHG-20260611-01 AC-6: ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent — safety_sell_enabled 값과 무관 동작 (회귀: 해당 경로 테스트 green) - -## Evidence - -### Tests (420 passed, 45 skipped — 2026-06-11) - -- **REQ-048 CHG-20260611-01 AC-10**: `tests/runtime/test_instance_config.py` - - `test_safety_sell_enabled_default_true` ✓ - - `test_safety_sell_enabled_false_from_toml` ✓ - - `test_safety_sell_enabled_true_explicit_from_toml` ✓ -- **REQ-005 CHG-20260611-01 AC-6** (회귀): 전체 테스트 스위트 420 passed — execution/exit/cancel/kill-switch 경로 모두 green ✓ - -### Code Review - -- `trex_core/runtime/config.py`: - - `DaemonInstanceConfig.safety_sell_enabled: bool = True` ✓ - - `from_toml()`: `safety_sell_enabled=bool(data.get("safety_sell_enabled", True))` ✓ - - `DaemonConfig.safety_sell_enabled: bool = True` ✓ - - `from_config()` replace: `safety_sell_enabled=inst.safety_sell_enabled` ✓ -- `trex_core/runtime/daemon.py`: - - `start()` workers 조건부: `if self.config.safety_sell_enabled: workers.append(...)` ✓ - - `else: _log.warning("safety_sell_enabled=false — trex-safety-sell worker not started...")` ✓ - -### Manual - -- `daemon.start()` 스레드 조건부 기동: 단위 테스트에서 실제 스레드 기동 없이 코드 경로 검증 (config 파싱 tests + code review) -- safety_sell 비기동 시 execution/exit/cancel/kill-switch 경로 무관함을 회귀 테스트로 확인 - -## Result - -- Ready for Release: true -- Blocking Issues: 없음 diff --git a/docs/batches/bat-037_20260612_slots-rpc-available-slots/design.md b/docs/batches/bat-037_20260612_slots-rpc-available-slots/design.md deleted file mode 100644 index 746a9fc..0000000 --- a/docs/batches/bat-037_20260612_slots-rpc-available-slots/design.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-037 Design - -- Status: confirmed -- Batch: BAT-037 - -## Architecture Summary -- `make_slots_rpc_handler` 에 `max_active_slots: Optional[int] = None` 파라미터를 추가하고, 응답 생성 시 `available_slots` 를 계산해 포함한다. -- `daemon.py` 의 핸들러 wiring 에 `max_active_slots=self.config.max_active_slots` 를 전달한다. -- 계산: `max_active_slots` 가 None 이면 `null`, 설정 시 `max(0, M − slot_repo.count_active(instance_key))`. -- `SlotService.compute_slot_capacity` 는 SlotService 인스턴스가 필요하고 slots 핸들러에는 repo 만 주입되어 있으므로, `count_active` 를 직접 호출하는 인라인 계산을 사용한다. - -## Changed Areas -- Modules: - - `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler` 시그니처 + 응답 구성 - - `trex_core/runtime/daemon.py` — `_slots_rpc_loop` 핸들러 wiring -- Tests: - - `tests/application/test_slots_handler.py` — AC-3 키 집합, AC-6 값 검증 -- Docs: `docs/interface-contract.md` 는 REQ-075 소관이므로 이번 배치에서 갱신하지 않음 - -## Key Decisions -1. `SlotService` 대신 `slot_repo.count_active` 직접 호출 — slots 핸들러에 SlotService 가 주입되지 않으며, count_active 는 repo 레벨에서 단일 쿼리로 처리 가능. 의존성을 늘리지 않는다. -2. `available_slots = null` when `max_active_slots is None` — "제한 없음"을 명시적으로 표현. 소비 측(orb-v1)은 null 을 "제한 없음"으로 처리. -3. `available_slots = max(0, M − count_active)` — active count 가 M 을 초과하는 경우(수동 DB 조작 등)는 0 으로 클램프. - -## Edge Cases -- `max_active_slots` 미설정(None): `available_slots = null` 반환. -- active > max_active_slots: `available_slots = 0` (클램프). -- `count_active` 쿼리 실패: 예외를 상위로 전파 — 응답 자체가 실패하므로 hint 만 null 로 fallback 하지 않는다(일관성 유지). - -## Architecture Impact -- none (runtime-flows 흐름 변경 없음, 응답 필드 추가만) - -## Reference Doc Update Plan -- project-structure.md: none -- runtime-flows.md: none diff --git a/docs/batches/bat-037_20260612_slots-rpc-available-slots/implementation.md b/docs/batches/bat-037_20260612_slots-rpc-available-slots/implementation.md deleted file mode 100644 index b089984..0000000 --- a/docs/batches/bat-037_20260612_slots-rpc-available-slots/implementation.md +++ /dev/null @@ -1,28 +0,0 @@ -# BAT-037 Implementation - -- Status: completed -- Batch: BAT-037 - -## Plan Summary -- `make_slots_rpc_handler` 에 `max_active_slots: Optional[int] = None` 파라미터 추가, 응답에 `available_slots` 포함 -- `daemon.py` 핸들러 wiring 에 `max_active_slots=self.config.max_active_slots` 전달 -- `test_slots_handler.py` AC-3 반전 + AC-6 테스트 3개 추가 - -## Changed Files -- `trex_core/application/messaging_handlers.py` -- `trex_core/runtime/daemon.py` -- `tests/application/test_slots_handler.py` - -## Execution Log -1. `make_slots_rpc_handler` 시그니처에 `max_active_slots: Optional[int] = None` 추가 -2. 응답 구성 직전 `available_slots` 계산: None → null, 설정 시 `max(0, M − count_active)` -3. `daemon.py` `_slots_rpc_loop` 에 `max_active_slots=self.config.max_active_slots` wiring -4. `test_slots_handler.py` AC-3 키 집합 갱신(`available_slots` 포함), `_wire` 에 `max_active_slots` 파라미터 전달 -5. AC-6 테스트 3개 추가: uncapped(null), remaining(M-N), full(0) - -## Validation -- `pytest tests/application/test_slots_handler.py` — 16 passed (신규 3개 포함) -- `pytest tests/` — 423 passed, 45 skipped, 회귀 없음 - -## Remaining Risks -- 없음 diff --git a/docs/batches/bat-037_20260612_slots-rpc-available-slots/index.md b/docs/batches/bat-037_20260612_slots-rpc-available-slots/index.md deleted file mode 100644 index 8388f0e..0000000 --- a/docs/batches/bat-037_20260612_slots-rpc-available-slots/index.md +++ /dev/null @@ -1,25 +0,0 @@ -# BAT-037: slots RPC 응답에 available_slots capacity hint 추가 (CHG-20260612-01) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-025 (REQ-073 change-req) -- Included REQ: REQ-073 (CHG-20260612-01) -- Excluded REQ: 없음 - -## Summary -- `trex.slots.req` 응답 payload 에 `available_slots: int|null` 필드를 추가한다. orb-v1 이 매 cycle `{filter: "inactive"}` 로 슬롯을 요청할 때 M 캡이 찬 상황에서도 intent 를 쏴야만 `capacity_full` 을 알 수 있는 불필요한 왕복을 제거하기 위한 capacity hint 다. - -## Scope -- In: `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler` 응답에 `available_slots` 추가 (`compute_slot_capacity` 활용), 관련 테스트 갱신 (AC-3 키 집합 반전·AC-6 값 검증) -- Out: admission gate(`capacity_full`) 로직 변경 없음, 슬롯 도메인 모델/DB 스키마 변경 없음, 다른 RPC 표면 변경 없음 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 인터페이스 변경(응답 스키마)이므로 standard profile 선택. 구현 자체는 경량(`compute_slot_capacity` 재활용). -- `available_slots` 는 hint — admission gate 를 대체하지 않으며 orb-v1 의 skip 최적화용. -- REQ-073 AC-3 이 반전됨(available_slots 제외→포함). BAT-035 AC-3 evidence 무효. diff --git a/docs/batches/bat-037_20260612_slots-rpc-available-slots/planning.md b/docs/batches/bat-037_20260612_slots-rpc-available-slots/planning.md deleted file mode 100644 index 376aa78..0000000 --- a/docs/batches/bat-037_20260612_slots-rpc-available-slots/planning.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-037 Planning - -- Status: confirmed -- Batch: BAT-037 -- Profile: standard - -## Included REQ -- REQ-073 (CHG-20260612-01): 응답에 `available_slots: int|null` 추가, AC-3 반전, AC-6 신규 - -## Delivery Plan -1. `messaging_handlers.py` — `make_slots_rpc_handler` 에서 `compute_slot_capacity` 호출 후 `available_slots` 를 응답에 포함 -2. `tests/application/test_slots_handler.py` — AC-3 키 집합 검사 갱신(available_slots 포함), AC-6 값 검증 케이스 추가 - -## Dependencies -- Internal: - - `trex_core/domain/slot/service.py` — `SlotService.compute_slot_capacity(max_active_slots)` 이미 존재, 재활용 - - `make_slots_rpc_handler` 에 `max_active_slots: Optional[int]` 파라미터가 이미 있는지 확인 필요 -- External: 없음 - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 응답 스키마 변경(인터페이스 계약) — AC-3 반전 근거와 `available_slots` 계산 경로를 design 에서 명확히 한 뒤 구현한다. - -## Milestones -1. Design confirmed — 계산 경로 확정 -2. Implementation — 핸들러 수정 + 테스트 갱신 -3. Verification — AC 전체 매핑 확인 - -## Risks -- `make_slots_rpc_handler` 에 `max_active_slots` 가 전달되지 않는 경로가 있으면 `null` fallback 처리 필요 — design 에서 확인. diff --git a/docs/batches/bat-037_20260612_slots-rpc-available-slots/verification.md b/docs/batches/bat-037_20260612_slots-rpc-available-slots/verification.md deleted file mode 100644 index bd3f613..0000000 --- a/docs/batches/bat-037_20260612_slots-rpc-available-slots/verification.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-037 Verification - -- Status: confirmed -- Batch: BAT-037 - -## Acceptance Mapping - -- REQ-073 AC-1 (토폴로지 — `trex.slots.req` 존재, `trex.snapshot.req` 부재): - - 이번 배치 변경 범위 밖. BAT-035 evidence 유효(handler wiring 경로는 유지됨). - - Evidence: `pytest tests/` 423 passed — 회귀 없음. - -- REQ-073 AC-2 (filter 동작 — inactive/pending/holding/all): - - Evidence: `test_filter_*` 5개 PASSED (`test_slots_handler.py`). - -- REQ-073 AC-3 (응답 키 집합 — `available_slots` 포함, `assets`/`watchdog`/strategy 제외): - - Evidence: `test_response_keys_only_slots_health` PASSED. - - `set(resp.payload.keys()) == {"slots", "available_slots", "degraded", "reason", "message"}` - - `"assets" not in resp.payload`, `"watchdog" not in resp.payload` 확인. - -- REQ-073 AC-4 (spy=True → 워치독 미갱신 / spy=False → 갱신): - - Evidence: `test_spy_true_no_watchdog_update`, `test_spy_false_updates_watchdog` PASSED. - -- REQ-073 AC-5 (degraded 신호 전파): - - Evidence: `test_degraded_signal_propagated` PASSED. - -- REQ-073 AC-6 (`available_slots` 값 규칙): - - Evidence: - - `test_available_slots_null_when_uncapped`: `max_active_slots=None` → `available_slots=null` PASSED. - - `test_available_slots_remaining_capacity`: M=2, active=0 → 2; active=1 → 1 PASSED. - - `test_available_slots_zero_when_full`: M=1, active=1 → 0 PASSED. - -## Evidence - -- Test: - - `pytest tests/application/test_slots_handler.py` — 16 passed (신규 AC-6 3개 포함) - - `pytest tests/` — 423 passed, 45 skipped, 회귀 없음 - -- Manual: - - Architecture Impact: none (design.md 확인) — `docs/project-structure.md`, `docs/runtime-flows.md` 동기화 불필요. - - `daemon.py` wiring: `max_active_slots=self.config.max_active_slots` 전달 경로 코드 리뷰 확인. - -## Result -- Ready for Release: true -- Blocking Issues: 없음 diff --git a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/design.md b/docs/batches/bat-038_20260612_sell-transaction-tax-fee/design.md deleted file mode 100644 index 19cc4c5..0000000 --- a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/design.md +++ /dev/null @@ -1,46 +0,0 @@ -# BAT-038 Design - -- Status: confirmed -- Batch: BAT-038 - -## Architecture Summary -- `broker_port.py` 의 fee 단일 정책 지점을 fee+tax 로 확장한다. 기존 `calculate_fee(side, amount)` 옆에 `calculate_tax(side, amount)` 를 추가하고, 수수료율·세율을 env 로딩 상수로 통일한다(`_FEE_BPS_*` 하드코딩 → env 기본값). -- paper/kis 어댑터의 3개 `tax=0.0` 사이트(paper.py:90, kis.py:213·230)를 `calculate_tax(side, amount)` 호출로 교체한다. -- KIS daily-ccld 응답은 fee/tax 필드를 제공하지 않으므로(아래 Key Decision 3) KIS 도 paper 와 동일하게 정책 추정값을 사용한다. 브로커 응답 정본 우선(FR-3)은 향후 확장 가능한 guarded 파싱 훅으로 표현하되 현재 endpoint 에서는 폴백 경로만 활성. -- fee/tax 전달 경로(`execution.py:_record_ledger` → `LedgerTrade`)는 이미 `fs.tax` 를 운반하므로 계약 변경 없음(planning R-3 점검 완료). - -## Changed Areas -- Modules: - - `trex_core/application/broker_port.py`: - - `_FEE_BPS_BUY`/`_FEE_BPS_SELL` → env(`TREX_KIS_FEE_BPS`, 기본 1.5) 로딩 상수로 통일 - - `_TAX_BPS_SELL` 신규 → env(`TREX_KRX_TAX_SELL_BPS`, 기본 20.0) - - `calculate_tax(side, amount) -> float` 신규: 매도 `amount × _TAX_BPS_SELL / 10_000`, 매수 0 - - `FillStatus.tax` 주석 갱신(0 고정 → 매도 정책 계산, dcy-026) - - `trex_core/adapters/broker/paper.py`: line 90 `tax=0.0` → `tax=calculate_tax(o.side, o.filled * o.price)` - - `trex_core/adapters/broker/kis.py`: - - line 213(전량)·230(부분) `tax=0.0` → `calculate_tax(side, ccld_qty * fill_price)` - - `calculate_tax` import 추가 - - (선택) `_parse_broker_cost(row)` guarded 훅 — fee/tax 필드 부재 시 None → 정책 폴백(FR-3 future-proof) -- Tests: - - `tests/application/` 또는 `tests/adapters/broker/`: 매수/매도 fee·tax 단위, issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책, KIS 폴백 경로 -- Docs: `docs/runtime-flows.md` line 254 - -## Key Decisions -1. **`calculate_tax` 신규 함수**(통합 아님): `calculate_fee` 와 대칭으로 별도 함수. side 분기 단순(매수 0/매도 세율), 호출부에서 `fee=calculate_fee(...)`, `tax=calculate_tax(...)` 명시적 페어. 단일 정책 지점 원칙 충족(AC-7). -2. **env 로딩, 기본값 1.5 / 20.0**: 기존 `TREX_*` os.environ 패턴(config.py 사례) 준용. 모듈 로드 시 `float(os.environ.get("TREX_KIS_FEE_BPS", "1.5"))`, `float(os.environ.get("TREX_KRX_TAX_SELL_BPS", "20.0"))`. paper·KIS 가 동일 모듈 상수 공유 → 동일 정책 보장(NFR-2). -3. **KIS 응답 fee/tax 필드 부재 → 항상 정책 폴백** (R-2 해소): 어댑터가 쓰는 `주식일별주문체결조회`(inquire-daily-ccld, `output1`)는 per-fill 수수료/증권거래세 필드를 제공하지 않는다(`tot_ccld_amt` 만 존재, docs/broker/kis/rest/국내주식/주문/계좌/021 확인). 따라서 KIS 도 `calculate_fee`/`calculate_tax` 정책값을 사용한다. FR-3 "브로커 정본 우선" 은 필드가 없어 구조적으로 폴백 경로만 활성 — **오파싱 리스크 자체가 제거됨**. 향후 fee/tax 노출 endpoint 도입 시 guarded 훅 지점에 파싱 추가. -4. **소급 보정 없음**(dcy-026 OQ-3): 과거 행 미변경, 신규 체결부터 정확값. - -## Edge Cases -- `amount <= 0`: `calculate_tax` 도 `calculate_fee` 처럼 0 반환(0/음수 가드). -- 매수 체결: `calculate_tax("buy", amount) == 0` (AC-6 매수 무세). -- 부분 체결(kis.py:230): 체결분 `ccld_qty * fill_price` 기준으로 fee/tax 계산(전량과 동일 공식, 체결분만). -- `fill_price == 0`(avg 부재): amount=0 → fee/tax 0(기존 fee 동작과 일관). -- KIS 응답 fee/tax 필드 부재(현재 항상): 정책 폴백, 로그 불필요(설계상 기본 경로). - -## Architecture Impact -- runtime-flows - -## Reference Doc Update Plan -- project-structure.md: none (모듈 구조 불변, 함수 1개 추가만) -- runtime-flows.md: line 254 `체결 → ledger outbox` flow — "계산된 `fee`(...), `tax=0` 을 운반" → "계산된 `fee`(`calculate_fee`)·`tax`(`calculate_tax`, 매도 정책 세율 0.2%/매수 0) 를 운반" 으로 갱신. fee/tax 단일 정책 지점(env `TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`) 주석 추가. diff --git a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/implementation.md b/docs/batches/bat-038_20260612_sell-transaction-tax-fee/implementation.md deleted file mode 100644 index 6753133..0000000 --- a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/implementation.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-038 Implementation - -- Status: completed -- Batch: BAT-038 - -## Plan Summary -- `broker_port.py` 에 `calculate_tax(side, amount)` 신규 + fee/tax 율을 env 로딩 상수로 통일. paper/kis 의 3개 `tax=0.0` 사이트를 `calculate_tax` 호출로 교체. runtime-flows fee/tax 흐름 갱신. 회귀 테스트 추가. - -## Changed Files -- `trex_core/application/broker_port.py` — `import os`; `_FEE_BPS`(`TREX_KIS_FEE_BPS`, 1.5)·`_TAX_BPS_SELL`(`TREX_KRX_TAX_SELL_BPS`, 20.0) env 상수; `calculate_tax(side, amount)` 신규; `calculate_fee` 단일 rate 정리; `FillStatus.tax` 주석 갱신 -- `trex_core/adapters/broker/paper.py` — `calculate_tax` import; line 90 `tax=0.0` → `tax=calculate_tax(o.side, amount)` -- `trex_core/adapters/broker/kis.py` — `calculate_tax` import; 전량(213)·부분(230) `tax=0.0` → `tax=calculate_tax(side, amount)`; KIS 응답 fee/tax 필드 부재 주석 -- `docs/runtime-flows.md` — `체결 → ledger outbox` flow line 254 fee/tax 정책 + Change Log -- `tests/application/test_fee_tax_policy.py` — 신규 (6 테스트) - -## Execution Log -1. `broker_port.py`: `os` import 추가, `_FEE_BPS`/`_TAX_BPS_SELL` env 로딩 상수, `calculate_tax` 추가(매도 `amount × _TAX_BPS_SELL / 10_000`, 매수/비양수 0), `FillStatus.tax` 주석을 dcy-026 정책으로 갱신 -2. `paper.py`: `calculate_tax` import, fill 시 `amount = o.filled * o.price` 로 fee/tax 동시 계산 -3. `kis.py`: `calculate_tax` import, 전량·부분 체결 두 경로 모두 `amount` 기준 fee/tax 계산. daily-ccld 응답 fee/tax 필드 부재 → 정책 추정값 사용(주석) -4. `runtime-flows.md`: line 254 `tax=0` → `calculate_tax` 정책 계산 + env 변수 + paper·KIS 공유 설명, Change Log 항목 추가 -5. `test_fee_tax_policy.py`: policy 함수 단위(fee buy/sell 동일, tax sell-only, 비양수 0), issue #24 4건 회귀(1,376.9원), paper sell fill tax 정책 운반, paper buy fill tax=0 - -## Validation -- `pytest tests/application/test_fee_tax_policy.py test_execution_ledger.py test_execution_cancel.py` — 19 passed (신규 6 포함, 기존 회귀 없음) -- `pytest tests/` — **429 passed, 45 skipped** (BAT-037 기준 423 → +6 신규, 회귀 0) -- `ruff check trex_core tests` — All checks passed -- 회귀 기준 확인: issue #24 4건 `fee` 합계 177.9 + `tax` 합계 1,199 = **1,376.9원** (테스트로 고정) -- 기존 `test_buy_fill...:61` `t.tax == 0.0` 은 매수 케이스로 정합 유지(AC-6). `test_execution_cancel.py` stub FillStatus(tax=0.0)는 execution 로직 테스트로 무영향. - -## Remaining Risks -- 없음. R-1(세율 오류)은 1,376.9원 회귀로 고정, R-2(KIS 오파싱)는 설계상 제거(daily-ccld fee/tax 필드 부재 → 정책 폴백), R-3(전달 경로)은 점검 완료(불변). -- 라이브 KIS 스모크는 운영 배포 단계(REL) 사항 — paper 정책과 동일 산식이므로 코드 경로 동일. diff --git a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/index.md b/docs/batches/bat-038_20260612_sell-transaction-tax-fee/index.md deleted file mode 100644 index f014347..0000000 --- a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-038: 매도 증권거래세 누락 정정 + fee/tax 단일 정책 지점 (CHG-20260612-01) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-026 (issue #24) -- Included REQ: REQ-018 (CHG-20260612-01) -- Excluded REQ: 없음 - -## Summary -- 매도 체결의 증권거래세가 전 체결에서 `tax=0.0` 으로 하드코딩되어 실현 손익·일일 수익률이 과대 계산되던 운영 버그(issue #24)를 닫는다. `FillStatus.tax` 가 매도 정책 세율(0.2%)로 계산되도록 정정하고, 수수료율·세율을 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 env 설정으로 관리한다. KIS 어댑터는 브로커 응답 fee/tax 가 있으면 정본 우선, 없으면 정책 폴백. - -## Scope -- In Scope: - - `trex_core/application/broker_port.py` — `calculate_tax(side, amount)` 신규 + 세율/수수료율 env 상수(`TREX_KIS_FEE_BPS`=1.5, `TREX_KRX_TAX_SELL_BPS`=20.0). `FillStatus.tax` 의미 정정(주석 갱신). - - `trex_core/adapters/broker/paper.py` — `tax=0.0` → `calculate_tax(...)` (line 90) - - `trex_core/adapters/broker/kis.py` — `tax=0.0` → 브로커 응답 fee/tax 파싱 + 정책 폴백 (line 213·230) - - `docs/runtime-flows.md` — 체결→ledger fee/tax 산출·저장 흐름 갱신 - - 테스트: 매수/매도 fee·tax 단위, issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책, KIS 응답 우선 경로 -- Out of Scope: - - 과거 잘못 저장된 ledger 행 소급 보정 (dcy-026 OQ-3 Closed — 보정 안 함) - - portfolio-hub 측 손익 재계산 로직 (수신측 저장소 책임) - - REQ-011 (BrokerPort 포트 계약) 변경 — fee/tax 는 REQ-018 영역, REQ-011 무영향 - - 세율 동적 설정 UI/운영 도구 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 인터페이스/계약 의미 변경(`FillStatus.tax` 0 고정 → 매도 정책 계산) + KIS 응답 파싱(신규 동작, R-2) + env 설정 + baseline 문서(runtime-flows) 정렬을 함께 포함하므로 standard profile. -- 회귀 기준값: issue #24 4건 `fee+tax` 합계 **1,376.9원**(fee 177.9 + tax 1,199). 이슈 기재 1,357원은 원작성자 추정 오차(~20원), baseline 아님. -- `TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS` 변수명에 도메인 라벨이 있으나 단일 정책 지점으로 paper·KIS 가 동일 공유. -- 핵심 리스크: R-1(세율 산식 오류 → 손익 역왜곡, 완화: 1,376.9원 회귀 고정), R-2(KIS 응답 필드명/단위 오파싱 → 라이브 전용 오류, 완화: raw 필드 확인 + 폴백 + 로그). diff --git a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/planning.md b/docs/batches/bat-038_20260612_sell-transaction-tax-fee/planning.md deleted file mode 100644 index 4fd19b1..0000000 --- a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/planning.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-038 Planning - -- Status: confirmed -- Batch: BAT-038 -- Profile: standard - -## Included REQ -- REQ-018 (CHG-20260612-01): 매도 증권거래세 정책 계산, `tax=0.0` 하드코딩 제거, 단일 정책 지점 + env 설정, KIS 응답 정본 우선. AC-3 갱신 + AC-6~9 신규. - -## Delivery Plan -1. `broker_port.py` — 세율 env 상수(`TREX_KRX_TAX_SELL_BPS`=20.0) + 수수료율 env 상수(`TREX_KIS_FEE_BPS`=1.5, 기존 하드코딩 `_FEE_BPS_*` 대체) 도입. `calculate_tax(side, amount)` 신규(매도 정책 세율, 매수 0). `FillStatus.tax` 주석 갱신. -2. `paper.py:90` — `tax=0.0` → `calculate_tax(side, amount)` 호출. -3. `kis.py:213·230` — 브로커 체결 응답 fee/tax 필드 파싱(있으면 정본), 없으면 `calculate_fee`/`calculate_tax` 폴백 + 로그. -4. 테스트 — 매수/매도 fee·tax 단위(매도 0.2%, 매수 tax=0), issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책 일치, KIS 응답 우선·폴백 경로. -5. `docs/runtime-flows.md` — 체결→ledger fee/tax 산출 흐름 갱신. - -## Dependencies -- Internal: - - `trex_core/application/broker_port.py` — `calculate_fee` 기존 함수, env 로딩 패턴(`TREX_*` os.environ) 기존 사례 준용 - - `trex_core/adapters/broker/{paper,kis}.py` — FillStatus 생성 지점(이미 fee 계산 호출 중) - - `trex_core/application/execution.py`·`ledger_outbox.py` — fee/tax 전달 경로(계약 불변 예상, 검증 필요) -- External: - - KIS 체결 응답 raw 필드(수수료/세금 포함 여부) — delivery 단계 raw 확인(CONFIRM, dcy-026) - -## Design Gate -- Design Required Before Implementation: yes -- Reason: (1) `FillStatus.tax` 계약 의미 변경(0 고정 → 매도 정책 계산), (2) KIS 응답 fee/tax 파싱 경로 — 필드 존재 여부·단위 결정이 라이브 정확성에 직결(R-2), (3) env 상수 도입과 기존 `_FEE_BPS_*` 하드코딩 정리 방식, (4) `calculate_tax` 신규 vs `calculate_fee` 통합 결정. design 에서 확정 후 구현. - -## Milestones -1. Design confirmed — `calculate_tax` 시그니처, env 로딩, KIS 파싱·폴백 경로 확정 -2. Implementation — broker_port + paper + kis 수정 + 테스트 -3. Verification — AC-3·6~9 매핑 + issue #24 회귀(1,376.9원) + 전체 회귀 + ruff - -## Risks -- R-1 (High): 세율 산식 오류 시 손익 역방향 왜곡 — 완화: §4 공식 기대 1,376.9원 회귀 테스트 고정, 매수/매도 각 케이스 단위. -- R-2 (Medium): KIS 응답 수수료/세금 필드명·단위(원 vs bp) 오파싱 → 라이브 전용 오류(paper 미검출) — 완화: raw 필드 확인 후 파싱, 미확인 시 정책 폴백 + 로그. design 에서 KIS 응답 스키마 확인. -- R-3 (Low → 해소): execution.py/ledger_outbox.py fee/tax 전달 경로 계약 변경 가능성 — **점검 완료: 불변 확인**. `execution.py:_record_ledger` (line 396-397)가 이미 `fee=float(fs.fee)`, `tax=float(fs.tax)` 를 ledger 로 전달 중. 어댑터 소스(`fs.tax`)만 정확히 채우면 되며 전달 경로 코드 변경 불필요. diff --git a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/verification.md b/docs/batches/bat-038_20260612_sell-transaction-tax-fee/verification.md deleted file mode 100644 index f86aef3..0000000 --- a/docs/batches/bat-038_20260612_sell-transaction-tax-fee/verification.md +++ /dev/null @@ -1,62 +0,0 @@ -# BAT-038 Verification - -- Status: confirmed -- Batch: BAT-038 - -## Acceptance Mapping - -- REQ-018 AC-1 (매수 체결 → holding + buy 원장 1건): - - 이번 배치 변경 범위 밖(기록 경로 불변). BAT-006 evidence 유효. - - Evidence: `test_buy_fill_enqueues_one_ledger_item_and_writer_records_it` PASSED. - -- REQ-018 AC-2 (매도 체결 → closed/inactive + sell 원장 1건): - - Evidence: `test_sell_fill_enqueues_sell_ledger_item` PASSED. - -- REQ-018 AC-3 (`FillStatus` 결정적 execution_id + 계산된 fee + 계산된 tax 운반): - - Evidence: - - 매수: `test_buy_fill_...` — `t.fee > 0.0 and t.tax == 0.0` (매수 무세) PASSED. - - 매도: `test_paper_sell_fill_carries_policy_tax` — `fs.fee == calculate_fee("sell", amount)`, `fs.tax == calculate_tax("sell", amount)`, `fs.tax > 0.0` PASSED. - -- REQ-018 AC-4 (portfolio_id 부재 → 기록 skip + 슬롯 전이 정상): - - 변경 범위 밖. Evidence: `test_portfolio_id_absent_skips_ledger_but_transitions_slot` PASSED + 전체 회귀. - -- REQ-018 AC-5 (원장 기록 단일 주체 = 집행 경로): - - 변경 범위 밖. fee/tax 는 어댑터 산출, 기록 주체(`execution.py:_record_ledger`) 불변. - - Evidence: `_record_ledger` 단일 enqueue 지점 유지(line 396-397 `fee`/`tax` 전달), 전체 회귀 429 passed. - -- REQ-018 AC-6 (매도 tax = amount × 0.2%, 매수 tax = 0): - - Evidence: - - `test_calculate_tax_sell_only`: `calculate_tax("sell", 1_000_000) == 2000.0`, `calculate_tax("buy", ...) == 0.0` PASSED. - - `test_paper_buy_fill_has_zero_tax`: paper 매수 fill `fs.tax == 0.0` PASSED. - -- REQ-018 AC-7 (단일 정책 지점 + env, paper·KIS 동일, `tax=0.0` 하드코딩 제거): - - Evidence: - - `test_calculate_fee_same_rate_buy_and_sell`: 매수·매도 동일 수수료율 PASSED. - - `test_fee_and_tax_zero_for_nonpositive_amount`: 비양수 amount 가드 PASSED. - - 단일 정책 지점: `broker_port.py` `calculate_fee`/`calculate_tax` (env `TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`). paper·kis 가 동일 함수 import 사용(코드 리뷰). - - `tax=0.0` 하드코딩 제거 확인: `grep -rn "tax=0.0\|tax=0," trex_core/adapters trex_core/application` → 코드 사이트 0건(주석 1건만). - -- REQ-018 AC-8 (KIS 응답 fee/tax 정본 우선, 부재 시 폴백): - - Evidence (design + 코드 리뷰): KIS 어댑터가 쓰는 `주식일별주문체결조회`(inquire-daily-ccld, `output1`)는 per-fill 수수료/증권거래세 필드를 제공하지 않음(`docs/broker/kis/rest/국내주식/주문/계좌/021...` output1 38필드 중 fee/tax 없음, `tot_ccld_amt` 만 존재). 따라서 KIS 는 정책 추정값(`calculate_fee`/`calculate_tax`)으로 폴백 — 구조적으로 폴백 경로만 활성, 오파싱 리스크 없음(design Key Decision 3). 향후 fee/tax 노출 endpoint 도입 시 정본 우선 파싱 추가 지점 명시. - - 잔여: 라이브 KIS 스모크는 REL 배포 단계 사항(paper 와 동일 산식이므로 코드 경로 동일). Blocking 아님. - -- REQ-018 AC-9 (issue #24 4건 → fee+tax 합계 1,376.9원): - - Evidence: `test_issue_24_four_fill_regression` — `total_fee == 177.9`, `total_tax == 1199.0`, `total == 1376.9` PASSED. - -## Evidence - -- Test: - - `pytest tests/application/test_fee_tax_policy.py` — 6 passed (신규) - - `pytest tests/application/test_fee_tax_policy.py test_execution_ledger.py test_execution_cancel.py` — 19 passed - - `pytest tests/` — **429 passed, 45 skipped** (BAT-037 423 → +6, 회귀 0) - - `ruff check trex_core tests` — All checks passed - -- Manual / Baseline 동기화 (Architecture Impact: runtime-flows): - - `docs/runtime-flows.md` `체결 → ledger outbox` flow (line 254): `tax=0` → `calculate_tax(side, amount)` 정책 계산 + env 변수(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`) + paper·KIS 공유 설명 갱신. Change Log 항목(2026-06-12 BAT-038) 추가. ✓ 코드와 동기화 확인. - - `docs/project-structure.md`: 변경 불필요(모듈 구조 불변, 함수 1개 추가) — design Reference Doc Update Plan = none. - - `tax=0.0` 하드코딩 제거 grep 확인(위 AC-7). - -## Result -- Ready for Release: true -- Blocking Issues: 없음 - - AC-8 라이브 KIS 스모크는 REL 배포 단계(비거래 시간) 사항이며, paper 와 동일 정책 산식·동일 코드 경로이므로 release-ready 차단 사유 아님. diff --git a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/design.md b/docs/batches/bat-039_20260612_paper-seq-redis-persistence/design.md deleted file mode 100644 index f80d616..0000000 --- a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/design.md +++ /dev/null @@ -1,42 +0,0 @@ -# BAT-039 Design - -- Status: confirmed -- Batch: BAT-039 - -## Architecture Summary -- paper 어댑터의 order seq 발번을 optional `SeqStore` 위로 옮긴다. 주입되면 영속 단조 카운터(Redis `INCR`)를, 미주입이면 기존 in-memory `_seq` 를 사용한다(byte-for-byte 기존 동작 유지). -- `SeqStore` 는 adapters/broker 레이어의 작은 포트로, REQ-016 `token_cache.py`(Redis best-effort)와 같은 패턴·같은 레이어에 둔다. 도메인은 의존하지 않는다(NFR-1 불변). -- 데몬은 `TREX_PAPER_REDIS_URL` 이 설정되면 `RedisSeqStore` 를 만들어 paper 브로커에 주입한다. - -## Changed Areas -- Modules: - - `trex_core/adapters/broker/seq_store.py`(신규): - - `SeqStore` Protocol — `next_seq() -> int` - - `RedisSeqStore(redis_url, *, key, redis_module=None)` — `INCR key` 로 원자·영속 단조 정수 반환. `from_url(socket_connect_timeout=2)`(token_cache 정합). - - `InMemorySeqStore` — thread-safe 카운터(테스트/명시 폴백) - - `trex_core/adapters/broker/paper.py` — `__init__(..., seq_store: Optional[SeqStore] = None)`; `submit_order` 에서 `seq = self._seq_store.next_seq()` (주입 시) / `self._seq += 1` (미주입) - - `trex_core/runtime/config.py` — `PaperBrokerConfig.redis_url: str = ""` + `from_config` `TREX_PAPER_REDIS_URL` - - `trex_core/runtime/daemon.py` — `_build_broker`: paper + `config.paper.redis_url` → `RedisSeqStore(url, key=f"trex:broker:paper:{config.instance_key}:order-seq")` 주입 -- Tests: - - `tests/adapters/broker/test_paper_seq_store.py`(신규) -- Docs: 없음(runtime-flows 흐름 불변 — 발번 영속화는 내부 결정성, 외부 흐름 동일) - -## Key Decisions -1. **`SeqStore.next_seq() -> int` 단일 메서드**: paper 가 필요로 하는 것은 단조 정수뿐. order_id 포맷(`PAPER-{seq:06d}`)은 paper 책임으로 유지. 포트는 최소. -2. **Redis `INCR`(원자·영속)**: GET/SET race 없이 단일 명령으로 단조 보장. 키 부재 시 0→1 로 시작해 첫 order_id 가 `PAPER-000001`(기존과 동일 시작값). -3. **키 스코프 = instance_key**: `trex:broker:paper:{instance_key}:order-seq`. 인스턴스별 독립 카운터(다중 인스턴스 교차 방지). 단일 인스턴스(default)는 단일 키. -4. **폴백 의미 = 설정 부재만**: `TREX_PAPER_REDIS_URL` 미설정 → seq store 미주입 → in-memory `_seq`(기존 동작). **런타임 Redis 오류는 전파**(silent in-memory 폴백 금지 — 그 폴백이 곧 버그 재현). paper 는 테스트 하네스이므로 오류 전파가 안전. (R-1) -5. **token_cache best-effort 와의 차이**: 토큰 캐시는 miss/error → 재발급(무해)이라 best-effort 폴백. seq 는 error → 폴백이 곧 멱등 키 충돌이므로 best-effort 폴백을 적용하지 않는다. - -## Edge Cases -- seq store 미주입: 기존 `self._seq += 1`, `PAPER-000001`부터. AC-1~3 불변. -- INCR 결과 > 999999: `f"{seq:06d}"` 는 최소 패딩 → `PAPER-1000000` 정상 표기, 고유성 유지. -- 재시작 모사(같은 key 공유 RedisSeqStore/InMemorySeqStore 인스턴스): 두 번째 PaperBroker 가 첫 인스턴스 이후 seq 로 시작 → 매도 order_id ≠ 직전 매수 → correlation_id 비충돌. -- 동시 submit(스레드): `INCR` 원자 / `InMemorySeqStore` 는 lock 으로 보호. - -## Architecture Impact -- none (runtime-flows 흐름 불변. 발번 경로 내부 결정성 보강만, 외부 계약/흐름 동일) - -## Reference Doc Update Plan -- project-structure.md: none (adapters/broker 에 파일 1개 추가 — 구조 패턴 불변, token_cache 와 동급) -- runtime-flows.md: none diff --git a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/implementation.md b/docs/batches/bat-039_20260612_paper-seq-redis-persistence/implementation.md deleted file mode 100644 index 3d8b67a..0000000 --- a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/implementation.md +++ /dev/null @@ -1,31 +0,0 @@ -# BAT-039 Implementation - -- Status: completed -- Batch: BAT-039 - -## Plan Summary -- `seq_store.py` 신규(`SeqStore`/`RedisSeqStore`/`InMemorySeqStore`), paper 어댑터에 optional 주입, config/daemon wiring, 재시작 모사 회귀 테스트. - -## Changed Files -- `trex_core/adapters/broker/seq_store.py` (신규) — `SeqStore` Protocol, `RedisSeqStore`(INCR), `InMemorySeqStore` -- `trex_core/adapters/broker/paper.py` — `SeqStore` import, `__init__(seq_store=None)`, `submit_order` 발번 분기 -- `trex_core/runtime/config.py` — `PaperBrokerConfig.redis_url` + `TREX_PAPER_REDIS_URL` -- `trex_core/runtime/daemon.py` — `_build_broker` paper 경로 `RedisSeqStore` wiring(key=`trex:broker:paper:{instance_key}:order-seq`) -- `tests/adapters/broker/test_paper_seq_store.py` (신규, 5 테스트) - -## Execution Log -1. `seq_store.py`: `next_seq()` 단일 메서드 포트. `RedisSeqStore` 는 `from_url(socket_connect_timeout=2)` + 원자 `INCR`(키 부재 시 1부터 → 첫 order_id `PAPER-000001`). `InMemorySeqStore` 는 lock 보호 카운터. 런타임 Redis 오류는 전파(silent in-memory 폴백 금지, 버그 재현 방지). -2. `paper.py`: `seq_store` 주입 시 `next_seq()`, 미주입 시 기존 `self._seq += 1`(byte-for-byte 유지). `_lock` 안에서 발번. -3. `config.py`: `PaperBrokerConfig.redis_url: str = ""`, `from_config` 에서 `TREX_PAPER_REDIS_URL` 읽기. -4. `daemon.py`: `config.paper.redis_url` 있으면 `RedisSeqStore` 생성·주입(instance_key 스코프 키). -5. 테스트 5종: 재시작 단조성, 매수→재시작→매도 correlation_id 비충돌, (버그 문서화) store 없을 때 충돌 재현, in-memory 폴백 시작값, fake redis INCR 단조·공유. - -## Validation -- `pytest tests/adapters/broker/test_paper_seq_store.py` — 5 passed -- `pytest tests/` — **434 passed, 45 skipped** (BAT-038 429 → +5, 회귀 0) -- `ruff check trex_core tests` — All checks passed -- 폴백 회귀: `test_in_memory_fallback_starts_at_one` 으로 미주입 시 `PAPER-000001` 시작 확인(AC-1~3 기존 paper 테스트 회귀 없음). - -## Remaining Risks -- 없음. R-1(런타임 오류 silent 폴백)은 전파 설계로 제거, R-2(자리수 초과)는 `:06d` 최소 패딩으로 무해, R-3(다중 인스턴스 키 공유)은 instance_key 스코프로 분리. -- 라이브 Redis 통합 스모크는 운영 배포 단계 사항(테스트는 fake redis_module + InMemorySeqStore 로 INCR/단조 계약 검증). 실 KIS 는 본 변경 무관(odno 고유). diff --git a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/index.md b/docs/batches/bat-039_20260612_paper-seq-redis-persistence/index.md deleted file mode 100644 index 6f42a5d..0000000 --- a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-039: paper 브로커 order seq Redis 영속화 (CHG-20260612-01) - -- Status: release-candidate -- Profile: standard -- Source Discovery: 없음 (issue #4, dcy-003/REQ-011 change-req) -- Included REQ: REQ-011 (CHG-20260612-01) -- Excluded REQ: 없음 - -## Summary -- paper 브로커 order seq 가 데몬 재시작 시 0 으로 리셋되어 `PAPER-000001` 부터 재발번 → 결정적 `execution_id` 재생성 → 원장 멱등 키 `correlation_id`(side 미포함)가 직전 run 매수와 충돌 → `ON CONFLICT DO NOTHING` dedup 으로 정상 매도 원장이 조용히 누락되는 paper 한정 결정성 버그(issue #4)를 닫는다. paper 어댑터에 optional seq store(Redis INCR 영속, REQ-016 token_cache 패턴)를 주입하고 미구성 시 in-memory 폴백. - -## Scope -- In Scope: - - `trex_core/adapters/broker/seq_store.py` 신규 — `SeqStore` 포트 + `RedisSeqStore`(INCR) + `InMemorySeqStore` - - `trex_core/adapters/broker/paper.py` — optional `seq_store` 주입, `submit_order` 발번 경로 - - `trex_core/runtime/config.py` — `PaperBrokerConfig.redis_url`(`TREX_PAPER_REDIS_URL`) - - `trex_core/runtime/daemon.py` — `_build_broker` paper 경로 seq store wiring - - 테스트: 재시작 모사(seq store 공유) 단조성 + correlation_id 비충돌 + in-memory 폴백 -- Out of Scope: - - 원장 `correlation_id` 에 side 추가(대안 미채택 — 프로덕션 계약 영향) - - KIS 어댑터(odno 고유, 충돌 없음) - - 과거 누락 원장 행 소급 보정 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- paper 한정 테스트 하네스 결정성 문제 — 실 KIS 는 broker `odno` 가 주문마다 고유해 충돌 없음(프로덕션 버그 아님). -- seq store 미주입(Redis 미구성) 시 기존 in-memory `_seq` 동작을 byte-for-byte 유지(AC-1~3 불변, CI 영향 최소). -- Redis 인프라는 REQ-016 `RedisTokenCache` 패턴(best-effort, `from_url(socket_connect_timeout=2)`)을 재사용. diff --git a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/planning.md b/docs/batches/bat-039_20260612_paper-seq-redis-persistence/planning.md deleted file mode 100644 index 50392b9..0000000 --- a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/planning.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-039 Planning - -- Status: confirmed -- Batch: BAT-039 -- Profile: standard - -## Included REQ -- REQ-011 (CHG-20260612-01): paper order seq Redis 영속화 + in-memory 폴백. AC-4 신규. AC-1~3 불변. - -## Delivery Plan -1. `seq_store.py` 신규 — `SeqStore` Protocol(`next_seq() -> int`), `RedisSeqStore`(Redis `INCR`, 영속·원자), `InMemorySeqStore`(thread-safe 카운터, 폴백/테스트용). token_cache 패턴(`from_url(socket_connect_timeout=2)`) 정합. -2. `paper.py` — `__init__(..., seq_store: Optional[SeqStore] = None)`. `submit_order`: `seq_store` 주입 시 `seq_store.next_seq()`, 미주입 시 기존 `self._seq += 1`(byte-for-byte 유지). -3. `config.py` — `PaperBrokerConfig.redis_url: str = ""`, `from_config` 에 `TREX_PAPER_REDIS_URL` 읽기. -4. `daemon.py` — `_build_broker` paper 경로: `config.paper.redis_url` 있으면 `RedisSeqStore` 생성·주입. -5. 테스트 — 동일 seq store 공유 두 PaperBroker(재시작 모사) order_id 단조 증가, 매수→재시작→매도 correlation_id 비충돌, 미주입 시 `PAPER-000001` 폴백, AC-1~3 회귀. - -## Dependencies -- Internal: - - `trex_core/adapters/broker/token_cache.py` — Redis best-effort 패턴 참조 - - `trex_core/application/broker_port.py` — `OrderRequest`/`OrderAck` 불변 - - execution_id 합성(`paper:{order_id}:{filled_qty}`) — order_id 단조성에 의존 -- External: - - Redis(운영). 테스트는 fake/in-memory redis_module 또는 InMemorySeqStore 로 충분. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 `SeqStore` 포트(인터페이스 추가) + Redis 키 네임스페이스/스코프(instance_key) + INCR 원자성·런타임 오류 처리 + 폴백 의미를 design 에서 확정해야 함. - -## Milestones -1. Design confirmed — SeqStore 계약, Redis 키, 폴백 의미 확정 -2. Implementation — seq_store + paper + config + daemon + 테스트 -3. Verification — AC-4 + AC-1~3 회귀 + 전체 회귀 + ruff - -## Risks -- R-1 (Medium): Redis 런타임 오류 시 in-memory 로 silent 폴백하면 버그 재현 위험 — 완화: 미주입(설정 부재)만 폴백, 런타임 오류는 전파(test 하네스이므로 허용). design Key Decision 에서 명시. -- R-2 (Low): order_id 가 6자리 초과(INCR 누적) 시 `PAPER-{seq:06d}` 포맷 — `:06d` 는 최소 패딩이라 초과 자리수도 정상 표기, 충돌 없음. -- R-3 (Low): 다중 인스턴스가 같은 Redis 키 공유 시 seq 교차 — 단조·고유성 유지되므로 correctness 무해. instance_key 스코프로 분리 가능(design). diff --git a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/verification.md b/docs/batches/bat-039_20260612_paper-seq-redis-persistence/verification.md deleted file mode 100644 index a84df92..0000000 --- a/docs/batches/bat-039_20260612_paper-seq-redis-persistence/verification.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-039 Verification - -- Status: confirmed -- Batch: BAT-039 - -## Acceptance Mapping - -- REQ-011 AC-1 (submit accepted, 동기 단축 없음): - - 변경 범위 밖(발번 경로만 수정, 비동기 계약 불변). Evidence: 기존 paper 테스트 전체 회귀 — `pytest tests/` 434 passed. - -- REQ-011 AC-2 (결정적 fill 시나리오 — 즉시/N틱/부분/거부): - - 변경 범위 밖. Evidence: 기존 paper 더블 시나리오 테스트 회귀 없음(434 passed). - -- REQ-011 AC-3 (도메인 순수성 — broker 의존 0, BrokerPort 구현 adapters 한정): - - `seq_store.py` 는 adapters/broker 레이어에 위치, 도메인 미참조. Evidence: 도메인 import 스윕 회귀 없음(전체 회귀 green). - -- REQ-011 AC-4 (seq store 재시작 단조 + correlation_id 비충돌 + 미주입 폴백): - - Evidence: - - `test_shared_seq_store_is_monotonic_across_restart`: 같은 store 공유 두 broker(재시작 모사) → `PAPER-000001` → `PAPER-000002` 단조 PASSED. - - `test_post_restart_sell_does_not_collide_with_prior_buy`: 매수→재시작→매도 correlation_id 비충돌 PASSED. - - `test_without_store_restart_reproduces_collision`: (버그 문서화) store 없을 때 두 broker 모두 `PAPER-000001` → correlation_id 동일(원장 dedup 발생) PASSED — 수정 전 동작 고정. - - `test_in_memory_fallback_starts_at_one`: 미주입 시 `PAPER-000001` 부터(기존 동작 유지) PASSED. - - `test_redis_seq_store_incr_is_monotonic_and_shared`: fake redis INCR 로 재시작 모사 단조·공유 PASSED. - -## Evidence - -- Test: - - `pytest tests/adapters/broker/test_paper_seq_store.py` — 5 passed (신규) - - `pytest tests/` — **434 passed, 45 skipped** (BAT-038 429 → +5, 회귀 0) - - `ruff check trex_core tests` — All checks passed - -- Manual / Architecture Impact (none): - - design.md Architecture Impact = none — `docs/project-structure.md`, `docs/runtime-flows.md` 동기화 불필요(발번 경로 내부 결정성 보강, 외부 흐름/계약 불변). `seq_store.py` 는 token_cache 와 동급 adapters 파일 추가로 구조 패턴 불변. - - `_build_broker` wiring(`TREX_PAPER_REDIS_URL` → `RedisSeqStore`, instance_key 스코프 키) 코드 리뷰 확인. - -## Result -- Ready for Release: true -- Blocking Issues: 없음 - - 라이브 Redis 통합 스모크는 REL 배포 단계 사항. 단위 테스트가 INCR/단조/폴백 계약을 fake redis_module + InMemorySeqStore 로 검증하므로 release-ready 차단 아님. 실 KIS 무관(odno 고유). diff --git a/docs/batches/bat-040_20260612_slots-rpc-db-filter/design.md b/docs/batches/bat-040_20260612_slots-rpc-db-filter/design.md deleted file mode 100644 index 01b5eeb..0000000 --- a/docs/batches/bat-040_20260612_slots-rpc-db-filter/design.md +++ /dev/null @@ -1,40 +0,0 @@ -# BAT-040 Design - -- Status: confirmed -- Batch: BAT-040 - -## Architecture Summary -- status 필터링을 핸들러(Python)에서 저장소 경계로 내린다. `SlotRepository.list_slots` 에 optional `status_filter: set[str]` 를 추가하고(REQ-002 포트), PostgreSQL 은 `status = ANY(%s)`(REQ-006), in-memory 는 set 멤버십으로 필터한다. -- 핸들러(REQ-073)는 요청 filter 토큰을 status 값 집합으로 매핑해 `list_slots` 에 위임하고, watchdog 갱신용 holding 슬롯을 이미 fetch 한 결과에서 재사용할 수 있으면(all/holding) 두 번째 쿼리를 생략한다. -- 관찰 가능한 응답 계약은 불변 — 동작 보존 최적화. - -## Changed Areas -- Modules: - - `trex_core/domain/slot/repository.py` — `list_slots(self, instance_key, status_filter: Optional[set[str]] = None)` - - `trex_core/domain/slot/in_memory_repository.py` — `status_filter` 멤버십 필터 - - `trex_core/adapters/persistence/postgresql.py` — `status_filter` → `WHERE instance_key = %s AND status = ANY(%s)` - - `trex_core/application/messaging_handlers.py` — filter→status 집합 매핑 + 위임 + watchdog 재사용 -- Tests: - - `tests/domain/`(in-memory status_filter), `tests/application/test_slots_handler.py`(filter/spy/키 회귀), `tests/adapters/persistence/`(PG 필터 게이트) -- Docs: 없음(관찰 계약·흐름 불변) - -## Key Decisions -1. **`status_filter: Optional[set[str]]` (status 값 집합)**: 단일 status 가 아니라 집합 — `pending` 토큰이 `{pending_buy, pending_sell}` 두 status 로 매핑되기 때문. `None` = 전체(기존). 핸들러가 토큰→집합 매핑을 소유(저장소는 status 값만 안다). -2. **filter 토큰 매핑(핸들러)**: `inactive→{"inactive"}`, `pending→{"pending_buy","pending_sell"}`, `holding→{"holding"}`, `error→{"error"}`, `all`/미인식→`None`. REQ-073 AC-2(미인식 filter → all 일관 처리) 보존. -3. **PG `status = ANY(%s)`**: psycopg 가 Python list 를 PG array 로 어댑팅. `IN` 대신 `ANY` 로 단일 파라미터 바인딩. None 이면 기존 `WHERE instance_key = %s` 분기 유지. -4. **watchdog 두 번째 쿼리 생략 조건**: `status_filter is None`(all) 또는 filter=="holding" 이면 이미 fetch 한 `slots` 에 holding 이 전부 포함되므로 거기서 추출. 그 외(inactive/pending/error)는 holding 이 응답에 없으므로 기존 `list_holding_slots` 호출. spy=True 면 watchdog 자체를 갱신하지 않아 어느 경우든 두 번째 쿼리 없음. -5. **동작 보존**: 핸들러 출력(slots/available_slots/degraded/reason/message, spy 시맨틱)은 불변. AC-1~6 회귀로 보증. - -## Edge Cases -- `list_slots` 무인자 호출자(reconcile/safety-sell/kill-switch): default `None` → 기존 전체 fetch, 동작 불변. -- filter=="all": status_filter=None → 전체 fetch, watchdog 은 fetch 결과에서 holding 추출(두 번째 쿼리 생략). -- filter=="holding": status_filter={"holding"} → holding 만 fetch, watchdog 재사용(두 번째 쿼리 생략). -- filter=="inactive"/"pending"/"error": holding 미포함 → watchdog 위해 `list_holding_slots` 호출(spy=false 시). -- 빈 결과(해당 status 슬롯 0): 빈 리스트 반환(정상). - -## Architecture Impact -- none (runtime-flows 흐름·관찰 계약 불변. 내부 쿼리 위치/호출 수 최적화만) - -## Reference Doc Update Plan -- project-structure.md: none -- runtime-flows.md: none (체결/슬롯 흐름 불변, 핸들러 내부 쿼리 최적화) diff --git a/docs/batches/bat-040_20260612_slots-rpc-db-filter/implementation.md b/docs/batches/bat-040_20260612_slots-rpc-db-filter/implementation.md deleted file mode 100644 index 202af41..0000000 --- a/docs/batches/bat-040_20260612_slots-rpc-db-filter/implementation.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-040 Implementation - -- Status: completed -- Batch: BAT-040 - -## Plan Summary -- `list_slots(status_filter)` 포트(REQ-002) + in-memory + PG(REQ-006) 구현, 핸들러(REQ-073) filter→status 집합 위임 + watchdog 조회 재사용, 테스트. - -## Changed Files -- `trex_core/domain/slot/repository.py` — `list_slots(instance_key, status_filter=None)` Protocol 시그니처 + docstring -- `trex_core/domain/slot/in_memory_repository.py` — `status_filter` 멤버십 필터 -- `trex_core/adapters/persistence/postgresql.py` — `list_slots` `status = ANY(%s)` 분기(None=전체) -- `trex_core/application/messaging_handlers.py` — `_FILTER_STATUS` 매핑 + `list_slots(status_filter)` 위임 + watchdog 조회 재사용(all/holding 시 두 번째 커넥션 생략) -- `tests/domain/slot/test_list_slots_filter.py` (신규, 5) -- `tests/adapters/persistence/test_postgresql_repository.py` — `test_list_slots_status_filter_pg` (PG 게이트) - -## Execution Log -1. `repository.py`: Protocol `list_slots` 에 `status_filter: Optional[set[str]] = None` 추가. -2. `in_memory_repository.py`: `status_filter is None or s.status.value in status_filter` 필터. -3. `postgresql.py`: `status_filter` truthy 면 `WHERE instance_key = %s AND status = ANY(%s)`(list 파라미터), 아니면 기존 전체 쿼리. -4. `messaging_handlers.py`: 모듈 상수 `_FILTER_STATUS`(inactive/pending/holding/error→status 집합, all/미인식→None). 핸들러가 `list_slots(instance_key, status_filter)` 위임. watchdog: `status_filter is None or filter=="holding"` 면 fetch 결과에서 holding 추출, 아니면 `list_holding_slots`(spy=true 면 둘 다 생략). -5. 테스트: in-memory 5종(None 전체/단일 status/pending 그룹/무매치 빈/instance 스코프), PG 게이트 1종. - -## Validation -- `pytest tests/application/test_slots_handler.py` — 16 passed (filter/spy/응답 키/available_slots 회귀, 관찰 계약 불변 확인) -- `pytest tests/domain/slot/test_list_slots_filter.py` — 5 passed (신규) -- `pytest tests/` — **439 passed, 46 skipped** (BAT-039 434 → +5 in-memory; PG status_filter 테스트는 DSN 부재로 skip, 동일 게이트) -- `ruff check trex_core tests` — All checks passed -- 무인자 `list_slots` 호출자(reconcile/safety-sell/kill-switch 등) 전수 회귀 없음(default None 동작 보존). - -## Remaining Risks -- 없음. R-1(무인자 호출자 회귀)=default None 보존+전체 회귀 green, R-2(ANY 어댑팅)=psycopg list→array(PG 게이트 테스트, DSN 가용 시 실행), R-3(watchdog 재사용)=spy/holding 터치 테스트 회귀 green. -- PG `status = ANY` 경로의 라이브 검증은 `TREX_POSTGRESQL_TEST_DSN` 설정 환경에서 게이트 테스트로 수행(CI/로컬 DSN). in-memory parity 가 동작 계약을 보증. diff --git a/docs/batches/bat-040_20260612_slots-rpc-db-filter/index.md b/docs/batches/bat-040_20260612_slots-rpc-db-filter/index.md deleted file mode 100644 index 7137474..0000000 --- a/docs/batches/bat-040_20260612_slots-rpc-db-filter/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-040: slots RPC per-request full scan 개선 — list_slots DB-level status 필터 (CHG-20260612-01) - -- Status: release-candidate -- Profile: standard -- Source Discovery: 없음 (issue #25, REQ-002/006/073 change-req) -- Included REQ: REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) -- Excluded REQ: 없음 - -## Summary -- `trex.slots.req` 핸들러가 매 요청마다 `list_slots`(전 행 fetch) 후 Python 에서 status 를 거르던 구조(issue #25)를 DB 레벨 필터로 바꾼다. `SlotRepository.list_slots` 에 `status_filter` 옵션을 추가(REQ-002 포트 + REQ-006 PG 구현)하고, 핸들러가 filter 토큰을 status 집합으로 매핑해 위임한다(REQ-073). watchdog 갱신용 holding 조회는 응답 필터가 이미 holding 을 포함한 경우(all/holding) fetch 결과를 재사용해 두 번째 커넥션을 생략한다. - -## Scope -- In Scope: - - `trex_core/domain/slot/repository.py` — `SlotRepository.list_slots(instance_key, status_filter=None)` (REQ-002) - - `trex_core/domain/slot/in_memory_repository.py` — status_filter 구현 (REQ-002) - - `trex_core/adapters/persistence/postgresql.py` — `list_slots` DB-level `status = ANY(%s)` (REQ-006) - - `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler` filter→status 집합 매핑·위임 + watchdog 조회 재사용 (REQ-073) - - 테스트: in-memory status_filter 단위, PG 필터(게이트), 핸들러 filter/spy/응답 키 회귀 -- Out of Scope: - - 관찰 가능한 RPC 계약 변경(REQ-073 AC-1~6 불변 — 동작 보존 최적화) - - 스키마/마이그레이션 변경(쿼리 한정) - - overlay(paused/warned) 서버측 필터(REQ-074 후속) - - 인덱스 추가/튜닝(별도 운영 판단) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 동작 보존 내부 최적화 — REQ-073 관찰 계약(filter 결과/키 집합/spy/degraded/available_slots) 불변. REQ-073 은 Implemented 유지(재검증), REQ-002/006 은 신규 AC-6 으로 Approved→Implemented 복귀. -- 도메인 포트 시그니처 변경(인터페이스 영향) + 다중 REQ 횡단 → minor-change fast-path 불가, standard profile. -- `list_holding_slots` 는 이미 status-scoped(full scan 아님). 주 개선은 `list_slots` full scan 제거 + all/holding 필터 시 두 번째 커넥션 생략. diff --git a/docs/batches/bat-040_20260612_slots-rpc-db-filter/planning.md b/docs/batches/bat-040_20260612_slots-rpc-db-filter/planning.md deleted file mode 100644 index 6538fdc..0000000 --- a/docs/batches/bat-040_20260612_slots-rpc-db-filter/planning.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-040 Planning - -- Status: confirmed -- Batch: BAT-040 -- Profile: standard - -## Included REQ -- REQ-002 (CHG-20260612-01): `SlotRepository.list_slots(instance_key, status_filter=None)` 포트 + in-memory 구현. AC-6 신규. -- REQ-006 (CHG-20260612-01): PostgreSQL `list_slots` DB-level `status = ANY(%s)`. AC-6 신규. -- REQ-073 (CHG-20260612-02): 핸들러 filter→status 집합 매핑·위임 + watchdog 조회 재사용. 관찰 계약 불변, Implemented 유지. - -## Delivery Plan -1. `repository.py` — `list_slots` Protocol 시그니처에 `status_filter: Optional[set[str]] = None` 추가(docstring 갱신). -2. `in_memory_repository.py` — `status_filter` 가 주어지면 `s.status.value in status_filter` 필터, None 이면 전체. -3. `postgresql.py` — `status_filter` 주어지면 `WHERE instance_key = %s AND status = ANY(%s)`(list 파라미터), None 이면 기존 전체 쿼리. -4. `messaging_handlers.py` — filter 토큰→status 집합 매핑(`inactive→{inactive}`, `pending→{pending_buy,pending_sell}`, `holding→{holding}`, `error→{error}`, all/미인식→None) 후 `list_slots(instance_key, status_filter)`. watchdog: `status_filter is None or filter=="holding"` 면 fetch 결과에서 holding 추출(두 번째 쿼리 생략), 아니면 `list_holding_slots`. -5. 테스트 — in-memory status_filter 단위, 핸들러 filter/spy/응답 키 회귀(기존), PG 필터(게이트, DSN 가용 시). - -## Dependencies -- Internal: - - `list_slots` 호출자 전수(reconcile/safety-sell/kill-switch 등)는 무인자 → default None 으로 동작 불변(회귀 확인 필요) - - `SlotState` 값 문자열(`inactive/pending_buy/holding/pending_sell/error`) -- External: PostgreSQL(통합 게이트). 단위는 in-memory. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 도메인 포트 시그니처 변경(인터페이스) + status 집합 매핑 규칙 + watchdog 쿼리 재사용 조건 + in-memory/PG parity 를 design 에서 확정. - -## Milestones -1. Design confirmed — 포트 시그니처·status 집합 매핑·watchdog 재사용 조건 확정 -2. Implementation — repository/in_memory/postgresql/handler + 테스트 -3. Verification — REQ-002/006 AC-6 + REQ-073 AC-1~6 회귀 + 기존 list_slots 호출자 회귀 + ruff - -## Risks -- R-1 (Medium): `list_slots` 무인자 호출자(reconcile 등)가 status_filter default 로 동작이 바뀌면 회귀 — 완화: default None = 기존 전체, 전수 호출 회귀 테스트. -- R-2 (Low): PG `ANY(%s)` 파라미터 어댑팅(list→array) — 완화: psycopg list→array 어댑팅 사용, PG 게이트 통합 테스트로 확인. -- R-3 (Low): watchdog 조회 재사용 조건 오류 시 holding 워치독 미갱신 — 완화: filter별 spy/watchdog 테스트로 holding 터치 보존 확인(REQ-073 AC-4). diff --git a/docs/batches/bat-040_20260612_slots-rpc-db-filter/verification.md b/docs/batches/bat-040_20260612_slots-rpc-db-filter/verification.md deleted file mode 100644 index 25bb99d..0000000 --- a/docs/batches/bat-040_20260612_slots-rpc-db-filter/verification.md +++ /dev/null @@ -1,53 +0,0 @@ -# BAT-040 Verification - -- Status: confirmed -- Batch: BAT-040 - -## Acceptance Mapping - -- REQ-002 AC-6 (`list_slots(instance_key, status_filter)` 포트 + in-memory, None 전체): - - Evidence (`tests/domain/slot/test_list_slots_filter.py`): - - `test_none_returns_all`: None/무인자 → 전체 5 PASSED. - - `test_single_status_filter`: `{"holding"}`→1, `{"inactive"}`→3 PASSED. - - `test_pending_group_filter`: `{"pending_buy","pending_sell"}`→pending 1 PASSED. - - `test_empty_when_no_match`: `{"error"}`→[] PASSED. - - `test_instance_scoped`: 타 instance 0 PASSED. - -- REQ-006 AC-6 (PostgreSQL `list_slots` DB-level `status = ANY(%s)`, None 전체): - - Evidence: `tests/adapters/persistence/test_postgresql_repository.py::test_list_slots_status_filter_pg` — None→전체 3, `{"inactive"}`→2, pending 그룹→1, `{"error"}`→[]. `TREX_POSTGRESQL_TEST_DSN` 설정 시 실행(현재 미설정 → skip, 전 PG 테스트와 동일 게이트). in-memory parity(REQ-002 AC-6)가 동작 계약을 보증. - -- REQ-073 AC-1 (토폴로지 — `trex.slots.req` 존재): - - 변경 범위 밖(핸들러 내부 위임만). Evidence: 전체 회귀 439 passed. - -- REQ-073 AC-2 (filter 동작 — inactive/pending/holding/all): - - Evidence: `test_filter_*` 5개 PASSED (`test_slots_handler.py`) — DB 위임 후에도 filter 결과 동일. 미인식 filter→None(all) 일관 처리 보존. - -- REQ-073 AC-3 (응답 키 집합 — slots/available_slots/degraded/reason/message): - - Evidence: `test_response_keys_only_slots_health` PASSED — 키 집합 불변. - -- REQ-073 AC-4 (spy=true 워치독 미갱신 / spy=false 갱신): - - Evidence: `test_spy_true_no_watchdog_update`, `test_spy_false_updates_watchdog` PASSED — watchdog 조회 재사용 최적화 후에도 holding 터치 시맨틱 보존. - -- REQ-073 AC-5 (degraded 신호 전파): - - Evidence: `test_degraded_signal_propagated` PASSED. - -- REQ-073 AC-6 (available_slots 값 규칙): - - Evidence: `test_available_slots_*` 3개 PASSED — capacity hint 불변. - -## Evidence - -- Test: - - `pytest tests/domain/slot/test_list_slots_filter.py` — 5 passed (신규) - - `pytest tests/application/test_slots_handler.py` — 16 passed (REQ-073 AC-1~6 회귀, 관찰 계약 불변) - - `pytest tests/` — **439 passed, 46 skipped** (BAT-039 434 → +5; PG status_filter 테스트는 DSN 부재로 skip, 동일 게이트) - - `ruff check trex_core tests` — All checks passed - -- Manual / Architecture Impact (none): - - design.md Architecture Impact = none — `docs/project-structure.md`, `docs/runtime-flows.md` 동기화 불필요(관찰 계약·흐름 불변, 핸들러 내부 쿼리 위치/호출 수 최적화만). - - 무인자 `list_slots` 호출자(reconcile/safety-sell/kill-switch) default None 동작 보존 — 전체 회귀로 확인. - - 핸들러 watchdog 조회 재사용 조건(all/holding fetch 재사용, 그 외 list_holding_slots) 코드 리뷰 + AC-4 테스트로 확인. - -## Result -- Ready for Release: true -- Blocking Issues: 없음 - - PG `status = ANY` 경로의 라이브 검증은 `TREX_POSTGRESQL_TEST_DSN` 가용 환경의 게이트 테스트로 수행(REQ-006 기존 PG 테스트 전부 동일 조건). in-memory parity 가 release-ready 를 보증 — 차단 아님. diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/design.md b/docs/batches/bat-041_20260612_long-term-instance-profile/design.md deleted file mode 100644 index 4dda704..0000000 --- a/docs/batches/bat-041_20260612_long-term-instance-profile/design.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-041 Design - -- Status: confirmed -- Batch: BAT-041 - -## Architecture Summary -- watchdog timeout 설정을 단일 지점에서 per-instance 화한다: `DaemonInstanceConfig.watchdog_timeout_sec`(TOML, optional) → `DaemonConfig.from_config` 에서 값이 있으면 `SafetySellConfig.watchdog_timeout_sec` 를 `dataclasses.replace` 로 override. 소비자 2곳(slots RPC 핸들러의 expired 표시, SafetySellService 의 trigger gating)은 모두 `config.safety_sell.watchdog_timeout_sec` 를 읽으므로 자동으로 일치한다. -- 중장기 프로파일은 신규 코드 없이 기존 설정 메커니즘 조합(`safety_sell_enabled=false` + `watchdog_timeout_sec=1800` + `max_active_slots` + market-close env 미설정)으로 표현되고, `config/longterm.example.toml` 로 표준화한다. -- runtime-flows 에 중장기 병행 운영 흐름(보호 경계 포함)을 추가한다. - -## Changed Areas -- Modules: - - `trex_core/runtime/config.py`: - - `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None` - - `from_toml`: `watchdog_timeout_sec` 파싱 + `<= 0` 거부(기존 `max_active_slots` 검증 패턴) - - `from_config`: `inst.watchdog_timeout_sec is not None` → `safety_sell = dataclasses.replace(safety_sell, watchdog_timeout_sec=...)` - - `config/longterm.example.toml` (신규) -- Tests: `tests/runtime/test_config*.py` 패턴 — TOML 적용/env 폴백/독립/검증 거부 -- Docs: `docs/runtime-flows.md`(중장기 운영 흐름 + Change Log), `docs/project-structure.md`(불필요 예상 — 구조 불변, config 필드 1개) - -## Key Decisions -1. **override 지점 = `from_config` 단일 지점**: TOML 값이 frozen `SafetySellConfig` 를 `dataclasses.replace` 로 교체. daemon.py·safety_sell.py·messaging_handlers.py 는 무변경 — 소비자가 이미 `config.safety_sell.watchdog_timeout_sec` 를 읽음(R-1 제거). -2. **TOML 우선, env 폴백**: `watchdog_timeout_sec` TOML 미설정 시 기존 env(`TREX_WATCHDOG_TIMEOUT`, 기본 120) 그대로 — 기존 인스턴스 byte-for-byte 무회귀(REQ-076 AC-2). REQ-060(env 폐기)과 달리 env 는 보존: 기존 운영 사용 중. -3. **`<= 0` 명시 거부**: `max_active_slots`/`default_order_timeout_sec` 와 동일한 from_toml 검증 패턴. -4. **프로파일 = 설정 조합(신규 코드 0)**: REQ-077 은 example TOML + 검증으로 충족. market-close 는 env 영역이므로 TOML 주석으로 "미설정 전제" 명시(코드 강제 없음 — 운영 절차). -5. **S-1 검증 범위**: 멀티데이 보유 유지는 영구 슬롯 DB 영속(dcy-020)의 기존 보장 + 자율 청산 미발화는 `safety_sell_enabled=false` 워커 미기동(BAT-036 evidence). 본 batch 는 프로파일 조합이 의도대로 로딩·적용되는지(워커 미기동 + timeout 1800 + 독립 instance_key)를 설정·기동 구성 단위로 검증하고, 실 데몬 장기 run 은 release 운영 검증으로 위임. - -## Edge Cases -- TOML `watchdog_timeout_sec = 0` 또는 음수: from_toml 명시 거부(RuntimeError). -- TOML 미설정 + env 미설정: 기본 120s(기존과 동일). -- TOML 설정 + env 도 설정: TOML 승리(per-instance 가 전역보다 구체적). -- 당일 인스턴스(TOML 미설정)와 중장기(1800) 병행: 인스턴스별 DaemonConfig 가 독립 생성되므로 분리 적용. - -## Architecture Impact -- runtime-flows (중장기 병행 운영 흐름 신설 — REQ-078 소관) - -## Reference Doc Update Plan -- project-structure.md: none 예상(구조 불변 — config 필드 1개 + 예시 파일. 예시 파일 위치 언급이 필요하면 한 줄) -- runtime-flows.md: 중장기 인스턴스 운영 섹션 신설 — 병행 토폴로지(instance_key 분리), 프로파일 구성(REQ-077 레시피), 보호 규칙 경계(전략 전적 책임 + CLI exit 백스톱, 코어 손절매 루틴 미사용), forced_close_at 미사용(발화 불가 근거), watchdog 중장기 의미(1800s, last_seen 가시성 유지). Change Log 항목 추가. diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md b/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md deleted file mode 100644 index 721e4e2..0000000 --- a/docs/batches/bat-041_20260612_long-term-instance-profile/implementation.md +++ /dev/null @@ -1,31 +0,0 @@ -# BAT-041 Implementation - -- Status: completed -- Batch: BAT-041 - -## Plan Summary -- `DaemonInstanceConfig.watchdog_timeout_sec`(TOML, env 폴백) + `from_config` 단일 지점 override, `config/longterm.example.toml` 표준 프로파일, runtime-flows 중장기 운영 flow, 테스트 7종. - -## Changed Files -- `trex_core/runtime/config.py` — `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None` + `from_toml` 파싱(<=0 거부) + `from_config` 에서 `SafetySellConfig` `dataclasses.replace` override(TOML 우선, env 폴백) -- `config/longterm.example.toml` (신규) — 중장기 표준 프로파일(instance_key=longterm, safety_sell_enabled=false, watchdog_timeout_sec=1800, max_active_slots=10) + 키별 중장기 의미 주석(forced_close_at 미사용, market-close env 미설정 포함) -- `docs/runtime-flows.md` — `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(6단계: 분리 기동/프로파일/보유 유지/보호 경계/forced_close_at 미사용/watchdog 의미) + Change Log -- `tests/runtime/test_watchdog_timeout_toml.py` (신규, 7 테스트) - -## Execution Log -1. `config.py`: `DaemonInstanceConfig` 에 optional 필드 추가, `from_toml` 에 기존 `max_active_slots` 검증 패턴으로 파싱+거부, `from_config` 에서 `inst.watchdog_timeout_sec is not None` 시 `safety_sell` replace — 소비자 2곳(slots RPC 핸들러 daemon.py:284, SafetySellService `_cfg`)이 모두 `config.safety_sell` 을 읽으므로 단일 지점으로 일치(design Key Decision 1). -2. `longterm.example.toml`: REQ-077 레시피 — 운영자 복제 사용 가능한 주석 포함. -3. `runtime-flows.md`: multi-instance 라우팅 섹션 뒤에 중장기 운영 flow 신설. -4. 테스트: TOML 적용(1800)/TOML>env 우선/env 폴백(300)/기본 120/인스턴스별 독립/<=0 거부/예시 프로파일 조합 로딩(longterm·false·1800·10). - -## Validation -- `pytest tests/runtime/test_watchdog_timeout_toml.py` — 7 passed (신규) -- `pytest tests/` — **446 passed, 46 skipped** (BAT-040 439 → +7, 회귀 0 — NFR-1 당일 무회귀 게이트 green) -- `ruff check trex_core tests` — All checks passed -- instance 격리(NFR-2): 인스턴스별 독립 timeout 테스트 + 기존 instance topology 테스트 회귀 green. - -## Remaining Risks -- 없음. R-1(소비자 불일치)=단일 지점 override 로 제거, R-2(frozen)=`replace` 사용, R-3(장기 run 재현)=설정·기동 구성 단위 검증 + BAT-036 evidence 재참조, 실 데몬 장기 운영 검증은 release 단계 위임(verification 명시). - -## Out-of-scope Finding (별도 후속) -- **pre-existing bug 발견**: `DaemonInstanceConfig.from_toml` 은 `default_order_timeout_sec` 를 파싱하지만 `DaemonConfig.from_config` 의 `replace(...)` 에 전파 누락 — TOML 의 `default_order_timeout_sec` 가 런타임에서 조용히 무시됨(daemon.py:156 은 `config.default_order_timeout_sec` 를 읽으나 항상 env-base 값). REQ-068/REQ-048 영역 — 본 batch 범위 밖, change-req 후속 필요. diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/index.md b/docs/batches/bat-041_20260612_long-term-instance-profile/index.md deleted file mode 100644 index 35fbe21..0000000 --- a/docs/batches/bat-041_20260612_long-term-instance-profile/index.md +++ /dev/null @@ -1,33 +0,0 @@ -# BAT-041: 중장기 인스턴스 운영 — watchdog per-instance TOML + 프로파일 + baseline (dcy-027) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-027 (issue #20 이월) -- Included REQ: REQ-076, REQ-077, REQ-078 -- Excluded REQ: 없음 - -## Summary -- 중장기 매매 전략용 인스턴스 운영 토대를 완성한다(dcy-027, 구현 위치 (a′) — 기존 슬롯 도메인 유지). watchdog timeout 을 per-instance TOML 로 노출하고(REQ-076, env 폴백), 중장기 표준 운영 프로파일 TOML 레시피를 제공·검증하며(REQ-077), 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱)와 forced_close_at 미사용을 baseline 문서에 명시한다(REQ-078). - -## Scope -- In Scope: - - `trex_core/runtime/config.py` — `DaemonInstanceConfig.watchdog_timeout_sec`(optional) + `from_toml` 파싱 + `from_config` 에서 `SafetySellConfig.watchdog_timeout_sec` override(TOML 우선, env 폴백) - - `config/longterm.example.toml` — 중장기 표준 프로파일 예시(키별 중장기 의미 주석) - - `docs/runtime-flows.md`(·필요 시 `docs/project-structure.md`) — 중장기 병행 운영 흐름 + 보호 규칙 경계 + forced_close_at 미사용 - - 테스트: TOML 로딩/env 폴백/인스턴스 독립, 프로파일 조합 기동 구성, instance 격리 회귀 -- Out of Scope: - - 멀티데이 보호 규칙 코어 구현 (dcy-027 OQ-2 — 전략 모듈 전적 책임, 코어 구현 없음) - - watchdog 소유권 진입 외부 알림(슬랙·이메일) — 후속 Discovery - - 중장기 전략 모듈 구현(별도 저장소) - - PositionBook 도메인 (OQ-1 미채택) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 코드 변경은 경량(config 1개 필드 + override 전파)이나, 런타임 설정 채널 + 운영 프로파일 + baseline 문서 정렬을 함께 포함하므로 standard profile. -- NFR-1(당일 무회귀)은 본 batch verification 의 전체 회귀 게이트, NFR-2(무간섭)는 REQ-077 AC-3, NFR-3(TOML 재사용)은 REQ-076 요구사항 제약으로 흡수(dcy-027). -- S-1 의 "일자 경계·재시작 보유 유지"는 영구 슬롯 모델(dcy-020, DB 영속)의 기존 보장 — 본 batch 는 프로파일 조합으로 자율 청산 미발화를 검증한다(설정 단위 + 기존 BAT-036 evidence 재참조). diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md b/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md deleted file mode 100644 index fa43a01..0000000 --- a/docs/batches/bat-041_20260612_long-term-instance-profile/planning.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-041 Planning - -- Status: confirmed -- Batch: BAT-041 -- Profile: standard - -## Included REQ -- REQ-076: watchdog timeout per-instance TOML(`watchdog_timeout_sec`, TOML 우선·env 폴백). AC 4. -- REQ-077: 중장기 운영 프로파일 — `config/longterm.example.toml` 레시피 + 기동 구성·병행 무간섭 검증. AC 4. -- REQ-078: baseline 정합 — 중장기 흐름 + 보호 규칙 경계 + forced_close_at 미사용. AC 3. - -## Delivery Plan -1. `config.py` — `DaemonInstanceConfig.watchdog_timeout_sec: Optional[int] = None` + `from_toml` 파싱(>0 검증) + `from_config` 에서 값 존재 시 `SafetySellConfig` override(`dataclasses.replace`). -2. `config/longterm.example.toml` — instance_key/safety_sell_enabled=false/watchdog_timeout_sec=1800/max_active_slots + 키별 중장기 의미 주석(forced_close_at 미사용 포함). -3. 테스트 — TOML 로딩(1800 적용)/미설정 env 폴백(120)/인스턴스별 독립/프로파일 조합 기동 구성(워커 미기동 + timeout 적용), instance 격리 회귀. -4. `docs/runtime-flows.md` — 중장기 병행 운영 흐름 섹션(토폴로지/프로파일/보호 경계/forced_close_at 미사용) + Change Log. - -## Dependencies -- Internal: - - `SafetySellConfig.watchdog_timeout_sec` 소비자 2곳 확인 완료 — slots RPC 핸들러(daemon.py:284), SafetySellService(`_cfg.watchdog_timeout_sec`). override 가 `config.safety_sell` 에 반영되면 두 경로 모두 자동 적용. - - 기존 TOML 패턴(`max_active_slots`/`default_order_timeout_sec`/`safety_sell_enabled`) 준용. - - BAT-036(safety_sell_enabled=false 워커 미기동) 기존 evidence — 프로파일 검증에서 재사용. -- External: 없음 (paper broker 로 검증, 라이브 불요). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: TOML→SafetySellConfig override 주입 지점(frozen dataclass replace), env 폴백 우선순위 의미, 프로파일 검증 범위(설정 단위 vs 실 데몬)를 design 에서 확정. - -## Milestones -1. Design confirmed — override 경로·검증 범위 확정 -2. Implementation — config + example TOML + 테스트 + baseline 문서 -3. Verification — REQ-076/077/078 AC 매핑 + 전체 회귀(NFR-1) + ruff - -## Risks -- R-1 (Low): override 가 SafetySellService 에는 적용되고 slots RPC 핸들러에 미적용(또는 역) — 완화: 단일 지점(`config.safety_sell`) override 로 두 소비자 자동 일치, 테스트로 확인. -- R-2 (Low): frozen dataclass 수정 시도 오류 — 완화: `dataclasses.replace` 사용. -- R-3 (Low): 일자 경계·재시작 시나리오를 실시간 재현 불가 — 완화: 영구 슬롯 DB 영속(dcy-020 기존 보장) + 자율 청산 미발화는 설정 단위 검증 + BAT-036 evidence 재참조로 충족(S-1 의 run-the-app 전수 재현은 release 운영 검증으로 위임, verification 에 명시). diff --git a/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md b/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md deleted file mode 100644 index f36c7e8..0000000 --- a/docs/batches/bat-041_20260612_long-term-instance-profile/verification.md +++ /dev/null @@ -1,48 +0,0 @@ -# BAT-041 Verification - -- Status: confirmed -- Batch: BAT-041 - -## Acceptance Mapping - -- REQ-076 AC-1 (TOML `watchdog_timeout_sec = 1800` → 1800s 동작): - - Evidence: `test_toml_watchdog_timeout_applied`(1800 적용) + `test_toml_wins_over_env`(env 300 무시, TOML 1800 승리) PASSED. -- REQ-076 AC-2 (미설정 → env 폴백, byte-for-byte 무회귀): - - Evidence: `test_absent_falls_back_to_env`(env 300 적용) + `test_absent_and_no_env_keeps_default_120` PASSED. 전체 회귀 446 passed(기존 인스턴스 경로 무회귀). -- REQ-076 AC-3 (인스턴스별 독립 timeout): - - Evidence: `test_instances_get_independent_timeouts` — day 120 / lt 1800 동시 분리 PASSED. -- REQ-076 AC-4 (저빈도 폴링 비오인 + last_seen 가시성): - - Evidence: timeout 1800s 적용 경로가 slots RPC 핸들러·SafetySellService 양쪽의 단일 소스(`config.safety_sell.watchdog_timeout_sec`, daemon.py:284 wiring 코드 리뷰)로 일치 — 600s 폴링 < 1800s timeout 으로 `is_expired` 산식상 비오인(WatchdogStore.is_expired 는 경과>timeout 비교). `last_seen` 노출 경로(slot_to_dict) 불변(기존 slots 핸들러 16 테스트 회귀 green). - -- REQ-077 AC-1 (프로파일 기동 구성 — 워커 미기동 + 1800 + 독립 큐): - - Evidence: `test_longterm_example_profile_loads_with_expected_combination` — instance_key=longterm, `safety_sell_enabled=False`, watchdog 1800, max_active_slots=10 PASSED. 워커 미기동(`safety_sell_enabled=false` → `trex-safety-sell` 스레드 제외 + WARNING)은 BAT-036 verification evidence 유효(본 batch 코드 경로 불변). per-instance 큐명은 dcy-009/BAT-013 evidence 유효. -- REQ-077 AC-2 (멀티데이 보유 유지 — 일자 경계·재시작): - - Evidence: 영구 자산 슬롯의 DB 영속·날짜 키 부재(dcy-020/BAT-027~030 verification — holding 이 재시작·일자 경계 통과)는 기존 evidence 유효. 본 프로파일은 자율 청산 트리거 전부 OFF(safety_sell_enabled=false + market-close env 미설정) 조합 — 설정 단위 검증 PASSED. **실 데몬 장기 run(수일) 검증은 release 운영 단계로 위임**(planning R-3 명시) — 코드 경로는 기존 검증 조합의 재구성이므로 release-ready 차단 아님. -- REQ-077 AC-3 (당일·중장기 병행 무간섭): - - Evidence: `test_instances_get_independent_timeouts`(설정 독립) + 기존 instance topology/스코프 격리 테스트 회귀 green(446 passed — DB instance_key 스코프·per-instance 큐 기존 evidence 유효). -- REQ-077 AC-4 (예시 파일 — 표준 레시피 + 의미 주석): - - Evidence: `config/longterm.example.toml` — 키별 중장기 의미 주석(safety_sell off 이유, 1800s 근거, forced_close_at 미사용, market-close env 미설정) 포함, 예시 로딩 테스트로 유효성 보증. - -- REQ-078 AC-1 (runtime-flows 중장기 흐름 — 토폴로지/프로파일/보호 경계 정합): - - Evidence: `docs/runtime-flows.md` `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(6단계) + Change Log — 현행 코드(BAT-036 워커 미기동, REQ-076 override 경로)와 정합 확인. -- REQ-078 AC-2 (forced_close_at 미사용 명시): - - Evidence: runtime-flows flow 5단계("의미 없다 — 집행 주체 OFF, 발화 불가") + longterm.example.toml 주석 명시. -- REQ-078 AC-3 (운영자 판단 가능성 — 보호장치 부재·책임 주체 명확): - - Evidence: flow 4단계 — "코어 손절매 루틴 일절 미사용 / 모든 보호 규칙 전략 모듈 전적 책임 / 유일한 백스톱 = 운영자 CLI exit" 모호함 없이 명시. - -## Evidence - -- Test: - - `pytest tests/runtime/test_watchdog_timeout_toml.py` — 7 passed (신규) - - `pytest tests/` — **446 passed, 46 skipped** (BAT-040 439 → +7, 회귀 0 — dcy-027 NFR-1 당일 무회귀 게이트 green) - - `ruff check trex_core tests` — All checks passed -- Manual / Baseline 동기화 (Architecture Impact: runtime-flows): - - `docs/runtime-flows.md` 중장기 운영 flow 신설 + Change Log ✓ 코드 정합. - - `docs/project-structure.md`: 변경 불필요(design Reference Doc Update Plan — 구조 불변, config 필드 1개 + 예시 파일). - - override 단일 지점(daemon.py:284 + SafetySellService) 코드 리뷰 확인. - -## Result -- Ready for Release: true -- Blocking Issues: 없음 - - REQ-077 AC-2 의 실 데몬 장기 run(수일) 검증은 release 운영 단계 사항 — 구성 요소별 기존 evidence(영구 슬롯 영속 BAT-027~030, 워커 미기동 BAT-036) + 프로파일 조합 설정 검증으로 release-ready 충족. verification 한계로 명시. - - out-of-scope finding(`default_order_timeout_sec` TOML 전파 누락, implementation.md 기록)은 본 batch REQ 와 무관한 pre-existing — 별도 change-req 후속. diff --git a/docs/batches/bat-042_20260615_slot-strategy-state-store/design.md b/docs/batches/bat-042_20260615_slot-strategy-state-store/design.md deleted file mode 100644 index 50b7d1f..0000000 --- a/docs/batches/bat-042_20260615_slot-strategy-state-store/design.md +++ /dev/null @@ -1,49 +0,0 @@ -# BAT-042 Design - -- Status: confirmed -- Batch: BAT-042 - -## Architecture Summary -- strategy-state 는 슬롯 본체와 분리된 저장 관심사다. 별도 `slot_strategy_state` 테이블 + `StrategyStateStore` 포트로 격리하고, 슬롯 행에는 정합성 가드용 `occupancy_seq` 컬럼 1개만 추가한다(full 3계층 분할은 후속). read 는 기존 `trex.slots.req` 에 piggyback(추가 왕복 0), write 는 신규 RPC `trex.slots.strategy_state.set` 로 분리(evaluate 순수성·재시도 격리). 점유 전환 ABA 는 write CAS 의 occupancy_seq 검증으로 구조적으로 차단한다. trex-core 는 `data` 를 해석하지 않는다(opaque UTF-8 + size cap 만 검증). - -## Changed Areas -- Docs: docs/runtime-flows.md, docs/project-structure.md, docs/interface-contract.md, docs/data-model.md (REQ-082) -- Modules: - - `trex_core/domain/slot/models.py` — `Slot.occupancy_seq: int = 0` + EXPECTED_SLOT_FIELDS - - `trex_core/domain/slot/service.py` — `inactive→pending_buy` 시 occupancy_seq++ / `→INACTIVE` 시 StrategyStateStore.delete(slot_id) 훅 - - 신규 `trex_core/application/strategy_state.py`(또는 ports.py 확장) — `StrategyStateStore` Protocol + `InMemoryStrategyStateStore` - - `trex_core/adapters/persistence/postgresql.py` — `PgStrategyStateStore` + 마이그레이션 SQL(occupancy_seq 컬럼, slot_strategy_state 테이블) - - `trex_core/adapters/messaging/topology.py` — `STRATEGY_STATE_SET_QUEUE` + per-instance queue helper - - `trex_core/application/messaging_handlers.py` — set 핸들러(store 위임) + slots 핸들러 strategy_state batched 조립 - - `trex_core/application/portfolio.py` — slot_to_dict 에 occupancy_seq + strategy_state 맵 - - `trex_core/runtime/daemon.py` — store 생성/와이어링 + set 핸들러 consumer -- Interfaces: `trex.slots.strategy_state.set` RPC(신규), `trex.slots.req` 응답 행 additive 확장 - -## Key Decisions -1. **저장 = 별도 테이블 + slot_id 키 + 맵**(dcy-028 OQ-4): `slot_strategy_state(slot_id, strategy_id, occupancy_seq, version, data, updated_at)` 복합 PK `(slot_id, strategy_id)`. JSONB-on-row 기각. occupancy_seq 는 행에 함께 저장(write 시 슬롯 현재값과 비교용 스냅샷이 아니라, store CAS 가 슬롯 현재 occupancy_seq 를 인자로 받아 비교). -2. **CAS 의미**(REQ-079/080): `set(slot_id, strategy_id, expected_occupancy_seq, expected_version, data, *, current_occupancy_seq, slot_active)` → - - slot_active=False → `slot_inactive` - - current_occupancy_seq ≠ expected_occupancy_seq → `occupancy_changed` - - size 초과 → `state_too_large` - - stored version ≠ expected_version → `version_conflict` - - 일치(없으면 expected_version=0) → commit, version+1. -3. **occupancy_seq bump 지점**(REQ-081): 슬롯이 `inactive→pending_buy` 로 전이할 때 ++. 한 점유 구간(pending_buy→holding→pending_sell) 동안 불변. 실패 매수 롤백(pending_buy→inactive)으로 번호가 건너뛰어도 monotonic 보장이면 무방. -4. **close 삭제 훅**(REQ-081/C-3): `service.py` 의 `→INACTIVE` 단일 분기(position 필드 clear 위치)에서 store.delete(slot_id). 모든 close 경로(전략 매도/safety-sell/forced-close/operator) 자동 커버. 행 삭제 아님. -5. **read 비대칭**(OQ-2): read 는 slots 핸들러가 반환 slot_id 들로 store.get_many(slot_ids) batched 조회 → group by → 행별 맵 조립. write 만 별도 RPC. occupancy_seq 는 슬롯 행 컬럼이라 slot_to_dict 에서 직접 노출. -6. **opaque**(OQ-1): data 는 UTF-8 str, core 무해석. size cap byte 기준 — 기본 상한 상수(예: `_STATE_CAP_BYTES`), 환경변수 override 여지(REQ 단계 확정). 기본 2KB, ceiling 4KB → 단일 cap 4KB 로 구현하고 1~2KB 권장은 문서 가이드(운영). 구현 cap = 4KB(byte) hard. - -## Edge Cases -- first-create 시 expected_version=0 + occupancy_seq 일치 → version=1. occupancy 불일치면 first-create 도 `occupancy_changed`. -- 동일 correlation_id 재전송 → 동일 expected_version 이라 commit 후 재요청은 version 이 이미 +1 → `version_conflict`(멱등적으로 무부작용). 또는 store가 동일 (occ,ver,data) 재적용을 무시. -- close 직후 inactive 슬롯에 write → `slot_inactive`(저장 안 함, 부활 차단). -- occupancy_seq 오버플로우: int(8 byte PG bigint 권장) — 실질 무한. -- 빈 strategy_state 슬롯 read → 행에 strategy_state omit/`{}`. -- in-memory store: dict[(slot_id, strategy_id)] = (occupancy_seq, version, data). 슬롯 current occupancy_seq 는 slot repo 에서 조회. - -## Architecture Impact -- both (project-structure + runtime-flows) - -## Reference Doc Update Plan -- project-structure.md: `slot_strategy_state` 테이블·`StrategyStateStore`(포트+PG/in-memory) 위치, slot `occupancy_seq` 컬럼 추가 반영 (REQ-082). -- runtime-flows.md: strategy-state read piggyback / write RPC / occupancy_seq·version CAS / 점유 가드(occupancy_changed) / close 삭제 흐름 신설 (REQ-082). -- interface-contract.md: `trex.slots.strategy_state.set` 계약(요청/응답/reject reason) + slots.req 행의 strategy_state·occupancy_seq 필드 (REQ-082). diff --git a/docs/batches/bat-042_20260615_slot-strategy-state-store/implementation.md b/docs/batches/bat-042_20260615_slot-strategy-state-store/implementation.md deleted file mode 100644 index a6bd36f..0000000 --- a/docs/batches/bat-042_20260615_slot-strategy-state-store/implementation.md +++ /dev/null @@ -1,39 +0,0 @@ -# BAT-042 Implementation - -- Status: completed -- Batch: BAT-042 - -## Plan Summary -- M1~M6 순서로 구현. in-memory store 우선 + PG 구현 병행, 각 단계 테스트 동반. paper/in-memory 로 무중단(live DB 비대상). - -## Changed Files -- `trex_core/domain/slot/models.py` — `Slot.occupancy_seq: int = 0` + `EXPECTED_SLOT_FIELDS`에 `occupancy_seq` (M1) -- `trex_core/domain/slot/service.py` — `inactive→pending_buy` 시 occupancy_seq++ / `→INACTIVE` 시 `strategy_state_store.delete_all(slot_id)` 훅 + 생성자 `strategy_state_store` 주입 (M1/M3) -- `trex_core/application/strategy_state.py` (신규) — `StrategyStateStore` Protocol, `StrategyStateEntry`/`SetResult`, `_cas` 공용 의사결정, `InMemoryStrategyStateStore`, reject reason 상수, size cap (M2) -- `trex_core/adapters/persistence/postgresql.py` — `_SLOT_COLUMNS`/update/`_row_to_slot`에 occupancy_seq + `PgStrategyStateStore`(SELECT FOR UPDATE CAS, batched get, delete) (M1/M2) -- `trex_core/adapters/persistence/sql/014_add_slot_occupancy_seq.sql`, `015_create_slot_strategy_state.sql` (신규 마이그레이션) (M1/M2) -- `trex_core/application/portfolio.py` — `slot_to_dict`에 `occupancy_seq` 노출 (M5) -- `trex_core/application/messaging_handlers.py` — slots 핸들러 strategy_state batched 조립(`strategy_state_store` 파라미터) + `make_strategy_state_set_handler`(write RPC, reject reason) (M4/M5) -- `trex_core/adapters/messaging/topology.py` — `STRATEGY_STATE_SET_QUEUE` + `strategy_state_set_queue()` (M4) -- `trex_core/adapters/messaging/rabbitmq.py` — declare_topology 에 strategy_state_set 큐 선언/바인딩 (M4) -- `trex_core/runtime/daemon.py` — `PgStrategyStateStore` 생성, SlotService 주입, slots 핸들러 wiring, `_strategy_state_set_loop` consumer 스레드 (M3/M4/M5) -- `trex_core/runtime/reset.py` — `slot_strategy_state` truncate 목록 추가 -- 테스트: `tests/application/test_strategy_state_store.py`, `tests/application/test_strategy_state_set_handler.py`, `tests/application/test_slots_handler_strategy_state.py`, `tests/domain/slot/test_occupancy_seq_and_close_delete.py`, `tests/adapters/persistence/test_postgresql_repository.py`(PG 게이트 추가) - -## Execution Log -1. M1: Slot.occupancy_seq 필드 + EXPECTED_SLOT_FIELDS + service bump(inactive→pending_buy) + PG 컬럼/마이그레이션 014. -2. M2: strategy_state.py(포트+in-memory+_cas+cap) + PgStrategyStateStore + 마이그레이션 015. -3. M3: service `→INACTIVE` delete_all 훅 + 생성자 주입. -4. M4: topology 큐 + set 핸들러(store 위임) + rabbitmq declare + daemon consumer 스레드. -5. M5: slot_to_dict occupancy_seq + slots 핸들러 strategy_state batched 조립(occupancy 필터). -6. M6: baseline 문서(REQ-082)는 verification 직후 정합 — 코드 확정 후 별도 반영(별도 커밋). - -## Validation -- `TREX_BROKER=paper uv run pytest -q` → **479 passed, 47 skipped**(PG 게이트는 live DSN 없어 skip). 신규 33 테스트 포함(store CAS 6 케이스+ABA, occupancy_seq bump/close 삭제 모든 경로, set RPC reason 집합, slots read 조립+occupancy_seq+stale 필터). -- `uv run ruff check trex_core/ tests/` → All checks passed. -- 회귀: 기존 slots 핸들러/슬롯 서비스/계약(EXPECTED_SLOT_FIELDS) 테스트 전부 green. - -## Remaining Risks -- M6 baseline 문서(REQ-082)는 본 구현 직후 반영 예정 — 현재 미반영(verification 에서 추적). -- PG 경로(PgStrategyStateStore, 마이그레이션 014/015)는 live DSN 테스트가 CI 에서 skip — 게이트 테스트는 작성됨, 실 DB 검증은 release 운영 단계. -- 단일 writer 전제(dcy-028) — 다중 writer 동시성은 범위 밖. diff --git a/docs/batches/bat-042_20260615_slot-strategy-state-store/index.md b/docs/batches/bat-042_20260615_slot-strategy-state-store/index.md deleted file mode 100644 index 472546c..0000000 --- a/docs/batches/bat-042_20260615_slot-strategy-state-store/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-042: Slot strategy-state 저장소 — opaque state store + occupancy_seq 가드 (dcy-028) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-028 (issue #26 C-1/C-2/C-2b/C-3) -- Included REQ: REQ-079, REQ-080, REQ-081, REQ-082, REQ-073 (CHG-20260615-01), REQ-074 (CHG-20260615-01) -- Excluded REQ: 없음 - -## Summary -- trex-tsl 등 전략 데몬이 슬롯 점유 동안 누적하는 opaque 상태(예: TSL peak)를 슬롯 수명에 묶어 server-side 영속화하는 저장소를 완성한다. 별도 `slot_strategy_state` 테이블 + `StrategyStateStore`(REQ-079), write RPC `trex.slots.strategy_state.set`(REQ-080), 점유 전환 ABA를 막는 occupancy_seq 가드 + close 삭제 불변식(REQ-081), read piggyback(REQ-073/074 CHG — slots.req 행에 strategy_state 맵·occupancy_seq additive), baseline 문서 정합(REQ-082). - -## Scope -- In Scope: - - REQ-079: `slot_strategy_state` 테이블(복합 PK (slot_id, strategy_id)) + `StrategyStateStore` 포트 + PG/in-memory 구현 + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap. - - REQ-080: `trex.slots.strategy_state.set` write RPC — 요청/응답 shape, CAS, reject reason 집합(occupancy_changed/version_conflict/slot_inactive/state_too_large/bad_request), 멱등. - - REQ-081: `Slot.occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS + 마이그레이션 + `inactive→pending_buy` bump + `→INACTIVE` strategy_state 삭제 훅. - - REQ-073/074 CHG: slots.req 응답 행에 strategy_state 맵(batched 조립)·occupancy_seq additive 노출. - - REQ-082: runtime-flows/project-structure/interface-contract baseline 정합. -- Out of Scope: - - C-4(SellIntent/watchdog) 신규 구현 — 기존 계약 확인(issue #26 답변 완료). - - full 3계층 슬롯 구조 분할(occupancy_id 키 전환) + daily_slot 네이밍 + daily_symbol_entries 잔재 제거 — 후속 "영구 슬롯 구조 정합" Discovery. - - 다중 writer 동시성 제어(단일 writer 전제), strategy_state 장기 보존/감사. - - optional batch set RPC 변형. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 단일 source discovery(dcy-028). 신규 테이블·도메인 필드·RPC·마이그레이션 + 기존 read 표면 확장(REQ-073/074 CHG) + baseline 정합 복합 → standard profile. -- REQ-073/074 는 CHG 로 Implemented→Approved 되돌림(additive AC, 기존 동작 보존). 본 batch 에서 신규 AC(073-AC7/074-AC6) 구현·검증 후 confirm-req-implemented 로 Implemented 복귀. -- NFR-1(opaque)은 FR 흡수, NFR-2(size cap)는 REQ-079, NFR-3(수명)은 REQ-081 로 흡수(dcy-028). -- 보안: 테스트는 TREX_BROKER=paper + in-memory store 로 무중단. live DB 비대상. diff --git a/docs/batches/bat-042_20260615_slot-strategy-state-store/planning.md b/docs/batches/bat-042_20260615_slot-strategy-state-store/planning.md deleted file mode 100644 index 6e77921..0000000 --- a/docs/batches/bat-042_20260615_slot-strategy-state-store/planning.md +++ /dev/null @@ -1,48 +0,0 @@ -# BAT-042 Planning - -- Status: confirmed -- Batch: BAT-042 -- Profile: standard - -## Included REQ -- REQ-079 (Data) — slot_strategy_state 저장소 + StrategyStateStore + CAS -- REQ-080 (Interface) — trex.slots.strategy_state.set write RPC -- REQ-081 (Data) — occupancy_seq 가드 + close 삭제 불변식 -- REQ-082 (Documentation) — baseline 정합 -- REQ-073 (Interface, CHG-20260615-01) — slots.req 응답 strategy_state·occupancy_seq additive -- REQ-074 (Data, CHG-20260615-01) — 슬롯 행 스키마 strategy_state·occupancy_seq additive - -## Delivery Plan -- 구현 순서(의존 기준): - 1. **M1 — 슬롯 occupancy_seq (REQ-081 일부)**: `Slot.occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS + PG 마이그레이션 SQL + `inactive→pending_buy` bump(service.py). 가장 하부 — 이후 CAS·노출의 선행. - 2. **M2 — strategy-state 저장소 (REQ-079)**: `StrategyStateStore` 포트 + in-memory 구현 + PG 구현 + 마이그레이션(`slot_strategy_state` 테이블) + occupancy_seq·version CAS + opaque UTF-8 + size cap. - 3. **M3 — close 삭제 훅 (REQ-081 나머지)**: `→INACTIVE` 전이 시 store.delete(slot_id) 훅(service.py 단일 지점) + 데몬 와이어링. - 4. **M4 — write RPC (REQ-080)**: topology 큐 + set 핸들러(store 위임) + daemon 와이어링 + reject reason. - 5. **M5 — read piggyback (REQ-073/074 CHG)**: slot_to_dict 에 occupancy_seq + slots 핸들러 strategy_state batched 조립. - 6. **M6 — baseline 문서 (REQ-082)**: runtime-flows/project-structure/interface-contract 정합(코드 확정 후). -- 각 M 마다 테스트 추가(TDD 우선 권장). in-memory store 로 paper/테스트 무중단. - -## Dependencies -- Internal: - - 슬롯 도메인(`models.py`/`service.py`/`state.py`), 슬롯 영속화(`postgresql.py`/in_memory_repository), 메시징(`topology.py`/`messaging_handlers.py`/`daemon.py`), `portfolio.py`(slot_to_dict). - - dcy-020 영구 슬롯 모델(→INACTIVE 전이), REQ-073/074 기존 read 표면. -- External: - - 없음(전략 데몬 trex-tsl 은 별도 저장소 소비자 — 본 batch 는 core 계약만). - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 신규 테이블 스키마 + 신규 도메인 필드(occupancy_seq) + 신규 RPC + 기존 read 표면(REQ-073/074) 확장 + CAS 동시성 의미가 얽혀 있어, 구현 전 인터페이스·스키마·전이 훅 경계를 design.md 로 고정해야 drift/재작업을 줄인다. runtime-flows baseline 영향도 있음. - -## Milestones -1. M1 occupancy_seq 필드+bump+마이그레이션 -2. M2 StrategyStateStore + CAS (in-memory+PG) -3. M3 close 삭제 훅 -4. M4 write RPC -5. M5 read piggyback(073/074) -6. M6 baseline 문서 - -## Risks -- 점유 전환 ABA(R-1, dcy-028): occupancy_seq CAS 로 차단 — M1+M2 에서 ABA 테스트(S-4) 필수. -- 기존 read 표면 회귀(REQ-073/074): additive 만 — 기존 키 집합/filter/spy/divergence 회귀 테스트로 보강. -- PG 마이그레이션 2건(occupancy_seq 컬럼 + slot_strategy_state 테이블): idempotent(IF NOT EXISTS) + in-memory 병행으로 paper 무중단. live DB 비대상(테스트는 trex_core_test DSN). -- EXPECTED_SLOT_FIELDS 계약 가드(test_contract): occupancy_seq 추가 시 계약 테스트 동반 갱신 필요. diff --git a/docs/batches/bat-042_20260615_slot-strategy-state-store/verification.md b/docs/batches/bat-042_20260615_slot-strategy-state-store/verification.md deleted file mode 100644 index 89c4bdb..0000000 --- a/docs/batches/bat-042_20260615_slot-strategy-state-store/verification.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-042 Verification - -- Status: confirmed -- Batch: BAT-042 - -## Acceptance Mapping -- REQ-079 (저장소+CAS): - - AC-1 first-create / AC-2 version commit·conflict / AC-3 occupancy·inactive reject / AC-4 size cap / AC-5 batched 조회 그룹화 / AC-6 PG·in-memory 동등 → `tests/application/test_strategy_state_store.py`(TestCas, TestBatchedRead, TestDelete) + `test_postgresql_repository.py::test_strategy_state_store_pg`(PG 게이트). -- REQ-080 (write RPC): - - AC-1 큐(declare_topology, rabbitmq) / AC-2 성공 version+1 / AC-3 occupancy·version reject / AC-4 inactive·size·bad_request / AC-5 멱등 / AC-6 reason 집합 → `tests/application/test_strategy_state_set_handler.py`(8 케이스 + reason 집합). -- REQ-081 (occupancy_seq + close 삭제): - - AC-1 계약 가드(EXPECTED_SLOT_FIELDS, `test_contract` green) / AC-2 bump·안정성 / AC-3 모든 close 경로 삭제 / AC-4 재사용 빈 시작 / AC-5 ABA reject → `tests/domain/slot/test_occupancy_seq_and_close_delete.py` + store ABA(`test_strategy_state_store.py::TestAbaGuard`). -- REQ-082 (baseline): - - AC-1 runtime-flows / AC-2 project-structure / AC-3 interface-contract / AC-4 경계 명시 → `docs/runtime-flows.md`(slot strategy-state 흐름 + Change Log), `docs/project-structure.md`(strategy_state.py 책임), `docs/interface-contract.md`(set RPC 계약 + slots.req additive + Change Log). -- REQ-073 (AC-7, CHG): - - 행에 strategy_state 맵(보유/미보유)·occupancy_seq, batched 조립, strategy_* reasoning 부재 → `tests/application/test_slots_handler_strategy_state.py` + 기존 `test_slots_handler.py` 회귀 green. -- REQ-074 (AC-6, CHG): - - 행 strategy_state 맵·occupancy_seq 필드 노출 → `tests/application/test_slots_handler_strategy_state.py`(row 단언). - -## Evidence -- Test: - - `TREX_BROKER=paper uv run pytest -q` → **479 passed, 47 skipped**(PG 게이트 live DSN 없어 skip). 신규 33 테스트(store CAS 6+ABA, occupancy_seq bump/close 삭제 4경로, set RPC 9, slots read 조립 5). - - 성공 기준 매핑: S-1=slots read 조립 테스트, S-2=set 핸들러/스토어 CAS 케이스, S-3=close 삭제 모든 경로, S-4=ABA reject(store+handler). -- Manual: - - 없음(전부 자동 테스트로 커버). 실 PG 경로는 게이트 테스트 작성 + release 운영 검증 위임. -- Logs: - - ruff: `uv run ruff check trex_core/ tests/` → All checks passed. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. (PG 마이그레이션 014/015·PgStrategyStateStore 의 실 DB 검증은 release 운영 단계 — 게이트 테스트는 작성됨.) diff --git a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/implementation.md b/docs/batches/bat-043_20260615_sell-intent-retryable-flag/implementation.md deleted file mode 100644 index d6a1382..0000000 --- a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/implementation.md +++ /dev/null @@ -1,26 +0,0 @@ -# BAT-043 Implementation - -- Status: completed -- Batch: BAT-043 - -## Plan Summary -- rejected SellIntent 응답에 additive `retryable: bool` 추가 (core 소유 매핑). 완전 additive·하위호환. - -## Changed Files -- `trex_core/application/messaging_handlers.py` — `_SELL_REJECT_RETRYABLE` dict + `_sell_reject_retryable(reason)` helper(미인식 true, `bad_request*` false) + intent 응답 빌더에 `intent_type=="sell"` & `status=="rejected"` 시 `retryable` 부여(정상 거부 경로 + sell bad_request 조기반환 경로 모두). -- `docs/interface-contract.md` — intent RPC 응답 표에 rejected sell `retryable` 명시 + Change Log. -- `tests/application/test_sell_retryable_flag.py` (신규) — 매핑 7 reason + 미인식 + None + bad_request + 응답 필드(inactive/unknown_slot/bad_request → false, admitted/buy → 부재). - -## Execution Log -1. `_SELL_REJECT_RETRYABLE` 매핑 + helper 추가(messaging_handlers). -2. 응답 빌더 2지점(정상 결정 / sell bad_request)에 additive 부여. -3. interface-contract 정합 + 테스트 작성. - -## Validation -- `TREX_BROKER=paper uv run pytest -q` → **487 passed, 47 skipped**(BAT-042 479 + 본 batch 8). 신규 8: 매핑 3 + 응답 5. -- `uv run ruff check trex_core/ tests/` → All checks passed. -- 회귀: 기존 intent consumer/handler 테스트 green(admitted/buy 응답 retryable 부재 확인 포함). - -## Remaining Risks -- reason 어휘 drift: 새 reason 추가 시 매핑 갱신 필요(미인식 기본 true 안전망 + 매핑 테스트가 현행 reason 전수 커버). -- 없음(완전 additive, 기존 계약 불변). diff --git a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/index.md b/docs/batches/bat-043_20260615_sell-intent-retryable-flag/index.md deleted file mode 100644 index 6aaa1b9..0000000 --- a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# BAT-043: rejected SellIntent `retryable` 플래그 (issue #27) - -- Status: release-candidate -- Profile: batch-lite -- Source Discovery: 없음 (REQ-007 change-req, issue #27) -- Included REQ: REQ-007 (CHG-20260615-01) -- Excluded REQ: 없음 - -## Summary -- rejected SellIntent 응답(`trex.intent.req`, `intent_type:"sell"`, `status:"rejected"`)에 additive `retryable: bool` 을 추가한다. core 가 소유한 reason→retryable 매핑으로 채우고 `reason` 은 유지한다. trex-tsl 데몬이 손절매 재시도 판단을 reason 문자열 하드코딩 대신 권위 플래그로 하게 해 안전 경로 결합을 제거한다(issue #27, #26 sell-idempotency 후속). - -## Scope -- In Scope: - - `trex_core/application/messaging_handlers.py` — intent 응답 빌더에 sell+rejected 시 `retryable` 부여 + core 소유 `_SELL_REJECT_RETRYABLE` 매핑(미인식 기본 true). - - `docs/interface-contract.md` — rejected sell 응답 `retryable` 필드 명시. - - 테스트: reason→retryable 매핑 케이스, 미인식 기본 true, admitted/processing/buy 부재, 기존 reason 유지. -- Out of Scope: - - reason 어휘 자체 변경(매핑만 추가). - - buy intent / admitted / processing 응답 변경. - - dcy-028(strategy-state) — 독립. - -## Documents -- Planning: [planning.md](./planning.md) -- Design: none (batch-lite, additive 단일 필드 — 구조/흐름 영향 없음) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 완전 additive·하위호환: 필드 부재(older core) → 데몬 reason-string 폴백. 기존 응답/admit/buy 불변이라 회귀 리스크 낮음. -- 안전 기본값: 미인식 reason → `retryable: true`(멈춘 손절매가 hammering 보다 위험, issue #27 답변에서 확정). -- `bad_request:*`(malformed) → false(클라이언트 결함, 같은 바이트 재시도 무의미). diff --git a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/planning.md b/docs/batches/bat-043_20260615_sell-intent-retryable-flag/planning.md deleted file mode 100644 index b8bef38..0000000 --- a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/planning.md +++ /dev/null @@ -1,29 +0,0 @@ -# BAT-043 Planning - -- Status: confirmed -- Batch: BAT-043 -- Profile: batch-lite - -## Included REQ -- REQ-007 (CHG-20260615-01) — rejected SellIntent 응답 additive `retryable: bool` - -## Delivery Plan -1. `messaging_handlers.py` 에 core 소유 `_SELL_REJECT_RETRYABLE` 매핑 + helper(`_sell_retryable(reason)` — 미인식 기본 true, `bad_request:*`/terminal → false). -2. intent 응답 빌더: `intent_type=="sell"` 且 `status=="rejected"` 일 때만 `retryable` 부여. bad_request 조기반환 경로(sell 식별 가능 시)도 동일 적용. -3. 테스트 추가 + interface-contract 문서 정합. - -## Dependencies -- Internal: `messaging_handlers.make_intent_rpc_handler`(응답 빌더), `intents.py`(reason 어휘 — _admit_sell/_dedup). -- External: trex-tsl 데몬(소비자, 별도 저장소) — 폴백 보유로 lockstep 불요. - -## Design Gate -- Design Required Before Implementation: no -- Reason: 단일 additive 응답 필드 + 순수 매핑(dict). 구조/런타임 흐름/스키마 영향 없음. 계약 변화는 interface-contract 한 줄. design.md 불요(batch-lite). - -## Milestones -1. 매핑 + helper + 응답 빌더 -2. 테스트 + 문서 - -## Risks -- reason 어휘 drift: 매핑이 core 에 있어 새 reason 추가 시 함께 갱신(미인식 기본 true 가 안전망). — 매핑 테스트로 현행 reason 전수 커버. -- bad_request 경로: sell 여부 판별 가능 지점에서만 retryable=false 부여(불명확하면 미부여 — 데몬 폴백). diff --git a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/verification.md b/docs/batches/bat-043_20260615_sell-intent-retryable-flag/verification.md deleted file mode 100644 index 56617a4..0000000 --- a/docs/batches/bat-043_20260615_sell-intent-retryable-flag/verification.md +++ /dev/null @@ -1,23 +0,0 @@ -# BAT-043 Verification - -- Status: confirmed -- Batch: BAT-043 - -## Acceptance Mapping -- REQ-007 (AC-7, CHG-20260615-01): - - rejected sell 응답에 additive `retryable` + core 소유 매핑 + `reason` 유지 → `tests/application/test_sell_retryable_flag.py::TestResponseField`(inactive→false, unknown_slot→false, bad_request→false). - - core 소유 매핑(7 reason) + 미인식 기본 true + bad_request false → `TestRetryableMapping`. - - 완전 additive(admitted/processing/buy·기존 필드 불변) → `test_admitted_sell_has_no_retryable`, `test_buy_rejection_has_no_retryable` + 기존 intent consumer 회귀. - -## Evidence -- Test: - - `TREX_BROKER=paper uv run pytest -q` → **487 passed, 47 skipped**. 신규 8(매핑 3 + 응답 5). -- Manual: - - 없음(자동 테스트 커버). -- Logs: - - ruff: All checks passed. - -## Result -- Ready for Release: true -- Blocking Issues: - - 없음. diff --git a/docs/batches/bat-044_20260616_slippage-tick-size-snapping/design.md b/docs/batches/bat-044_20260616_slippage-tick-size-snapping/design.md deleted file mode 100644 index b5a6997..0000000 --- a/docs/batches/bat-044_20260616_slippage-tick-size-snapping/design.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-044 Design - -- Status: confirmed -- Batch: BAT-044 - -## Architecture Summary -- 가격→호가단위 변환을 순수 도메인 함수(`trex_core/domain/pricing.py`)로 분리하고, `execution.py`의 dispatch-time 슬리피지 산출이 이를 소비해 제출 직전 유효 호가로 스냅한다. 도메인은 무의존 유지(application이 domain을 단방향 참조). - -## Changed Areas -- Docs: docs/runtime-flows.md(주문 집행 루프 슬리피지 스냅 단계), docs/project-structure.md(domain/pricing.py) -- Modules: trex_core/domain/pricing.py(신규), trex_core/application/execution.py(`_dispatch`) -- Interfaces: 외부 메시지/계약 변경 없음 — 제출되는 `OrderRequest.limit_price` 값만 유효 호가로 정정(관측 가능한 동작: 거부→체결). - -## Key Decisions -1. **반올림 방향 = 상한 준수**(매수 `floor_to_tick`, 매도 `ceil_to_tick`). `slippage_pct`를 리스크 상한으로 취급 → 스냅이 상한을 넘기지 않음. REQ-070 원래 메모("체결 보장=매수 상위/매도 하위")에서 전환(dcy-030 결정). -2. **band<1tick 보장**: 매수 = `max(floor_to_tick(raw), ceil_to_tick(base))`, 매도 = `min(ceil_to_tick(raw), floor_to_tick(base))`. slippage가 1틱 미만이어도 entry의 유효 호가에서 체결 가능(미체결 방지). on-tick entry(실거래 정상)에서는 guard가 no-op(floor 결과가 이미 ≥ entry). -3. **부동소수 가드**: `floor_to_tick`/`ceil_to_tick`에서 `q=price/tick`에 ±1e-9 epsilon. `1000*1.005=1004.9999…` 같은 잡음이 off-by-one을 일으키지 않게 함. q가 유계라 안전. -4. **유틸 위치 = domain**(순수 함수, 무의존). slot 도메인과 동급의 가격 도메인 모듈. -5. **주식 전용**: 단일 구간표 상수. 종목유형 분기 없음(universe에 유형 필드 부재, dcy-030 OQ-1). - -## Edge Cases -- 시장가(`limit_price=None`) 또는 `slippage_pct` 없음/0(falsy) → 스냅 미적용, 기존 동작(회귀 없음). -- 구간 경계(2,000/5,000/…/500,000) → 하한 포함 처리. -- ceil이 상위 구간으로 넘어가는 경우(예: 199,950→200,000) → 결과가 상위 구간 tick의 배수임(KRX 표가 중첩 배수라 항상 유효). -- 2,000원 미만 비주식(ETF 등) → 주식 tick(1원) 적용 시 무효 가능 → known limitation(미지원, dcy-030 OQ-1). - -## Architecture Impact -- both (project-structure: 신규 domain 모듈 / runtime-flows: 슬리피지 스냅 단계) - -## Reference Doc Update Plan -- project-structure.md: `domain/pricing.py` 모듈 1행 추가 — 완료. -- runtime-flows.md: 주문 집행 루프 슬리피지 보호 지정가 항목에 호가단위 스냅·방향·band None` (default no-op) 추가. 기존 구현(`InMemoryTransport`)에 no-op 추가. daemon에서 `self.event_publisher.keepalive_tick()` 직접 호출. 포트 인터페이스 추가는 transport 어댑터 내부이므로 wire 계약과 무관. -5. **`_publish_dlq` 내 keepalive**: DLQ 발행은 같은 channel에서 `_ensure_connected()` 후 수행. keepalive와 별도 경로이므로 간섭 없음. DLQ 발행은 no publisher-confirms이므로 block 없음. -6. **`close()` 후 `keepalive_tick()` 호출**: `_conn is None` 체크로 no-op. 데몬 shutdown 시 stop 이벤트 세팅 후 루프 종료 → keepalive 틱 호출 없음. - -## Architecture Impact - -- runtime-flows (발행 재시도/keepalive/DLQ 관측 경로 추가) -- project-structure (`keepalive_tick` 메서드, `MessagingTransport` 포트 확장 기술) - -## Reference Doc Update Plan (REQ-094) - -- `docs/runtime-flows.md`: - - `슬롯 이벤트 발행/구독` 흐름: 재시도/강제 재연결 경로, idle keepalive 동작, DLQ 유입 관측 경로 추가. - - `데몬 lifecycle`: keepalive 틱이 execution-loop 틱에 삽입됨(OQ-1 결정 반영). -- `docs/project-structure.md`: - - `messaging.py`(MessagingTransport 포트) 설명에 `keepalive_tick()` 추가. - - `rabbitmq.py` 설명에 발행 재시도·keepalive 기술 추가. -- `docs/operations.md`: - - 신규 섹션 "DLQ 가시성/경보": `trex.events.dlq` 유입 로그 확인 방법, RabbitMQ 관리 UI queue depth 조회, 운영자 조치 절차. diff --git a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/implementation.md b/docs/batches/bat-047_20260619_event-publisher-connection-reliability/implementation.md deleted file mode 100644 index ac953bd..0000000 --- a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/implementation.md +++ /dev/null @@ -1,63 +0,0 @@ -# BAT-047 Implementation - -- Status: completed -- Batch: BAT-047 - -## Plan Summary - -REQ-091~094 구현. 변경은 발행 경로에 국소화하고 wire 계약·consume/RPC 경로 불변 유지. - -1. `rabbitmq.py` — `_connect` 타임아웃 파라미터화, `_force_reconnect` 신규, `keepalive_tick` 신규, `publish_event` 재시도 로직, `_publish_dlq` ERROR 격상. -2. `application/messaging.py` — `MessagingTransport` 포트에 `keepalive_tick()` 추가 (no-op default). -3. `adapters/messaging/in_memory.py` — `keepalive_tick()` no-op 구현. -4. `runtime/daemon.py` — `_make_event_emitter` WARNING→ERROR 격상, `_execution_loop` keepalive 틱 삽입. -5. `tests/adapters/messaging/test_publish_reliability.py` — 신규 테스트 17건. -6. `docs/` — baseline 문서 정합 (REQ-094). - -## Changed Files - -### 코드 -- `trex_core/adapters/messaging/rabbitmq.py` - - `_connect(connect_timeout=None)`: 파라미터화, 기본값 `self._timeout` 유지 - - `_force_reconnect()`: 신규 — `close()` + `_connect(connect_timeout=2.0)` (NFR-2 5초 블록 예산) - - `keepalive_tick()`: 신규 — `process_data_events(time_limit=0)`, 비소유 스레드 호출 시 잠재적 오남용 방어(소유 스레드에서만 호출 규칙은 daemon.py에서 보장) - - `publish_event()`: 1차 실패 시 `_force_reconnect` + 재시도, `UnroutableError`는 재시도 없이 DLQ - - `_do_basic_publish()`: 신규 내부 헬퍼 — basic_publish 로직 추출(DRY) - - `_publish_dlq()`: WARNING → ERROR 2건(DLQ 유입 + DLQ 재발행 실패) -- `trex_core/application/messaging.py` - - `MessagingTransport` 프로토콜에 `keepalive_tick() -> None` 추가 -- `trex_core/adapters/messaging/in_memory.py` - - `InMemoryTransport.keepalive_tick()` no-op 구현 -- `trex_core/runtime/daemon.py` - - `_make_event_emitter`: WARNING → ERROR, routing key + slot_id + strategy_id 포함 - - `_execution_loop`: `self.event_publisher.keepalive_tick()` 각 틱 시작 시 호출 - -### 테스트 -- `tests/adapters/messaging/test_publish_reliability.py` (신규, 17 테스트) - -### 문서 (REQ-094) -- `docs/runtime-flows.md` — `슬롯 이벤트 발행/구독`, `데몬 lifecycle` 갱신 -- `docs/project-structure.md` — messaging.py, rabbitmq.py 기술 갱신 -- `docs/operations.md` — DLQ 가시성/경보 섹션 신규 - -## Execution Log - -1. `rabbitmq.py` — `_connect`, `_force_reconnect`, `keepalive_tick`, `publish_event`, `_do_basic_publish`, `_publish_dlq` 수정/추가. -2. `application/messaging.py` — `keepalive_tick` 포트 메서드 추가. -3. `in_memory.py` — no-op 추가. -4. `daemon.py` — `_make_event_emitter` + `_execution_loop` 수정. -5. 신규 테스트 17건 작성. -6. `python3 -m pytest tests/adapters/messaging/test_publish_reliability.py -v` → **17 passed**. -7. `python3 -m pytest tests/ --ignore=...rabbitmq_transport... --ignore=...rabbitmq_intent_e2e... -q` → **554 passed, 40 skipped** (기존 537 + 신규 17, 회귀 0). -8. baseline 문서 정합 (runtime-flows, project-structure, operations). - -## Validation (중간) - -- 신규 17건 전부 통과. -- 기존 537 passed → 554 passed (신규 17 추가). 회귀 0. -- `TREX_RABBITMQ_TEST_URL` 필요 통합 테스트 2건(test_rabbitmq_transport.py, test_rabbitmq_intent_e2e.py): 환경 없어 skip — 기존과 동일. - -## Remaining Risks - -- R-3(Medium): at-least-once 재시도 — 중복 이벤트 발행 가능. OQ-2(orb/tsl 소비자 멱등성)는 delivery 검증으로 추적. trex-core 범위 밖. -- R-live: 실제 pika 연결 환경(TREX_RABBITMQ_TEST_URL)에서의 keepalive/재연결 동작은 통합 테스트로만 검증 가능 — CI 환경 없어 미검증. unit mock 기반으로 logic을 검증했으나 실제 broker half-open 재현은 운영 환경 배포 후 확인 필요. diff --git a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/index.md b/docs/batches/bat-047_20260619_event-publisher-connection-reliability/index.md deleted file mode 100644 index 836ba35..0000000 --- a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/index.md +++ /dev/null @@ -1,36 +0,0 @@ -# BAT-047: 이벤트 발행 연결 신뢰성 — idle keepalive + 재시도/재연결 + DLQ 가시성 + baseline 정합 (OPS-005) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-031 -- Included REQ: REQ-091, REQ-092, REQ-093, REQ-094 -- Excluded REQ: 없음 - -## Summary -- OPS-005 운영 리포트 기반 발행 신뢰성 4건을 단일 batch로 묶는다. 발행 재시도+강제 재연결(REQ-091), idle keepalive(REQ-092), DLQ 가시성+emit ERROR 경보(REQ-093)는 같은 발행 경로(`publish_event`/`_publish_dlq`/`_make_event_emitter`)를 공유하고, 동일 messaging 검증 스위트에서 검증된다. baseline 문서 정합(REQ-094)은 구현 완료 후 같은 batch에서 처리한다. -- M2(trex-tsl-v1 라이브, 6/28 목표) 선행 조건. idle 60초 후 첫 슬롯 전이 이벤트 유실 결함 제거. - -## Scope -- In Scope: - - `trex_core/adapters/messaging/rabbitmq.py` — `publish_event` 재시도+강제 재연결(REQ-091), keepalive `process_data_events` 틱(REQ-092), `_publish_dlq` ERROR 로그(REQ-093) - - `trex_core/runtime/daemon.py` — `_make_event_emitter` ERROR 경보 강화(REQ-093), keepalive 틱 연동(REQ-092) - - `tests/adapters/messaging/` — half-open 시뮬레이션, 재시도 소진, idle keepalive, DLQ 경보 신규 테스트 - - `tests/application/test_messaging_event_publisher.py` — 기존 회귀 유지 - - `docs/runtime-flows.md`, `docs/project-structure.md`, `docs/operations.md` — baseline 정합(REQ-094) -- Out of Scope: - - 이벤트 영속 outbox(옵션 C) — M3 후보 - - consumer(orb/tsl) idempotency 구현 - - RPC client(`call_rpc`) 연결 정책 - - `docs/interface-contract.md` 변경 — AMQP wire 계약 불변 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- R-1(High): M1 라이브 trex-core — 변경은 발행 경로에 국소화, wire 계약·consume/RPC 경로 불변. opus qc-reviewer 강제(dcy-031 §10 CONFIRM①). -- R-2(Medium): pika 스레드 비안전성 — keepalive I/O는 발행 연결 소유 스레드에서만 실행(OQ-1 설계 확정). -- R-3(Medium): 재시도 at-least-once 중복 가능 — OQ-2는 delivery 검증으로 추적(소비자 멱등성). -- NFR-2 타임아웃 수치는 design 단계에서 확정. diff --git a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/planning.md b/docs/batches/bat-047_20260619_event-publisher-connection-reliability/planning.md deleted file mode 100644 index 7e83341..0000000 --- a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/planning.md +++ /dev/null @@ -1,67 +0,0 @@ -# BAT-047 Planning - -- Status: confirmed -- Batch: BAT-047 -- Profile: standard - -## Included REQ - -- REQ-091: 이벤트 발행 재시도 + 강제 재연결 (idle 후 첫 발행 무유실) -- REQ-092: 발행 전용 연결 idle keepalive (서버 측 heartbeat 사망 예방) -- REQ-093: DLQ 가시성/경보 + emit 실패 경보 (trex.events.dlq 관측성) -- REQ-094: 이벤트 발행 신뢰성 baseline 문서 정합 (runtime-flows / project-structure / operations 갱신) - -## Delivery Plan - -구현 순서는 의존성과 위험도 기준으로 아래와 같다. - -1. **REQ-092 keepalive 설계 확정(OQ-1)**: draft-batch-design에서 pika 스레드 소유 모델 기준으로 keepalive 실행 위치(별도 스레드 vs. execution-loop 틱) 결정. 이 결정이 REQ-092 구현 전제. - -2. **REQ-093 DLQ 가시성 방식 결정(OQ-3)**: draft-batch-design에서 dcy-029 로깅 자산 재사용 가능성 검토 후 메트릭/로그/소비자 중 택1. 이 결정이 REQ-093 DLQ 유입 관측 구현 전제. - -3. **REQ-093 NFR-2 타임아웃 수치 확정**: design에서 단일 publish 경로 전체 블록 상한 수치 결정. - -4. **구현(run-batch-implementation)**: 설계 결정 순서대로 - - Phase A: `rabbitmq.py` `publish_event` 재시도+강제 재연결(REQ-091) + `_publish_dlq` ERROR 로그(REQ-093) - - Phase B: keepalive 구현(REQ-092) — OQ-1 결정 결과 적용 - - Phase C: `daemon.py` `_make_event_emitter` ERROR 경보 강화(REQ-093) - - Phase D: 신규 테스트 작성 (half-open/재시도/keepalive/DLQ/emit ERROR AC 커버) - -5. **REQ-094 baseline 문서 정합**: 구현 완료 후 `runtime-flows.md`, `project-structure.md`, `operations.md` 갱신. - -선행 조건: REQ-091~093은 코드 범위가 분리되어 있어 병렬 구현 가능하나, keepalive(REQ-092)와 emit 경보(REQ-093)는 OQ-1/OQ-3 설계 결정이 전제여서 설계 완료 후 시작. - -## Dependencies - -- Internal: - - `trex_core/adapters/messaging/rabbitmq.py` — 변경 대상 핵심 파일 (BAT-007에서 도입됨) - - `trex_core/runtime/daemon.py` — `_make_event_emitter` 변경 대상 - - `tests/adapters/messaging/` — 기존 messaging 회귀 스위트(현재 537 passed) 전체 통과 유지 필수 - - `tests/application/test_messaging_event_publisher.py` — 기존 회귀 유지 - - dcy-029(BAT-045/046) — 구조화 로깅 인프라 자산(OQ-3 재사용 검토 대상) -- External: - - pika BlockingConnection API(`process_data_events`, `close`, `BlockingConnection`) — 버전 고정 (pyproject.toml 기준) - - RabbitMQ broker — 통합 테스트는 `TREX_RABBITMQ_TEST_URL` skip(CI 환경 없으면 skip, unit은 mock 기반) - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: - - OQ-1(keepalive 실행 위치): pika 스레드 비안전성이 명시적 스레드 소유 모델 결정을 요구한다. 설계 없이 구현하면 다른 스레드에서 I/O 호출 시 연결 손상 리스크가 있다. - - OQ-3(DLQ 가시성 방식): 메트릭/로그/소비자 데몬 중 선택이 구현 구조를 결정한다. 특히 소비자 데몬 방식이면 별도 스레드/모듈이 추가된다. - - NFR-2(타임아웃 수치): execution-loop 블록 상한 수치가 코드에 상수로 박힌다. 설계에서 확정해야 AC-3 기준이 명확해진다. - - architecture impact가 `runtime-flows`(keepalive 틱, 재시도 경로)이므로 standard profile design이 필요하다. - -## Milestones - -1. draft-batch-design 완료 — OQ-1(keepalive 위치), OQ-3(DLQ 방식), NFR-2(타임아웃 수치) 확정 -2. run-batch-implementation 완료 — 신규 테스트 포함, 기존 회귀 537 passed 유지 -3. draft-batch-verification 완료 — AC별 커버 확인, baseline 문서 정합 확인 -4. QC reviewer 검증 대기 (confirm-batch-verification은 팀장 게이트) - -## Risks - -- R-1(High): M1 라이브 trex-core — wire 계약·consume/RPC 경로 회귀 시 orb(라이브)·tsl(M2)에 즉시 영향. 완화: 변경을 `publish_event`/`_publish_dlq`/`_make_event_emitter` 경로에 국소화. `declare_topology`/`consume_rpc_forever`/`consume_events_forever`/`call_rpc` 경로 불변. 기존 회귀 스위트 전체 통과 강제. -- R-2(Medium): pika 스레드 비안전성 — keepalive가 발행 전용 연결 소유 스레드 밖에서 실행되면 연결 손상. 완화: OQ-1을 design에서 확정, AC-2/AC-3(스레드 안전성) 테스트로 검증. -- R-3(Medium): 재시도 at-least-once — 중복 이벤트 발행 가능. 완화: OQ-2는 delivery 검증(소비자 orb/tsl 멱등성 확인)으로 추적. 전이 이벤트는 멱등 적용 가능 추정. -- R-4(Low): keepalive `process_data_events(time_limit=0)` 틱이 execution-loop와 같은 스레드에서 실행될 경우 루프 지연 유발 가능. 완화: NFR-2 타임아웃 수치를 design에서 확정, AC-4(블록 상한) 테스트로 검증. diff --git a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/verification.md b/docs/batches/bat-047_20260619_event-publisher-connection-reliability/verification.md deleted file mode 100644 index 256754d..0000000 --- a/docs/batches/bat-047_20260619_event-publisher-connection-reliability/verification.md +++ /dev/null @@ -1,131 +0,0 @@ -# BAT-047 Verification - -- Status: release-candidate -- Batch: BAT-047 -- Verified By: trex-core-dev (draft-batch-verification → QC PASS-WITH-NOTES 시정 후 재검증) -- Verified At (KST): 2026-06-19 (최초), 2026-06-19 (QC 시정 재검증) -- Confirmed By: 팀장 (독립 opus qc-reviewer PASS-WITH-NOTES + 시정 2건 완료 + 팀장 직접 재확인) - -## Human Approval Memo (팀장 confirm-batch-verification) -- 독립 opus qc-reviewer 판정 = PASS-WITH-NOTES. 반환 2건 모두 시정 완료: - - ① ruff F401 3건 제거 → 팀장 직접 재실행 `All checks passed!` 확인. - - ② REQ-093 AC-3 테스트가 프로덕션 `Daemon._make_event_emitter()`를 실제 호출하도록 재작성 → 팀장 코드 확인(L398 `object.__new__(Daemon)` → L404 `_make_event_emitter()` → L422 `emit(...)`). - - 팀장 직접 전체 재실행: **554 passed / 47 skipped / 0 실패** (회귀 0). -- 수용한 잔여 한계(blocker 아님, release 노트에 명시할 것): - - half-open / broker 재시작 / idle 60초 생존은 mock unit으로만 검증. 실 broker 통합(`TREX_RABBITMQ_TEST_URL`)은 환경 미설정으로 skip → M1 운영 또는 broker 포함 CI로 통합검증 위임. - - NFR-2 "publish 경로 블록 ≤5초"는 재연결(2.0s)+재시도(2.0s) 구간에 엄밀 성립. 1차 실패 시도가 원 socket_timeout(5.0s)에 묶이면 최악 케이스 초과 가능 → 필요 시 후속 REQ로 초기 publish timeout 강화. -- NFR-1(M1 라이브 무회귀): wire 계약·consume/RPC·토폴로지·`interface-contract.md`(diff 0) 불변 확인 — qc-reviewer 코드 추적 + 팀장 확인. - -## Test Run Summary - -> **정정 (QC PASS-WITH-NOTES 시정 후)**: 최초 verification.md에서 "ruff clean"으로 기재했으나, QC 독립 검증에서 ruff F401 3건(threading, unittest.mock.call, trex_core.runtime.daemon.Daemon)이 검출됐다. 또한 REQ-093 AC-3 테스트가 프로덕션 `_make_event_emitter` 경로를 호출하지 않고 emit 클로저를 테스트 본문에 직접 복제하는 구조였다. 이하는 시정 후 실제 재실행 결과다. - -### 시정 후 재실행 결과 - -``` -명령: python3 -m ruff check . -결과: All checks passed! -``` - -``` -명령: python3 -m pytest tests/ --ignore=tests/adapters/messaging/test_rabbitmq_transport.py \ - --ignore=tests/adapters/messaging/test_rabbitmq_intent_e2e.py -q -결과: 554 passed, 40 skipped in 1.51s -기존 baseline: 537 passed (BAT-046 기준) -신규 추가: 17 passed (tests/adapters/messaging/test_publish_reliability.py) -회귀: 0건 -``` - -## Acceptance Criteria Mapping - -### REQ-091: 이벤트 발행 재시도 + 강제 재연결 - -| AC | 내용 | Evidence | Result | -|---|---|---|---| -| AC-1 | half-open → 재연결+재시도 → 발행 성공 | `TestPublishRetryOnHalfOpen::test_half_open_first_attempt_fails_retry_succeeds` PASSED | PASS | -| AC-2 | 재시도 소진 → `TransportUnavailableError` 전파 | `TestPublishRetryExhausted::test_retry_exhausted_raises_transport_unavailable` PASSED | PASS | -| AC-3 | 타임아웃 상한 확인 (`connect_timeout=2.0`) | `TestForceReconnectTimeout::test_force_reconnect_uses_2s_timeout` PASSED | PASS | -| AC-4 | 정상 발행(연결 살아 있음) — 재시도 경로 미호출 | `TestNormalPublishNoRetry::test_normal_publish_does_not_call_force_reconnect` PASSED | PASS | -| AC-5 | consume/RPC 경로 무회귀 | `TestConsumePaths::test_rabbitmq_transport_has_consume_methods` PASSED + 전체 회귀 554 passed 0 회귀 | PASS | -| AC-6 | broker 재시작 후 발행 복구 | `TestBrokerRestartScenario::test_broker_restart_publish_recovers` PASSED | PASS | - -### REQ-092: 발행 전용 연결 idle keepalive - -| AC | 내용 | Evidence | Result | -|---|---|---|---| -| AC-1 | idle 후 연결 생존 (keepalive가 `process_data_events` 호출) | `TestKeepaliveTick::test_keepalive_tick_calls_process_data_events_on_live_connection` PASSED | PASS | -| AC-2 | 발행 소유 스레드에서만 I/O (design 결정: execution-loop 틱에 삽입) | OQ-1 design 문서에 Answered 기록. daemon.py `_execution_loop` 내 `keepalive_tick()` 호출 코드 diff 확인. pika 스레드 경계 불침범 | PASS | -| AC-3 | 동시 발행+keepalive 충돌 없음 | `TestKeepaliveAndPublishSameThread::test_keepalive_then_publish_sequential_no_error` PASSED (단일 스레드 순차 호출) | PASS | -| AC-4 | keepalive 비블록 (`time_limit=0`) | `TestKeepaliveTimeLimitZero::test_keepalive_uses_time_limit_zero` PASSED | PASS | -| AC-5 | consume/RPC 경로 무회귀 | `TestKeepaliveDoesNotAffectConsumePaths::test_keepalive_tick_not_present_on_consume_rpc_forever_signature` PASSED | PASS | -| AC-6 | OQ-1 종료 조건 (design 문서에 keepalive 위치 결정) | `design.md` "OQ-1: Answered — execution-loop 틱 삽입" 기록 완료 | PASS | - -### REQ-093: DLQ 가시성/경보 + emit 실패 경보 - -| AC | 내용 | Evidence | Result | -|---|---|---|---| -| AC-1 | unroutable → DLQ 유입 + 관측 지표 노출 | `TestDlqVisibilityOnUnroutable::test_unroutable_calls_publish_dlq_and_logs_error` PASSED + `test_publish_dlq_logs_error_on_entry` PASSED | PASS | -| AC-2 | DLQ 깊이 파악 가능 | 운영자 RabbitMQ 관리 UI + `rabbitmqctl list_queues` 조회 절차 `docs/operations.md` §4에 기재 | PASS | -| AC-3 | emit 최종 실패 → ERROR 로그(rk+slot_id+strategy 포함) | `TestEmitErrorOnFinalPublishFailure::test_emit_logs_error_on_transport_unavailable` PASSED — **시정 후**: 복제 클로저 제거, `object.__new__(Daemon)`으로 stub 생성 후 `event_publisher`만 주입해 실제 `Daemon._make_event_emitter()`가 반환한 emit 클로저 직접 호출로 재작성. 프로덕션 경로 회귀 가드 확립. | PASS | -| AC-4 | `_publish_dlq` 재발행 실패 → ERROR 로그 | `TestDlqPublishFailureLogsError::test_dlq_publish_failure_logs_error_not_warning` PASSED | PASS | -| AC-5 | OQ-3 종료 조건 (design 문서에 DLQ 가시성 방식 결정) | `design.md` "OQ-3: Answered — 구조화 로그 기반 (dcy-029 자산 재사용)" 기록 완료 | PASS | -| AC-6 | consume/RPC 경로 무회귀 | `TestConsumePaths` PASSED + 전체 회귀 554 passed 0 회귀 | PASS | - -### REQ-094: baseline 문서 정합 - -| AC | 내용 | Evidence | Result | -|---|---|---|---| -| AC-1 | `runtime-flows.md` 발행/keepalive 경로 정합 | `docs/runtime-flows.md` — `슬롯 이벤트 발행/구독` 재시도/재연결/keepalive/DLQ 관측 경로 기술 추가 확인 | PASS | -| AC-2 | OQ-1 결정(keepalive 위치) `runtime-flows.md` 반영 | `데몬 lifecycle` flow에 `trex-execution` 틱 시작 시 `keepalive_tick()` 호출 기술 추가 확인 | PASS | -| AC-3 | OQ-3 결정(DLQ 가시성) `operations.md` DLQ 절차 신규 기재 | `docs/operations.md` §4 "DLQ 가시성 / 이벤트 발행 경보" 섹션 신규 생성 확인 | PASS | -| AC-4 | `project-structure.md` 신규 컴포넌트 반영 | `messaging.py`(keepalive_tick), `rabbitmq.py`(재시도+keepalive), `daemon.py`(keepalive/ERROR 격상), 신규 테스트 파일 기술 추가 확인 | PASS | -| AC-5 | `interface-contract.md` 불변 | diff 확인 — 본 batch에서 `docs/interface-contract.md` 변경 없음 (AMQP 토폴로지·큐·exchange·라우팅 키 불변) | PASS | - -## Wire 계약 무회귀 검증 (NFR-1) - -- `declare_topology` 경로: 변경 없음. -- `consume_rpc_forever` / `consume_events_forever` / `call_rpc` 경로: 변경 없음. -- `EVENTS_EXCHANGE`, `EVENTS_DLQ_EXCHANGE`, `EVENTS_DLQ_QUEUE` 토폴로지: 변경 없음. -- 발행 routing key 형식(`{event_type}.{strategy_id}.{symbol}`): 변경 없음. -- 페이로드 스키마: 변경 없음. -- `docs/interface-contract.md`: 변경 없음. - -## baseline 문서 동기화 (Architecture Impact: runtime-flows) - -- `docs/runtime-flows.md` — 갱신 완료 (Last Updated: 2026-06-19 BAT-047) -- `docs/project-structure.md` — 갱신 완료 (Last Updated: 2026-06-19 BAT-047) -- `docs/operations.md` — DLQ 절차 신규 기재 완료 - -## 미검증 항목 (Blocking Issues 없음, 잔여 위험으로 분류) - -- **실제 broker half-open 재현**: `TREX_RABBITMQ_TEST_URL` 환경이 없어 통합 테스트(`test_rabbitmq_transport.py`, `test_rabbitmq_intent_e2e.py`) skip됨 (기존 skip과 동일 조건). 신규 테스트는 모두 mock 기반 unit test로 로직을 검증함. -- **OQ-2 소비자 멱등성**: orb/tsl 재시도 중복 이벤트 수신 시 멱등성 확인은 각 repo 별도 추적(본 batch 밖). trex-core 범위 완료. - -## QC PASS-WITH-NOTES 시정 내역 (2026-06-19 재검증) - -QC opus 검증에서 지적된 2건을 시정했다. - -**시정 1 — ruff F401 3건 제거** -- `tests/adapters/messaging/test_publish_reliability.py` L28: `import threading` 제거 -- `tests/adapters/messaging/test_publish_reliability.py` L29: `from unittest.mock import MagicMock, call, patch` → `call` 제거 -- `trex_core.runtime.daemon.Daemon` import는 시정 2에서 실제 사용하도록 재작성해 F401 해소 - -**시정 2 — REQ-093 AC-3 테스트를 프로덕션 경로로 재작성** -- 구: 테스트 본문에 emit 클로저 직접 복제 (프로덕션 `_make_event_emitter` 미호출) -- 신: `object.__new__(Daemon)`으로 stub 인스턴스 생성 → `event_publisher`만 mock 주입 → `daemon_stub._make_event_emitter()`가 반환한 emitter를 그대로 호출 -- 효과: `daemon.py` L247-253의 `_log.error(...)` 경로를 실제로 타므로, 해당 코드가 제거/변경되면 테스트가 실패해 회귀를 가드함 - -**재검증 실제 결과** -- `python3 -m ruff check .` → `All checks passed!` -- `python3 -m pytest tests/ --ignore=... -q` → `554 passed, 40 skipped in 1.51s`, 회귀 0 - -## Ready for confirm-batch-verification - -- 신규 테스트: 17 passed -- 전체 테스트: 554 passed, 40 skipped (기존 대비 +17, 회귀 0) -- ruff: clean (F401 3건 시정 완료, 실제 실행 결과) -- AC 커버: REQ-091 AC-1~6, REQ-092 AC-1~6, REQ-093 AC-1~6, REQ-094 AC-1~5 전부 PASS 또는 evidence 있음 -- wire 계약 무회귀: PASS -- baseline 문서 정합: PASS -- Blocking Issues: 없음 -- Ready for Release: true (팀장 confirm-release 승인 2026-06-25 — 이미 검증·푸시 완료, REL-009 포함 확정) diff --git a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/design.md b/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/design.md deleted file mode 100644 index 09ee243..0000000 --- a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/design.md +++ /dev/null @@ -1,337 +0,0 @@ -# BAT-048 Design - -- Status: draft -- Batch: BAT-048 - ---- - -> **Design Review 게이트**: 이 문서의 Key Decisions(KD-1, KD-2)와 REQ-097 알람 스파이크 분석(섹션 "알람 능력 스파이크 — REQ-097")은 팀장·사장님의 design review 승인 후 run-batch-implementation으로 이어진다. 아래 권고안은 trex-core-dev가 코드 분석 근거로 작성한 것이며 팀장이 KD-1/KD-2를, 사장님이 알람 M2 포함 여부(OQ-1)를 최종 확정한다. - ---- - -## Architecture Summary - -이 batch의 핵심 변경은 `application/execution.py` 내 체결가 결정 로직에 국소화된다. 아키텍처 계층상 변경 위치는 **application 레이어 전용**이며, wire 계약·adapter·domain은 불변이다. - -현재 문제 흐름: -``` -KIS.poll_fill() - → FillStatus(avg_price=None or 0.0) - → _on_filled / _on_partial_cancel - fill_price = 0.0 [← 현재: avg_price=None이면 old_avg(=0) 사용] - → slot.avg_price = 0 [슬롯 평단 0 오염, 영구 — clear_dispatch 이후 재폴링 없음] - → _record_ledger - price = 0.0 [← 현재: avg_price=None이면 0.0 통과] - → LedgerWorkItem(price=0.0) [원장 cost basis 0 오염] -``` - -변경 후 흐름: -``` -KIS.poll_fill() - → FillStatus(avg_price=None or 0.0) - → _resolve_fill_price(fs, slot) [신규 헬퍼, application 레이어] - → slot.limit_price 백필 [지정가 양수이면 사용] - → 폴백 정책 적용(KD-2 확정 후) [지정가마저 없으면] - → _log.warning(백필 WARN) [REQ-096 AC-1] - → _on_filled / _on_partial_cancel - fill_price = _resolve_fill_price 결과 [0 아닌 값] - → slot.avg_price = 정상 가중평균 - → _record_ledger - price = 동일 결과값 [0 아닌 값] - → LedgerWorkItem(price > 0) -``` - -변경 계층: -- `trex_core/application/execution.py` (application 레이어) — 백필 헬퍼 + 분기 + WARN 로그 -- `tests/application/test_execution.py` — 신규 AC 테스트 -- `docs/runtime-flows.md`, `docs/operations.md` — 갱신 (implementation 후) - -불변 영역: -- `trex_core/adapters/broker/kis.py` — adapter 불변 (KD-1 근거) -- `trex_core/adapters/broker/paper.py` — adapter 불변 (paper는 항상 양수 avg_price 반환) -- AMQP wire 계약 (slots RPC payload / trex.events 스키마) — NFR-1 - -## Changed Areas - -- Docs: - - `docs/runtime-flows.md` — 체결 처리 흐름에 백필 분기 추가 (implementation 완료 후) - - `docs/operations.md` — 백필 WARN 발생 시 운영자 점검 절차 추가 (implementation 완료 후) -- Modules: - - `trex_core/application/execution.py` — `_resolve_fill_price`(신규 헬퍼), `_on_filled`·`_on_partial_cancel`·`_record_ledger`에 분기 삽입 - - `tests/application/test_execution.py` — 신규 AC 커버 테스트 -- Interfaces: - - `LedgerTrade.price` 필드: 0 → 양수 값으로 정정. 필드 타입/이름 변경 없음(NFR-1). - - slots RPC `avg_price` 필드: 0 → 양수 값으로 정정. 스키마 변경 없음(NFR-1). - -## Key Decisions - -### KD-1: 백필 결정 지점 — application 레이어 단일화 권고 - -**권고: `application/execution.py`의 `_resolve_fill_price` 헬퍼로 단일화. `adapters/broker/kis.py`는 불변.** - -#### 분석 근거 - -**현재 kis.py의 avg_price 처리 패턴** (`:209`, `:219`, `:232`, `:238`, `:243`): -```python -# 완전체결(:209) -fill_price = float(avg) if avg else 0.0 -# ... -avg_price=fill_price or None, # 0.0 → None으로 변환(:219) - -# 부분취소(:232) -fill_price = float(avg) if avg else 0.0 -# ... -avg_price=fill_price or None, # 동일 패턴(:238) - -# 부분체결(아직 pending, :243) -avg_price=float(avg) if avg else None # 미체결분은 그대로 -``` - -kis.py는 이미 `avg_price=None`을 FillStatus로 정직하게 전달하는 구조다. 이 층에서 백필을 하려면 **kis.py가 `slot.limit_price`를 알아야** 하는데, `poll_fill(broker_order_id, side, symbol)` 시그니처에 슬롯 컨텍스트(`slot`)가 없다. 따라서 adapter에서 백필하려면 시그니처 변경(`BrokerPort.poll_fill` 확장)이 필요하고 이는 포트 계약 변경이다. - -**paper.py 일관성**: paper 브로커는 `_Order.__init__`에서 `self.price = float(req.limit_price) if req.limit_price is not None else 1000.0`으로 항상 양수 avg_price를 반환한다. paper에는 백필 분기가 필요 없어 adapter 통일 구현의 이점이 없다. - -**dcy-021 잔고 재구성 경로**: `reconcile()`은 `_poll(r)`을 호출 → 동일한 `_on_filled`/`_on_partial_cancel`을 통과한다. application에 백필 헬퍼를 두면 재구성 경로도 동일 헬퍼를 자동으로 탄다. adapter에 두면 재구성 경로에서 kis.py → FillStatus(avg_price=None) → application으로 None이 그대로 전달되는 차이가 생긴다(일관성 깨짐). - -**결론**: application 단일화가 유일하게 porter 계약을 건드리지 않고, 모든 경로(KIS 완전체결/부분취소/재구성/paper)에서 동일 헬퍼를 탄다. - -**adapter 방식의 장단점 (기각 이유)**: -- 장점: adapter 경계에서 일찍 정정 → application이 항상 양수 fill_price를 받는다는 불변식 성립. -- 단점: `BrokerPort.poll_fill` 시그니처에 `slot` 컨텍스트 추가 필요(포트 계약 변경) / paper도 수정 필요 / paper는 avg_price가 이미 양수여서 불필요한 중복 코드. - -**application 방식의 장단점 (권고 이유)**: -- 장점: BrokerPort/adapter 불변 / paper 불변 / reconcile 자동 일관성 / `slot.limit_price` 직접 접근 가능. -- 단점: `_on_filled`/`_on_partial_cancel`/`_record_ledger` 세 지점에 분기 삽입 필요 → 헬퍼로 추출해 DRY 유지. - -**팀장 승인 필요**: 이 결정이 확정되어야 Phase A 구현이 시작된다. - ---- - -### KD-2: 지정가 부재/≤0 최종 폴백 정책 — skip+강 WARN 권고 - -**권고: `slot.limit_price`마저 None 또는 ≤0인 극단 케이스는 "체결 처리를 skip하되 강 WARN 로그 발행" 정책.** - -#### 분석 대상 후보 - -| 후보 | 설명 | 장점 | 단점 | 안전성 | 구현비용 | -|---|---|---|---|---|---| -| **A: 재폴링 (체결 처리 보류)** | avg_price=None + limit_price=None 이면 이번 틱 처리를 skip, 다음 틱 재폴링 | KIS가 다음 틱에 정상 avg를 줄 수 있음 | 완전체결 후 `clear_dispatch` 전에 재폴링이 막힘 — 완전체결 케이스에서 재폴링 경로가 없어 영구 대기. `_on_filled` 내부에서 skip하면 슬롯이 PENDING_BUY에 잠기고 타임아웃/watchdog 경로가 구조에 의존. | 낮음 (영구 교착 가능) | 높음 (complete fill 케이스 구분 필요) | -| **B: 직전가/현재가 조회** | KIS에 현재가(`get_current_price`) 추가 조회 → 해당 가격으로 백필 | 지정가보다 실제 시세에 가까운 근사 | (1) `BrokerPort`에 `get_current_price` 추가 필요(포트 계약 변경) (2) KIS 추가 API 호출(타임아웃, 네트워크 실패) (3) 매우 드문 케이스를 위한 복잡도 증가 (4) safety_sell.py의 `get_current_price`(paper 전용 구현)가 있지만 KIS에는 미구현 | 중간 | 높음 | -| **C: skip + 강 WARN (권고)** | avg_price=None + limit_price=None 이면 체결 처리를 **skip**하되 slot_id·symbol·reason을 포함한 WARN 이상 로그 발행. 슬롯 전이 미수행, 다음 틱 재폴링 | 단순·안전 / 무경고 통과 0(REQ-096 AC-2 충족) / 추가 포트 계약 변경 없음 / "0 저장도 없음 + 강한 WARN"으로 REQ-095 AC-6 최소 기준 충족 | (1) 완전체결 상황에서 슬롯이 PENDING 상태에 잠길 수 있음 (2) 운영자 개입 필요(수기 확인 후 `trex-core cancel`·재진입) | 높음 | 낮음 | - -#### 권고 근거 - -- A(재폴링)은 **완전체결(is_filled=True) 케이스에서 실행 불가**다. `_on_filled` 내부에서 skip하면 `clear_dispatch`가 호출되지 않아 슬롯은 PENDING_BUY로 잠기고, 다음 틱에 동일 주문을 다시 poll → `is_filled=True` 반복 → 루프. 이는 dcy-003 R-1(pending order stuck)과 동일한 패턴이다. -- B(현재가 조회)는 `BrokerPort` 포트 계약 변경이며 구현비용 대비 발생 빈도가 낮다. Discovery A-2: "KIS 평균가 누락은 드문 순간 레이스". 또한 KIS `get_current_price` 구현은 현재 없어 신규 adapter 작업이 필요하다. -- C(skip + 강 WARN)은 **REQ-095 AC-6 최소 기준("이 케이스에서 최소한 0 저장은 발생하지 않거나 강한 WARN이 남아야 한다")을 충족**하고, REQ-096 AC-2("무경고 통과 없음")도 충족한다. 슬롯이 PENDING에 잠기는 것은 watchdog/stale_pending 경보가 탐지해 운영자 개입을 유도하므로 안전망이 있다. - -**구현 스펙 (C 채택 시)**: -```python -def _resolve_fill_price(self, fs: FillStatus, slot) -> tuple[float | None, bool]: - """ - Returns (fill_price, was_backfilled). - fill_price=None → skip signal (폴백 불가, 강 WARN 이미 발행). - """ - avg = fs.avg_price - if avg is not None and float(avg) > 0: - return float(avg), False - # avg_price None or ≤0: 지정가 백필 시도 - limit = slot.limit_price - if limit is not None and float(limit) > 0: - _log.warning( - "fill price backfill: slot=%s symbol=%s broker_order_id=%s " - "avg_price=%s backfill_price=%s reason=avg_price_backfill", - slot.slot_id, slot.symbol, slot.broker_order_id, - avg, limit, - ) - return float(limit), True - # 지정가마저 부재/≤0: skip + 강 WARN - _log.warning( - "fill price unresolvable: slot=%s symbol=%s broker_order_id=%s " - "avg_price=%s limit_price=%s reason=fill_price_unresolvable " - "(slot stays PENDING — operator attention required)", - slot.slot_id, slot.symbol, slot.broker_order_id, - avg, limit, - ) - return None, False -``` - -`_resolve_fill_price`가 `None`을 반환하면 호출자(`_on_filled`, `_on_partial_cancel`, `_record_ledger`)에서 해당 체결 처리를 skip한다. - -**팀장 승인 필요**: 폴백 정책 확정 후 REQ-095 AC-6 구체화. 다른 후보 채택 시 설계가 변경된다. - ---- - -### KD-3: `_record_ledger` 컨텍스트 패싱 — 파라미터 확장 권고 - -**권고: `_on_filled`/`_on_partial_cancel`에서 산출한 `fill_price`(백필 결과)를 `_record_ledger`에 직접 전달.** - -#### 현재 흐름 분석 - -`_on_filled`(`:231~285`)는 `fill_price`(avg_price 가공값)를 계산해 `slot.avg_price` 갱신에만 사용한다. 이후 `:281`에서 `self._record_ledger(slot, fs, side, broker_order_id, r)`를 호출하는데, `_record_ledger`(`:401~437`)는 `:424`에서 `fs.avg_price`를 다시 꺼내 쓴다. - -즉 **같은 `fs.avg_price`를 두 곳에서 독립적으로 해석**한다. 백필 헬퍼를 추가하면 `_on_filled`에서 한 번 계산한 결과를 `_record_ledger`에서 재계산 없이 전달하는 것이 일관성 있는 설계다. - -**구현 스펙**: -```python -# _on_filled 내부: -fill_price, was_backfilled = self._resolve_fill_price(fs, slot) -if fill_price is None: - return # skip: 폴백 불가, WARN 이미 발행됨 -# ... slot avg_price 계산 ... -self._record_ledger(slot, fs, side, broker_order_id, r, resolved_price=fill_price) - -# _record_ledger 시그니처 변경: -def _record_ledger(self, slot, fs, side: str, broker_order_id: str, - r: ExecutionResult, *, resolved_price: float | None = None) -> None: - # ... - price = resolved_price if resolved_price is not None else ( - float(fs.avg_price) if fs.avg_price is not None else 0.0 - ) -``` - -`resolved_price` 기본값을 `None`으로 두면 기존 `_record_ledger` 호출 코드(다른 경로가 있다면)의 하위 호환성이 유지된다. 현재 코드베이스에서 `_record_ledger`는 `_on_filled`와 `_on_partial_cancel`에서만 호출된다. - ---- - -## 알람 능력 스파이크 — REQ-097 - -> REQ-097 AC-1: 후보 A·B·C 각각 장점·단점·최소 구현 범위·M2 일정 적합성 명시. -> REQ-097 AC-2: 후보 B wire 계약 변경 여부와 blast radius 명시. - -### 후보 A: 구조화 로그 기반 - -**개요**: FR-3 WARN 로그(REQ-096)를 알람 소스로 삼아 외부 알림 파이프라인(Loki → Grafana Alert / PagerDuty / etc.)에서 소비. - -**장점**: -- trex-core 코드 변경 최소 — REQ-096(WARN 로그)만 구현하면 코어 측 작업 완료. -- dcy-029(BAT-045/046) 구조화 로그 인프라(json formatter, correlation_id) 이미 구축됨. -- wire 계약 불변, blast radius 없음. -- M2 일정 내 코어 변경 없음. - -**단점**: -- 운영자가 로그 파이프라인(Loki + 알림 규칙)을 별도로 구성해야 함(인프라 의존). -- 알림 지연이 로그 수집 파이프라인 지연에 종속된다(수십 초 ~ 분 단위). -- trex-core 자체에는 알람을 "보내는" 능력이 없음 — 알람은 로그 소비자가 담당. - -**최소 구현 범위**: REQ-096 WARN 로그 구현만으로 충분. 알람 파이프라인 구축은 인프라 작업(trex-core 범위 밖). - -**M2 일정 적합성**: 코어 측 추가 작업 없음 → **M2 적합**. - ---- - -### 후보 B: AMQP 이벤트 기반 - -**개요**: `trex.events` exchange 또는 신규 `trex.alerts` exchange에 알람 이벤트(예: `fill_price_backfill`, `fill_price_unresolvable`)를 publish. - -**wire 계약 변경 여부 분석**: - -현재 `trex.events` 토폴로지 (interface-contract.md): -- routing key 패턴: `{event_type}.{strategy_id}.{symbol}` -- 소비자: `trex.events.strategy.{strategy_id}` 큐가 `*.{strategy_id}.#` 패턴으로 바인딩 -- trex-orb-v1, trex-tsl-v1이 이 큐를 소비 - -알람 이벤트를 `trex.events`에 추가하면: -- `alert.fill_backfill.{strategy_id}.{symbol}` 형태의 routing key를 사용할 경우 → 기존 `*.{strategy_id}.#` 바인딩에 매칭 → **orb/tsl에 알람 이벤트가 전달됨**. orb/tsl이 미처리 메시지를 어떻게 처리하는지 불명확. -- 신규 `trex.alerts` exchange를 추가할 경우 → exchange 신설 + 큐 + 바인딩 추가 = **AMQP 토폴로지 변경** = **wire 계약 변경**. -- 기존 `slot_filled_buy` 등 이벤트 스키마에 알람 관련 필드를 추가하는 것도 **payload 스키마 변경** = **wire 계약 변경**. - -**결론: 후보 B는 어떤 구현 형태를 취하더라도 wire 계약(AMQP 토폴로지 또는 payload 스키마) 변경을 수반한다.** - -**blast radius**: trex-orb-v1, trex-tsl-v1 양쪽 모두 영향. 특히 trex-tsl-v1은 M2 라이브 목표(6/28) 직전 wire 변경은 위험 수준 High. - -**wire 계약 변경 필요 여부**: **예 (후보 B는 Out of Scope)** - -> CLAUDE.md 교차 영향 사안 정책에 따라: 후보 B 분석에서 wire 계약 변경 필요성이 확인됨. 이 사안을 팀장에게 보고한다. **후보 B는 본 batch 범위 밖이며 구현 후보에서 제외 권고**. - ---- - -### 후보 C: 외부 푸시 직접 연동 - -**개요**: trex-core 내에서 직접 Slack webhook / Telegram API / 이메일 등 외부 채널로 알람 전송. - -**장점**: -- 운영자가 로그 파이프라인 없이 즉시 수신. -- 지연 최소(수 초 이내). - -**단점**: -- trex-core에 외부 HTTP 의존성 추가(Slack/webhook URL 환경변수, 인증 토큰). -- 외부 채널 장애 시 trex-core 알람 발행 실패가 체결 처리에 영향 줄 수 있음(best-effort 설계 필요). -- 채널별 구현(Slack vs Telegram vs email)에 따라 코드 복잡도 증가. -- 자격증명(webhook URL / API key) 관리가 운영 절차에 추가됨. -- 알람 어댑터 포트 신규 정의 필요 + 구체 어댑터 구현 + 테스트. - -**최소 구현 범위**: `AlertPort` (application 레이어 포트) + `SlackWebhookAlertAdapter` (adapter 레이어) + `DaemonConfig`에 webhook URL 추가 + `execution.py`에서 포트 호출. 최소 5개 파일 변경. - -**M2 일정 적합성**: 구현 범위가 상당하고 자격증명 운영 절차가 추가됨 → **M2 일정 압박 (R-3)**. M3 후보 더 적합. - ---- - -### 알람 스파이크 권고 요약 - -| 후보 | M2 포함 권고 | 이유 | -|---|---|---| -| A: 구조화 로그 기반 | **권고** (코어 작업 불필요) | REQ-096 WARN 구현만으로 완결, wire 계약 불변, M2 적합 | -| B: AMQP 이벤트 기반 | **Out of Scope** | wire 계약 변경 수반, orb/tsl blast radius, M2 위험 High | -| C: 외부 푸시 직접 연동 | **이월 권고** (M3 후보) | 구현 범위 크고 자격증명 관리 추가, M2 일정 압박 | - -**trex-core-dev 권고**: 후보 A(구조화 로그 기반)를 기반 경로로 채택. REQ-096 WARN 로그 → Loki → 알람 파이프라인 연결은 인프라 작업으로 분리. 후보 C의 실제 구현은 M3에서 별도 Discovery로 정의. - -FR-3 WARN 로깅(REQ-096)은 알람 채택 여부와 무관하게 M2 필수이며 본 batch에 포함된다. - -**사장님 결정 필요 (OQ-1)**: 알람 실제 구현(후보 A 인프라 파이프라인 구축 or 후보 C 코어 구현)의 M2 포함 여부. trex-core-dev는 이 결정을 임의로 내리지 않는다. - ---- - -## Edge Cases - -### EC-1: 완전체결 + avg_price=None + limit_price=None - -- 발생 경로: KIS가 완전체결 보고 + avg=0 + 슬롯에 limit_price가 None(드문 케이스, 예: orb가 market 주문 시) -- 현재: `fill_price=0.0` → `slot.avg_price=0` 영구 오염. -- KD-2 C 채택 시: `_resolve_fill_price` → `None` 반환 → `_on_filled` skip → `clear_dispatch` 미호출 → 슬롯이 PENDING_BUY 유지. 다음 틱 재폴링 → 동일 `is_filled=True` → skip 반복. -- 안전망: `_check_stale_pending`이 `stale_threshold_sec` 경과 후 WARN을 발생시키고, watchdog 만료 후 safety-sell이 개입한다. -- 운영자 조치: `trex-core cancel ` → 슬롯 롤백 → 재진입. -- 이 케이스는 Discovery A-1(orb 매수는 지정가 주문)에 위배되는 예외 케이스이므로 저빈도라고 가정. - -### EC-2: 부분취소 + avg_price=None + limit_price > 0 - -- `_on_partial_cancel`에서 `_resolve_fill_price` → `slot.limit_price` 백필 + WARN. -- 백필 가격으로 `new_avg` 계산 + `_record_ledger` price 기록 → 정상 흐름. - -### EC-3: 매도 체결 + avg_price=None - -- `_on_filled`에서 side=PENDING_SELL 분기: 이 분기는 `fill_price`/`avg_price`를 슬롯 평단 계산에 사용하지 않는다(매도는 avg_price 보존). 따라서 **매도 체결에서는 `_on_filled` 내 slot 전이에 백필 영향 없음**. -- 단, `_record_ledger`의 `price`(매도 체결가)는 여전히 `fs.avg_price`를 사용 → 여기서도 백필 헬퍼 적용 필요. -- 매도 `limit_price`는 `_dispatch`에서 HOLDING 진입 시 `None`으로 클리어되므로, 매도 시 `slot.limit_price=None`이 정상이다. 따라서 매도 체결에서 avg_price=None이면 EC-1과 유사한 skip+강 WARN 경로를 탄다(KD-2 C 확정 시). - -### EC-4: happy path 회귀 - -- `fs.avg_price`가 정상 양수이면 `_resolve_fill_price`는 `(fs.avg_price, False)` 반환 → 기존과 동일 경로. WARN 미발생, 분기 overhead 없음. - -### EC-5: paper 브로커에서 avg_price=None 발생 가능성 - -- paper 브로커(`paper.py:92,103,106`)는 `avg_price=o.price`를 반환하며 `o.price`는 `__init__`에서 `float(req.limit_price) if req.limit_price is not None else 1000.0`로 항상 양수다. -- 따라서 paper 브로커 사용 시 백필 경로 진입 없음 → paper 관련 테스트에서 WARN 미발생 단언 가능. - -## Architecture Impact - -- runtime-flows (체결 처리 흐름 변경 — `_on_filled`/`_on_partial_cancel`/`_record_ledger`에 백필 분기 추가) - -## Reference Doc Update Plan - -- `docs/runtime-flows.md`: implementation 완료 후 "Flow: 체결 처리" 섹션에 백필 분기 flowchart 추가. `_resolve_fill_price` 헬퍼 경로(avg_price=None/≤0 → limit_price 백필 → WARN 로그) 명시. -- `docs/operations.md`: implementation 완료 후 "백필 WARN 발생 시 운영자 점검 절차" 추가. 포함 내용: WARN 로그 식별 방법(`reason=avg_price_backfill` or `reason=fill_price_unresolvable`), 수기 점검 항목(slotID·원장 확인), `trex-core cancel` 조치 절차. -- `docs/project-structure.md`: `_resolve_fill_price` 헬퍼 추가는 execution.py 내 사적 메서드로 project-structure 변경 없음. -- `docs/interface-contract.md`: AMQP wire 계약 불변 — 변경 없음. - ---- - -## Design Review 확정 항목 (팀장·사장님) - -| # | 항목 | 권고 | 결정 권한 | 상태 | -|---|---|---|---|---| -| D-1 | 백필 결정 지점: application 단일화 vs adapter | application 권고(KD-1) | 팀장 | 미확정 | -| D-2 | 지정가 부재/≤0 최종 폴백: skip+강WARN vs 재폴링 vs 현재가 조회 | skip+강WARN 권고(KD-2) | 팀장 | 미확정 | -| D-3 | 알람 구현 M2 포함 여부 | 후보 A(로그 기반) + 실제 파이프라인은 후속 이월 권고 | 사장님 | 미확정 | -| D-4 | (D-3 M2 포함 시) 후보 A/C 중 무엇을 코어에서 구현 | 후보 C는 M3, 후보 A는 인프라 작업으로 분리 권고 | 사장님 | 미확정 | diff --git a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/index.md b/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/index.md deleted file mode 100644 index 504ae7e..0000000 --- a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/index.md +++ /dev/null @@ -1,37 +0,0 @@ -# BAT-048: 체결가 0/None 백필 가드 + WARN 로깅 + 알람 능력 스파이크 (dcy-032) - -- Status: verified (confirm-batch-verification 팀장 2026-06-21 — REQ-095/096 Implemented, 575 passed/47 skipped/0 fail 독립 재실행 + ruff clean, opus QC PASS-WITH-NOTES→NOTE-1/2 정정완료, 통합 3건 미실행 결정 기록[영속/전송 diff 0·KIS avg=None 결정적 재현 불가]) -- Profile: standard -- Source Discovery: dcy-032 -- Included REQ: REQ-095 (구현 완료), REQ-096 (구현 완료), REQ-097 (스파이크 산출물 완료 — 알람 impl 이월) -- Excluded REQ: 없음 - -## Summary -- dcy-032 발견: KIS 체결조회에서 체결수량은 반환되나 평균체결가(`avg`)가 None/0일 때, 슬롯 평단(`slot.avg_price`)과 원장 cost basis(`_record_ledger` price)가 동시에 0으로 무경고·영구 오염된다. 완전체결 시 `clear_dispatch`로 재폴링이 없어 0이 영속된다. -- 세 REQ가 단일 인과 사슬(체결가 누락 → 무결성 가드 → 가시성 → 알람 검토)을 형성하므로 같은 batch로 묶는다. REQ-095/096은 구현·테스트가 동일 코드 경로(`execution.py:_on_filled`, `_on_partial_cancel`, `_record_ledger`)에 있어 분리 구현의 이점이 없다. REQ-097은 같은 batch design 산출물로 제출되어 사장님 결정의 근거가 된다. - -## Scope -- In Scope: - - `trex_core/application/execution.py` — `_on_filled`/`_on_partial_cancel`의 `fill_price` 백필 분기(REQ-095) + WARN 로그(REQ-096) - - `trex_core/application/execution.py` — `_record_ledger`의 `price` 백필 분기(REQ-095) + 백필/0가격 통과 WARN(REQ-096) - - 알람 채널 후보 A/B/C 분석 문서(REQ-097) — design 산출물 - - `tests/application/test_execution.py` — avg_price=None, avg_price=0, happy path, WARN 로그 발생 AC 커버 - - `docs/runtime-flows.md` — 체결 처리 흐름에 백필 분기 추가 (implementation 후 갱신) - - `docs/operations.md` — 백필 WARN 발생 시 운영자 점검 절차 추가 (implementation 후 갱신) -- Out of Scope: - - `trex_core/adapters/broker/kis.py` 수정 — 백필 결정 지점을 application으로 단일화하므로 adapter 변경 없음 (design 확정 결과) - - slots RPC / AMQP wire 계약 변경 — NFR-1 명시적 금지 - - 과거 0 기록 원장/슬롯의 소급 정정 - - 알람 실제 구현 — design review 후 사장님 결정 사안(REQ-097 OQ-1) - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: `trex_core/application/execution.py`, `tests/application/test_execution_fill_price_backfill.py` -- Verification: [verification.md](./verification.md) - -## Notes -- R-1(Medium, Discovery R-1): 지정가 백필도 추정치. 실제 체결가 ≠ 지정가일 수 있음. 완화: WARN+추적 가능 플래그, 런북 명시. -- R-2(Medium, Discovery R-2): 백필 결정 지점(adapter vs application) 선택에 따라 paper/dcy-021 재구성 경로 일관성이 달라짐. design에서 단일 지점 확정 필수 — 팀장 승인 게이트. -- R-3(Low, Discovery R-3): 알람 구현 포함 시 범위 확대 → M2 일정 압박. design review에서 사장님 결정. -- wire 계약 안전: 후보 B(AMQP 이벤트 알람) 분석에서 wire 계약 변경 필요성이 발생하면 즉시 팀장 보고·중단(CLAUDE.md 교차 영향 사안 정책). diff --git a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/planning.md b/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/planning.md deleted file mode 100644 index 36d4310..0000000 --- a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/planning.md +++ /dev/null @@ -1,79 +0,0 @@ -# BAT-048 Planning - -- Status: confirmed -- Batch: BAT-048 -- Profile: standard - -## Included REQ - -- REQ-095: 체결가 0/None 지정가 백필 가드 (슬롯 평단 + 원장 price) -- REQ-096: 체결가 백필·0가격 통과 WARN 로깅 -- REQ-097: 알람(alert) 능력 검토 스파이크 산출물 - -## Delivery Plan - -REQ-095/096은 같은 코드 경로(`execution.py`)의 같은 함수들에 걸쳐 있어 분리 구현의 이점이 없다. 두 REQ를 묶어 구현한 뒤 테스트에서 값 정정(REQ-095)과 로그 가시성(REQ-096)을 동시에 검증한다. REQ-097은 design 산출물로 완결된다. - -구현 순서(run-batch-implementation 기준): - -1. **design 확정 사항 선행 (이 planning 이전 게이트)**: - - 백필 결정 지점: application 단일화(후술 Design Gate). paper 브로커·dcy-021 재구성 경로 일관성 분석 후 팀장 승인. - - 지정가 부재/≤0 최종 폴백 정책: 후보별 분석 → 팀장 승인 (design review 게이트). - - REQ-097 알람 분석 산출물: design 문서 내 포함 → design review에서 사장님 결정. - -2. **Phase A — `_on_filled` / `_on_partial_cancel` 백필 분기(REQ-095 AC-1/2) + WARN 로그(REQ-096 AC-1)**: - - 두 함수 내 `fill_price = float(fs.avg_price) if fs.avg_price is not None else old_avg` 를 백필 헬퍼(`_resolve_fill_price`)로 추출. - - `_resolve_fill_price(fs, slot)`: avg_price None/≤0 시 `slot.limit_price` 사용, 지정가도 없으면 폴백 정책 적용. - - 백필 시 `_log.warning(...)` 발행(REQ-096 AC-1 필드 집합). - -3. **Phase B — `_record_ledger` price 백필(REQ-095 AC-3) + WARN 로그(REQ-096 연동)**: - - `price = float(fs.avg_price) if fs.avg_price is not None else 0.0` 라인을 `_resolve_fill_price` 결과로 교체. - - 이미 Phase A에서 백필 WARN을 발행했으면 중복 WARN 없음; `_record_ledger` 진입 전에 백필 여부를 파악할 수 있는 컨텍스트 설계 필요 (design에서 결정). - -4. **Phase C — 테스트 작성**: - - `tests/application/test_execution.py`: AC-1~4(REQ-095) + AC-1~4(REQ-096) 커버. - - avg_price=None, avg_price=0, happy_path(정상 양수), limit_price 부재 극단 케이스. - - WARN 로그 발생 단언(`caplog` 사용). - - 기존 execution 테스트 회귀 유지. - -5. **Phase D — baseline 문서 갱신**: - - `docs/runtime-flows.md` — 체결 처리 흐름에 백필 분기 추가. - - `docs/operations.md` — 백필 WARN 발생 시 운영자 점검 절차. - -선행 조건: Phase A~C는 설계 결정(백필 헬퍼 구조, `_record_ledger` 컨텍스트 패싱)이 확정되어야 시작 가능. - -## Dependencies - -- Internal: - - `trex_core/application/execution.py` — 변경 대상 핵심 파일(BAT-004에서 도입, 이후 지속 갱신) - - `trex_core/adapters/broker/kis.py` — 읽기 참조만(백필은 application에서, adapter 불변) - - `trex_core/adapters/broker/paper.py` — 읽기 참조만(paper는 `o.price`를 항상 양수로 반환 → 백필 분기 미진입 정상 케이스) - - `tests/application/test_execution.py` — 기존 회귀 스위트 전체 통과 유지 필수 - - dcy-029(BAT-045/046) — 구조화 로깅 인프라(`_log`, json formatter) 재사용 -- External: - - portfolio-hub `LedgerPort` — `price` 필드 타입(float) 불변, 값만 정정. HTTP 계약 변경 없음. - - slots RPC(trex-orb-v1, trex-tsl-v1) — `avg_price` 필드 값이 0→정상값으로 바뀌는 것은 wire 계약 변경이 아님(NFR-1). trex-tsl-v1은 avg_price≤0 시 tier 무시 2% 폴백에서 정상 tier 평가로 자동 개선. - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: - - **백필 결정 지점 (Discovery R-2, REQ-095 Notes)**: adapter(kis.py)에서 처리 vs application(execution.py)에서 처리. paper 브로커·dcy-021 잔고 재구성 경로와의 일관성이 다르다. 설계 없이 구현하면 경로별 일관성 깨짐 리스크. design에서 단일 지점 확정 후 팀장 승인 필수. - - **지정가 부재/≤0 폴백 정책 (Discovery OQ-2, REQ-095 AC-6)**: 재폴링 / 직전가 조회 / skip+강알림 등 후보별 구현비용·안전성이 다르다. 설계 확정 없이 구현하면 AC-6가 미완성이다. design review에서 팀장 확정 필수. - - **`_record_ledger` 컨텍스트 패싱 설계**: Phase A 백필 결과(`fill_price`, `was_backfilled`)를 `_record_ledger`까지 전달하는 방법(파라미터 확장 vs 재계산)이 함수 시그니처를 변경할 수 있다. - - **REQ-097 알람 스파이크 분석**: 알람 채널 후보 B(AMQP 이벤트)가 wire 계약 변경을 수반하는지 여부가 설계에서 확인되어야 한다. wire 변경 수반 시 즉시 팀장 보고·중단. - - architecture impact가 `runtime-flows`(체결 처리 흐름 변경)이므로 standard profile design이 필요하다. - -## Milestones - -1. draft-batch-design 완료 — 백필 결정 지점, 폴백 정책, `_record_ledger` 컨텍스트 패싱, REQ-097 알람 분석 산출물 확정(팀장·사장님 design review 게이트) -2. run-batch-implementation 완료 — Phase A~D 신규 테스트 포함, 기존 execution 회귀 유지 -3. draft-batch-verification 완료 — REQ-095/096 AC별 커버, REQ-097 산출물 제출 확인 -4. QC reviewer 검증 대기 (confirm-batch-verification은 팀장 게이트) - -## Risks - -- R-1(Medium): 지정가 백필이 추정치라 실제 체결가와 다를 수 있다. 완화: WARN + 추적 가능 플래그, 런북 명시. "0보다 합리적인 근사 + 추적 가능"이 목표(Discovery R-1). -- R-2(Medium): 백필 결정 지점을 adapter로 두면 paper 브로커에도 동일 백필 로직 복제가 필요해진다. application 단일화가 권고. design에서 확정. -- R-3(Low): `_resolve_fill_price` 헬퍼 추출 시 함수 시그니처 변경이 기존 테스트에 영향 가능. 완화: 기존 테스트 회귀 유지를 verification 기준으로 명시. -- R-4(Low): 알람 후보 B 분석 중 wire 계약 변경 필요 여부가 나오면 즉시 중단·팀장 보고. diff --git a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/verification.md b/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/verification.md deleted file mode 100644 index d2d4517..0000000 --- a/docs/batches/bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/verification.md +++ /dev/null @@ -1,156 +0,0 @@ -# BAT-048 Verification - -- Status: verified (confirm-batch-verification 팀장 2026-06-21) -- Batch: BAT-048 -- Scope: REQ-095 (지정가 백필 가드), REQ-096 (백필 WARN 로깅) -- REQ-097: 알람 능력 스파이크 산출물로 완료 — 알람 실제 구현은 사장님 결정에 따라 이 batch 완료 후 별도 트랙 이월 - ---- - -## 구현 범위 요약 - -| 변경 파일 | 변경 내용 | -|---|---| -| `trex_core/application/execution.py` | `_resolve_fill_price(fs, slot)` 헬퍼 신설; `_on_filled`·`_on_partial_cancel`에 백필 분기 + skip 경로 삽입; `_record_ledger`에 `resolved_price` 키워드 파라미터 추가(기본 None, 하위호환) | -| `tests/application/test_execution_fill_price_backfill.py` | 신규 단위 테스트 21개 (REQ-095/096 AC 전체 커버 + EC-3 매도 경로 5개 추가; QC PASS-WITH-NOTES 정정 반영) | -| `docs/runtime-flows.md` | "주문 집행 루프" Flow — poll 항목에 체결가 백필 가드 분기 문서화 | -| `docs/operations.md` | §5 신설: 체결가 백필 WARN 발생 시 운영자 점검 절차; 디버깅 표에 2행 추가 | - -불변 영역 확인: -- `trex_core/adapters/broker/kis.py` — 수정 없음 (KD-1: application 단일화) -- `trex_core/adapters/broker/paper.py` — 수정 없음 -- AMQP wire 계약 (`docs/interface-contract.md`) — 변경 없음 (NFR-1) - ---- - -## 단위 테스트 실행 결과 - -**실행 명령**: `python3 -m pytest tests/application/test_execution_fill_price_backfill.py -v` - -**신규 테스트 결과 (21개, QC 정정 후):** - -``` -tests/application/test_execution_fill_price_backfill.py::TestOnFilledBackfill::test_avg_price_none_with_limit_price_uses_limit_price PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnFilledBackfill::test_avg_price_zero_with_limit_price_uses_limit_price PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnFilledBackfill::test_happy_path_uses_avg_price_no_backfill_warn PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnFilledBackfill::test_avg_price_none_limit_price_none_skips_fill PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnFilledBackfill::test_avg_price_none_limit_price_zero_skips_fill PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnPartialCancelBackfill::test_avg_price_none_partial_cancel_uses_limit_price PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnPartialCancelBackfill::test_avg_price_zero_partial_cancel_uses_limit_price PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnPartialCancelBackfill::test_happy_path_partial_cancel_no_backfill_warn PASSED -tests/application/test_execution_fill_price_backfill.py::TestOnPartialCancelBackfill::test_partial_cancel_unresolvable_skips_fill PASSED -tests/application/test_execution_fill_price_backfill.py::TestRecordLedgerBackfillPrice::test_ledger_price_equals_backfill_limit_price_on_filled PASSED -tests/application/test_execution_fill_price_backfill.py::TestRecordLedgerBackfillPrice::test_ledger_price_equals_backfill_limit_price_on_partial_cancel PASSED -tests/application/test_execution_fill_price_backfill.py::TestRecordLedgerBackfillPrice::test_ledger_price_on_happy_path_uses_avg_price PASSED -tests/application/test_execution_fill_price_backfill.py::TestRecordLedgerBackfillPrice::test_ledger_not_written_when_fill_price_unresolvable PASSED -tests/application/test_execution_fill_price_backfill.py::TestWarnLogFields::test_backfill_warn_contains_required_fields PASSED -tests/application/test_execution_fill_price_backfill.py::TestWarnLogFields::test_unresolvable_warn_contains_required_fields PASSED -tests/application/test_execution_fill_price_backfill.py::TestSellPathBackfill::test_sell_filled_avg_price_none_with_limit_price_uses_backfill PASSED -tests/application/test_execution_fill_price_backfill.py::TestSellPathBackfill::test_sell_partial_cancel_avg_price_none_with_limit_price_uses_backfill PASSED -tests/application/test_execution_fill_price_backfill.py::TestSellPathBackfill::test_sell_filled_avg_price_none_limit_price_none_skips PASSED -tests/application/test_execution_fill_price_backfill.py::TestSellPathBackfill::test_sell_partial_cancel_avg_price_none_limit_price_none_skips PASSED -tests/application/test_execution_fill_price_backfill.py::TestSellPathBackfill::test_sell_filled_happy_path_no_backfill_warn PASSED -tests/application/test_execution_fill_price_backfill.py::TestPaperBrokerRegression::test_paper_broker_buy_no_backfill_warn PASSED - -21 passed in 0.21s -``` - -**전체 스위트 회귀 확인 결과:** - -``` -575 passed, 47 skipped in 1.40s -``` - -회귀: 0건. 기존 테스트 전부 통과. - ---- - -## REQ-095 AC별 충족 여부 - -| AC | 내용 | 충족 여부 | 근거 테스트 | -|---|---|---|---| -| AC-1 | avg_price=None + limit_price>0 → _on_filled에서 slot.avg_price = limit_price(0 아님) | 충족 | `test_avg_price_none_with_limit_price_uses_limit_price` | -| AC-2 | avg_price=0.0 + limit_price>0 → _on_partial_cancel에서 slot.avg_price = limit_price(0 아님) | 충족 | `test_avg_price_zero_with_limit_price_uses_limit_price`, `test_avg_price_zero_partial_cancel_uses_limit_price` | -| AC-3 | avg_price=None → _record_ledger LedgerWorkItem.price = 백필 지정가(0.0 아님) | 충족 | `test_ledger_price_equals_backfill_limit_price_on_filled`, `test_ledger_price_equals_backfill_limit_price_on_partial_cancel` | -| AC-4 | avg_price 정상 양수(happy path) → 기존 fs.avg_price 사용, 백필 미개입 | 충족 | `test_happy_path_uses_avg_price_no_backfill_warn`, `test_ledger_price_on_happy_path_uses_avg_price` | -| AC-5 | wire 계약(slots RPC 스키마) 불변 — 값 정정만 | 충족 | interface-contract.md 수정 없음, KD-1 분석 | -| AC-6 | limit_price=None/≤0 극단 케이스 → 체결 skip + slot_id·symbol·broker_order_id·avg_price·limit_price·reason=fill_price_unresolvable 강 WARN | 충족 | `test_avg_price_none_limit_price_none_skips_fill`, `test_avg_price_none_limit_price_zero_skips_fill`, `test_ledger_not_written_when_fill_price_unresolvable`, `test_unresolvable_warn_contains_required_fields` | - ---- - -## REQ-096 AC별 충족 여부 - -| AC | 내용 | 충족 여부 | 근거 테스트 | -|---|---|---|---| -| AC-1 | 백필 발동 시 slot_id·symbol·broker_order_id·원avg_price·backfill_price·reason=avg_price_backfill 포함 WARN 정확히 1건 | 충족 | `test_avg_price_none_with_limit_price_uses_limit_price`, `test_backfill_warn_contains_required_fields` | -| AC-2 | 지정가마저 부재/≤0 극단 케이스 — WARN 이상 1건 이상, 무경고 통과 없음 | 충족 | `test_avg_price_none_limit_price_none_skips_fill`, `test_unresolvable_warn_contains_required_fields` | -| AC-3 | 정상 avg_price(happy path) — 백필 관련 WARN 미발생 | 충족 | `test_happy_path_uses_avg_price_no_backfill_warn`, `test_happy_path_partial_cancel_no_backfill_warn`, `test_paper_broker_buy_no_backfill_warn` | -| AC-4 | 백필 WARN 로그에서 어떤 슬롯·체결에서 백필이 발생했는지, 원래 가격, 대체가격 식별 가능(NFR-3) | 충족 | `test_backfill_warn_contains_required_fields` — slot_id/symbol/avg_price(None)/backfill_price 필드 단언 | - ---- - -## QC PASS-WITH-NOTES 정정 내역 - -QC 독립 검증에서 PASS-WITH-NOTES 판정 후 아래 2건을 정정했다. - -### NOTE-1 (ruff 위생) - -- `:18` `import pytest` F401(미사용) → 제거 -- `:276` `r = execution.run_once()` F841(미사용 변수) → `execution.run_once()`로 변경 -- `ruff check tests/application/test_execution_fill_price_backfill.py` → `All checks passed!` - -### NOTE-2 (매도 경로 테스트 보강) - -- `TestSellPathBackfill` 클래스 신설(5개 테스트) — 상세는 아래 섹션 참조. -- 매도 경로에서 설계와 다른 실제 동작 발견 없음 — 프로덕션 코드 수정 없음. - ---- - -## EC-3 매도(PENDING_SELL) 경로 커버리지 (QC NOTE-2 정정) - -design.md EC-3 설계 의도: 매도 체결 시 슬롯 avg_price(cost basis)는 보존되며, -`_record_ledger`의 price 필드에만 매도 체결가(`resolved_price`)가 기록된다. -매도 슬롯의 `limit_price`는 HOLDING 진입 시 None으로 클리어되므로, -market sell(limit_price=None)에서 avg_price=None이면 skip+강WARN이 설계 의도된 동작이다. - -| 테스트 | 케이스 | 단언 | -|---|---|---| -| `test_sell_filled_avg_price_none_with_limit_price_uses_backfill` | EC-3a: _on_filled PENDING_SELL + avg_price=None + limit_price>0 | 슬롯 INACTIVE, 1 WARN reason=avg_price_backfill, 원장 price=limit_price(0 아님), side=sell | -| `test_sell_partial_cancel_avg_price_none_with_limit_price_uses_backfill` | EC-3b: _on_partial_cancel PENDING_SELL + avg_price=None + limit_price>0 | 슬롯 HOLDING(잔량), 1 WARN reason=avg_price_backfill, 원장 price=limit_price | -| `test_sell_filled_avg_price_none_limit_price_none_skips` | EC-3c: _on_filled PENDING_SELL + avg_price=None + limit_price=None (TSL market sell) | 슬롯 PENDING_SELL 유지, 원장 미생성, 강WARN reason=fill_price_unresolvable | -| `test_sell_partial_cancel_avg_price_none_limit_price_none_skips` | EC-3d: _on_partial_cancel PENDING_SELL + avg_price=None + limit_price=None | 슬롯 PENDING_SELL 유지, 원장 미생성, 강WARN | -| `test_sell_filled_happy_path_no_backfill_warn` | EC-3e: 정상 매도 (avg_price>0) | 슬롯 INACTIVE, WARN 미발생, 원장 price=avg_price | - -설계와 다른 동작 없음 — 코드 수정 없이 테스트만 추가. - ---- - -## REQ-097 처리 - -REQ-097(알람 능력 스파이크)은 `design.md`의 "알람 능력 스파이크 — REQ-097" 섹션에서 후보 A/B/C 분석 산출물로 완결됐다. - -- **후보 B(AMQP 이벤트 기반)**: wire 계약 변경 수반 + trex-orb-v1/trex-tsl-v1 blast radius 확인 → 팀장에게 보고, Out of Scope 확정. -- **후보 A(구조화 로그 기반)**: REQ-096 WARN 로그가 알람 소스 역할. 코어 추가 작업 없음. -- **후보 C(외부 푸시 직접 연동)**: 구현 범위 크고 자격증명 관리 추가 → M3 이월 권고. - -알람 실제 구현(알람 파이프라인 구축 또는 후보 C 코어 구현)은 사장님 결정(OQ-1)에 따라 이 batch 완료 후 별도 트랙으로 이월됨. REQ-097은 스파이크 산출물로 완료 처리. - ---- - -## 통합 테스트 — 팀장 직접 실행 필요 - -단위 테스트는 모두 in-memory 어댑터로 검증됐다. 아래 항목은 실 인프라(KIS/PostgreSQL/RabbitMQ)가 필요한 통합 테스트로, 팀장이 실 환경에서 직접 실행해야 한다. - -1. **KIS avg_price=None 케이스 재현**: KIS sandbox에서 체결조회가 avg=0을 반환하는 순간 포착 → backfill WARN 발행 + 슬롯 avg_price/원장 price가 지정가로 저장됨을 확인. -2. **PostgreSQL ledger_outbox price 확인**: 실 PostgreSQL에서 backfill된 체결 건의 `ledger_outbox.trade_data.price`가 0.0이 아닌 지정가 값인지 확인. -3. **reconcile 경로 일관성**: 데몬 재시작 후 `reconcile()`이 backfill 헬퍼를 동일하게 탄다는 것을 KIS sandbox에서 확인. - ---- - -## 남은 위험 - -| 위험 | 수준 | 설명 | -|---|---|---| -| R-1 | Medium | 지정가 백필 자체가 추정치 — 실제 체결가와 다를 수 있다. WARN + 추적 가능 플래그(reason=avg_price_backfill)으로 완화. 운영자 판단 여지. | -| R-2 | Low | fill_price_unresolvable 케이스에서 슬롯 PENDING 고착 → watchdog/stale_pending이 탐지 후 운영자 개입 필요. 자동 복구 없음(설계 의도). | -| R-3 (해소) | — | 기존 테스트 영향 없음(회귀 0). | diff --git a/docs/batches/bat-049_20260621_alarm-system-dcy-033/design.md b/docs/batches/bat-049_20260621_alarm-system-dcy-033/design.md deleted file mode 100644 index 0ccfbb0..0000000 --- a/docs/batches/bat-049_20260621_alarm-system-dcy-033/design.md +++ /dev/null @@ -1,120 +0,0 @@ -# BAT-049 Design - -- Status: confirmed -- Batch: BAT-049 - -## Architecture Summary - -알람 시스템을 application 레이어의 `AlarmEmitter`(포트 경유) + adapters 레이어의 `publish_alarm_event()` 확장으로 구현한다. 기존 레이어드 아키텍처(domain/application/adapters/runtime)와 의존성 역전 규칙을 그대로 따른다. - -``` -발생 지점(execution.py 등) - │ alarm_emitter.publish_alarm(code, title, fields) - ▼ -AlarmEmitter (application 레이어) - │ MessagingTransport.publish_alarm_event(routing_key, payload) - ▼ -RabbitMQTransport.publish_alarm_event() (adapters 레이어) - │ basic_publish(exchange=trex.alerts, routing_key=alarm.critical.trex-core, mandatory=False) - ▼ -trex.alerts exchange → (notifier가 alarm.# 패턴으로 바인딩) -``` - -`AlarmEmitter`는 `MessagingTransport` 포트만 주입받는다. pika 직접 import 없음. domain 레이어 접촉 없음. - -## Changed Areas - -| 파일 | 변경 유형 | 내용 | -|---|---|---| -| `docs/interface-contract.md` | 추가(기존 불변) | `Interface: trex.alerts (알람 채널, dcy-033)` 섹션 신규 | -| `trex_core/adapters/messaging/topology.py` | 추가 | `ALERTS_EXCHANGE = "trex.alerts"` 상수 추가 | -| `trex_core/adapters/messaging/rabbitmq.py` | 확장 | `declare_topology()`에 trex.alerts exchange 선언 + `publish_alarm_event()` 메서드 신규 | -| `trex_core/adapters/messaging/in_memory.py` | 확장 | `publish_alarm_event()` stub 추가 (발행 기록 + unavailable 시 TransportUnavailableError) | -| `trex_core/application/messaging.py` | 확장 | `MessagingTransport` Protocol에 `publish_alarm_event()` 추가 | -| `trex_core/application/alarm_emitter.py` | 신규 | `AlarmEmitter` 클래스 구현 | -| `trex_core/application/execution.py` | 배선 | `fill_price_unresolvable`, `sell_skipped_no_balance` 알람 호출 추가 | -| `trex_core/application/reconciliation.py` | 배선 | `core_holding_broker_flat` 알람 호출 추가 | -| `trex_core/application/ledger_outbox.py` | 배선 | `ledger_delivery_failed` 알람 호출 추가 | -| `trex_core/application/killswitch.py` | 배선 | `kill_switch_fired` 알람 호출 추가 | -| `trex_core/adapters/broker/rabbitmq.py`(`messaging/rabbitmq.py`) | 배선 | `publish_unavailable_after_reconnect` / `dlq_publish_failed` — 콜백 패턴 | -| `trex_core/application/portfolio.py` / `snapshot_scheduler.py` | 배선 | `portfolio_hub_unavailable` 알람 호출 추가 | -| `trex_core/runtime/reset.py` | 배선 | `schema_verification_failed` / `migration_failed` 알람 호출 (transport None 안전) | -| `trex_core/runtime/daemon.py` | 조립 | `AlarmEmitter` 생성 + 각 서비스에 주입 | -| `tests/application/test_alarm_emitter.py` | 신규 | AlarmEmitter 단위 테스트 | -| `tests/adapters/messaging/test_topology.py` | 확장 | declare_topology trex.alerts assertion | -| `docs/runtime-flows.md` | 추가(기존 불변) | 알람 발신 흐름 섹션 신규 | - -## Key Decisions - -### KD-1: publish_unavailable_after_reconnect / dlq_publish_failed — 콜백 패턴 채택 - -**문제**: 두 alarm code는 `trex_core/adapters/messaging/rabbitmq.py`(어댑터) 내부에서 발생하지만, `AlarmEmitter`는 application 레이어에 있다. 어댑터가 application을 직접 import하면 의존성이 역전된다. - -**채택 결정: 콜백(callable) 주입** -- `RabbitMQTransport`에 `alarm_callback: Optional[Callable[[str, str, dict], None]]` 파라미터를 추가한다. 시그니처: `(code: str, title: str, fields: dict) -> None`. -- `daemon.py`에서 `AlarmEmitter.publish_alarm`을 콜백으로 주입한다. -- 어댑터는 application 타입을 import하지 않고, callable 타입 힌트(`Optional[Callable]`)만 사용. -- 콜백 미주입 시(기본값 None): 기존 동작 유지 (로그만). -- 콜백이 raise하면 어댑터는 except로 삼킨다(best-effort 원칙 일관). - -**기각 대안**: -- 이벤트 버스/큐: 과도한 복잡성, 결정론적 단위 테스트 어려움. -- AlarmEmitter를 adapters 레이어로 이동: domain/application 순수성 원칙 위반. -- Transport 프로토콜에 alarm_callback getter 추가: Protocol 오염 + 테스트 어려움. - -### KD-2: reset.py transport None 안전 — AlarmEmitter None-guard 패턴 - -**문제**: `reset.py`는 CLI 진입점으로 `MessagingTransport`가 없다. AlarmEmitter에 None transport를 전달하면 호출 시 crash. - -**채택 결정**: `AlarmEmitter.__init__`이 `Optional[MessagingTransport]`를 받고, `publish_alarm()` 내부에서 transport가 None이면 WARN 로그 후 skip. -- `reset.py`는 `AlarmEmitter(transport=None)`로 생성하거나 `publish_alarm()` 호출 전 None 체크. -- 실제로는 `run_reset()` 함수가 `AlarmEmitter`를 직접 받는 시그니처로 확장하거나, reset 전용 경량 emitter를 분리한다. -- 가장 단순한 방법: `AlarmEmitter(transport=None)` 허용 + None guard → reset 코드에서 생성 부담 최소화. - -### KD-3: publish_alarm_event 시그니처 — transport 레이어 알람 전용 메서드 - -**결정**: `MessagingTransport` 프로토콜에 `publish_alarm_event(routing_key: str, payload: dict) -> None` 추가. -- `publish_event()`와 구분하는 이유: alarm은 `trex.alerts` exchange, `mandatory=False`, `delivery_mode=1`(best-effort, 비영속) vs events는 `trex.events`, `mandatory=True`, `delivery_mode=2`. -- `InMemoryTransport`: 발행된 alarm을 `_alarm_events: list[tuple[str, dict]]`에 기록 + unavailable 시 TransportUnavailableError 발생(호출자가 삼킴). -- `RabbitMQTransport`: `publish_alarm_event()`는 `basic_publish(exchange=trex.alerts, mandatory=False, delivery_mode=1)`. 연결 실패 시 TransportUnavailableError raise(AlarmEmitter가 삼킴). retry-on-failure 없음(best-effort 원칙). - -### KD-4: AlarmEmitter 인터페이스 - -```python -class AlarmEmitter: - def __init__(self, transport: Optional[MessagingTransport]) -> None: ... - - def publish_alarm( - self, - code: str, - title: str, - *, - fields: dict | None = None, - severity: str = "critical", - ) -> None: - """Best-effort 발행. 실패 시 ERROR 로그 후 예외 삼킴.""" - ... -``` - -- `source="trex-core"`, `schema_version="1"` 상수화. -- `occurred_at` = 발행 시점 KST ISO-8601. -- routing key = `alarm.{severity}.trex-core`. -- 발행 실패(TransportUnavailableError, 기타 모든 예외): ERROR 로그 후 예외 삼킴(re-raise 금지). - -## Edge Cases - -- **transport disconnect 중 알람 발행**: `publish_alarm_event()` → TransportUnavailableError → AlarmEmitter가 ERROR 로그 후 삼킴. 본업 경로 비차단. -- **rabbitmq.py 콜백이 None인 경우**: `alarm_callback` guard로 skip. 기존 코드 변경 없이 하위호환. -- **reset.py에서 AlarmEmitter(transport=None)**: None guard 분기 → INFO 로그("alarm skipped: no transport") 후 skip. -- **mandatory=False + notifier 미연결**: silent drop. UnroutableError 없음. -- **8개 code 중 일부 경로에서 AlarmEmitter 미주입**: daemon.py에서 전부 주입. 미주입 시 AttributeError → execution_loop except로 삼킴(기존 보호 패턴). -- **AlarmEmitter.publish_alarm 내 예외 루프**: 각 호출마다 독립 try/except. 한 코드 발행 실패가 동일 tick의 다른 호출에 영향 없음. - -## Architecture Impact - -`both` — runtime-flows.md + interface-contract.md 양쪽 업데이트 필요. - -## Reference Doc Update Plan - -- `docs/interface-contract.md`: `Interface: trex.alerts (알람 채널, dcy-033)` 섹션 추가 + Change Log 갱신 + Last Updated 갱신. -- `docs/runtime-flows.md`: `Flow: 알람 발신 (trex.alerts, dcy-033)` 섹션 추가. 기존 섹션 불변. diff --git a/docs/batches/bat-049_20260621_alarm-system-dcy-033/implementation.md b/docs/batches/bat-049_20260621_alarm-system-dcy-033/implementation.md deleted file mode 100644 index 18c536b..0000000 --- a/docs/batches/bat-049_20260621_alarm-system-dcy-033/implementation.md +++ /dev/null @@ -1,92 +0,0 @@ -# BAT-049 Implementation - -- Status: completed -- Batch: BAT-049 - -## Plan Summary - -REQ-098(문서) → REQ-099(topology+transport) → REQ-100(AlarmEmitter+배선) → REQ-101(runtime-flows 정합) 순서로 구현. -design.md의 4개 Key Decision을 그대로 따름. - -## Changed Files - -| 파일 | 변경 유형 | 내용 | -|---|---|---| -| `docs/interface-contract.md` | 섹션 추가 + 헤더 갱신 | `Interface: trex.alerts (알람 채널, dcy-033)` 신규 섹션 (7필드 스키마, routing key, topology, CRITICAL code 목록, Update Trigger). Change Log + Last Updated 갱신. 기존 섹션 불변. | -| `trex_core/adapters/messaging/topology.py` | 추가 | `ALERTS_EXCHANGE = "trex.alerts"` + `alarm_routing_key()` 함수 | -| `trex_core/adapters/messaging/rabbitmq.py` | 확장 | `declare_topology()` trex.alerts 선언 추가. `publish_alarm_event()` 신규. `set_alarm_callback()` + `_fire_alarm_callback()` 신규. 콜백 호출 배선 (publish_unavailable_after_reconnect, dlq_publish_failed). `Callable` import 추가. | -| `trex_core/adapters/messaging/in_memory.py` | 확장 | `_alarm_events` 리스트 추가. `publish_alarm_event()` stub + `alarm_events()` 조회. | -| `trex_core/application/messaging.py` | 확장 | `MessagingTransport` Protocol에 `publish_alarm_event()` 추가. | -| `trex_core/application/alarm_emitter.py` | 신규 | `AlarmEmitter` 클래스. envelope 구성, KST occurred_at, 예외 삼킴+ERROR 로그, None transport skip. | -| `trex_core/application/execution.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `sell_skipped_no_balance` + `fill_price_unresolvable` 발행 배선. | -| `trex_core/application/reconciliation.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `core_holding_broker_flat` 발행 배선. | -| `trex_core/application/ledger_outbox.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `ledger_delivery_failed` 3지점(conflict_blocked, permanent_failure, dead_lettered) 배선. | -| `trex_core/application/killswitch.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `kill_switch_fired` 배선. | -| `trex_core/application/snapshot_scheduler.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `portfolio_hub_unavailable` 2지점(whoami_failed, get_portfolios_failed) 배선. `reconcile_core_holdings` 호출에 `alarm_emitter` 전달. | -| `trex_core/runtime/reset.py` | 배선 | `_emit_reset_alarm()` 헬퍼 추가. `run_reset()` alarm_emitter 파라미터 추가. migration 예외 + schema_ok=False 지점 배선. | -| `trex_core/runtime/daemon.py` | 조립 | `AlarmEmitter` import. `alarm_emitter` 생성 (event_publisher 이후). `set_alarm_callback()` 주입. `LedgerWriter`, `KillSwitchService`, `SnapshotScheduler`, `ExecutionService` 생성 시 alarm_emitter 주입. 서비스 생성 순서 재배치(alarm_emitter 생성 후 주입 보장). | -| `tests/application/test_alarm_emitter.py` | 신규 | AlarmEmitter 단위 테스트 29개 (8 code × envelope 검증, 8 code × TransportUnavailableError 삼킴, 8 code × 임의 예외 삼킴, None transport skip, fields default, routing key, pika 미import, TELEGRAM 미참조). | -| `tests/adapters/messaging/test_topology.py` | 확장 | `test_alerts_exchange_constant`, `test_alarm_routing_key_format` 추가. | -| `tests/adapters/messaging/test_in_memory_transport.py` | 확장 | `test_publish_alarm_event_records_event`, `test_publish_alarm_event_raises_when_unavailable` 추가. | -| `docs/runtime-flows.md` | 섹션 추가 + 헤더 갱신 | `Flow: 알람 발신 (trex.alerts, dcy-033)` 신규 섹션 (발행 흐름, best-effort 정책, 8종 code 목록, trex.events 차이표). 기존 섹션 불변. | - -## Design Decisions Implemented - -**KD-1 (rabbitmq.py 어댑터 alarm 콜백 패턴)**: -- `RabbitMQTransport.__init__`에 `alarm_callback: Optional[Callable]` 파라미터 추가. -- `set_alarm_callback()` setter 추가 (늦은 주입 — daemon.py에서 AlarmEmitter 생성 후 바인딩). -- `_fire_alarm_callback()` 내부 헬퍼: callback None guard + 예외 삼킴. -- `publish_event()` retry 실패 시 `publish_unavailable_after_reconnect` 콜백 호출. -- `_publish_dlq()` DLQ 실패 시 `dlq_publish_failed` 콜백 호출. - -**KD-2 (reset.py transport None 안전)**: -- `_emit_reset_alarm(alarm_emitter, code, title, fields)` 헬퍼: None guard + 예외 삼킴. -- `run_reset()` 시그니처에 `alarm_emitter: Optional[object] = None` 추가. -- migration 예외(raise 전)와 schema_ok=False 지점에서 헬퍼 호출. - -**KD-3 (publish_alarm_event 별도 메서드)**: -- `trex.alerts` exchange, `mandatory=False`, `delivery_mode=1` (non-persistent). -- `MessagingTransport` Protocol + `RabbitMQTransport` + `InMemoryTransport` 모두 구현. - -**KD-4 (AlarmEmitter 인터페이스)**: -- `AlarmEmitter(transport: Optional[MessagingTransport])`. -- `publish_alarm(code, title, *, fields=None, severity="critical")`. -- `source="trex-core"`, `schema_version="1"` 고정. -- `occurred_at` = KST ISO-8601. - -## Execution Log - -1. docs/interface-contract.md — trex.alerts 섹션 추가 (REQ-098 완료) -2. topology.py — ALERTS_EXCHANGE + alarm_routing_key() (REQ-099 일부) -3. messaging.py — publish_alarm_event Protocol 추가 (REQ-099 일부) -4. rabbitmq.py — declare_topology + publish_alarm_event + 콜백 패턴 (REQ-099 완료 + REQ-100 KD-1) -5. in_memory.py — publish_alarm_event stub (REQ-099 완료) -6. alarm_emitter.py — AlarmEmitter 신규 (REQ-100 핵심) -7. execution.py — fill_price_unresolvable + sell_skipped_no_balance (REQ-100 배선) -8. reconciliation.py — core_holding_broker_flat (REQ-100 배선) -9. ledger_outbox.py — ledger_delivery_failed (REQ-100 배선) -10. killswitch.py — kill_switch_fired (REQ-100 배선) -11. snapshot_scheduler.py — portfolio_hub_unavailable (REQ-100 배선) -12. reset.py — schema_verification_failed + migration_failed (REQ-100 배선) -13. daemon.py — AlarmEmitter 생성·주입 + 서비스 생성 순서 재배치 (REQ-100 조립) -14. runtime-flows.md — 알람 발신 흐름 섹션 추가 (REQ-101 완료) -15. 테스트 작성 (test_alarm_emitter.py 29개, topology/in_memory 추가) - -## Validation - -``` -pytest tests/application/test_alarm_emitter.py -v -→ 29 passed, 0 failed, 0 skipped - -pytest --tb=short -q (전체) -→ 608 passed, 47 skipped, 0 failed - -ruff check trex_core/ tests/ -→ All checks passed! -``` - -## Remaining Risks - -- R-1(Low): `trex.alerts` exchange 선언은 실 RabbitMQ에서만 검증 가능 (단위에서 mock). 팀장이 실인프라 통합 테스트 수행 예정. -- R-2(Low): daemon.py에서 event_publisher가 `InMemoryTransport`인 경우 `set_alarm_callback()`이 없어 `hasattr` guard 처리됨 (InMemoryTransport는 콜백 불필요). -- R-3(Low): reset.py의 `alarm_emitter` 파라미터가 `main()`(CLI 진입점)에서 None으로 실행됨 — 의도된 동작이나 실인프라 CLI 시나리오에서 알람 미발행. diff --git a/docs/batches/bat-049_20260621_alarm-system-dcy-033/index.md b/docs/batches/bat-049_20260621_alarm-system-dcy-033/index.md deleted file mode 100644 index 81f3bcc..0000000 --- a/docs/batches/bat-049_20260621_alarm-system-dcy-033/index.md +++ /dev/null @@ -1,52 +0,0 @@ -# BAT-049: 알람 시스템 — 알람 envelope wire 계약 + trex.alerts exchange + core CRITICAL emitter + baseline 정합 (dcy-033) - -- Status: release-candidate -- Profile: standard -- Source Discovery: dcy-033 -- Included REQ: REQ-098 (Interface), REQ-099 (Integration), REQ-100 (Integration), REQ-101 (Documentation) -- Excluded REQ: 없음 - -## Summary -- dcy-033(제품군 알람 전달 체계) confirmed Discovery에서 확정된 알람 시스템 1단계 구현. -- REQ-098: 알람 envelope 표준 JSON 스키마 wire 계약을 `docs/interface-contract.md`에 문서화. -- REQ-099: `trex.alerts` (topic, durable=True) AMQP exchange를 `declare_topology()`에 멱등 선언 추가. -- REQ-100: application 레이어 `AlarmEmitter` + CRITICAL code 8종 발생 지점 배선. -- REQ-101: `docs/runtime-flows.md`에 알람 발신 흐름 섹션 추가 + baseline 문서 정합. - -## Scope -- In Scope: - - `docs/interface-contract.md` — `Interface: trex.alerts (알람 채널, dcy-033)` 신규 섹션 추가(기존 섹션 불변) - - `trex_core/adapters/messaging/topology.py` — `ALERTS_EXCHANGE` 상수 추가 - - `trex_core/adapters/messaging/rabbitmq.py` — `declare_topology()`에 `trex.alerts` 선언, `publish_alarm_event()` 메서드 추가 - - `trex_core/adapters/messaging/in_memory.py` — `publish_alarm_event()` stub 추가 - - `trex_core/application/messaging.py` — `MessagingTransport` 프로토콜에 `publish_alarm_event()` 추가 - - `trex_core/application/alarm_emitter.py` — `AlarmEmitter` 신규 구현 - - `trex_core/application/execution.py` — `fill_price_unresolvable`, `sell_skipped_no_balance` 배선 - - `trex_core/application/reconciliation.py` — `core_holding_broker_flat` 배선 - - `trex_core/application/ledger_outbox.py` — `ledger_delivery_failed` 배선 - - `trex_core/application/killswitch.py` — `kill_switch_fired` 배선 - - `trex_core/adapters/broker/rabbitmq.py` — `publish_unavailable_after_reconnect` / `dlq_publish_failed` 배선 (콜백 패턴) - - `trex_core/application/portfolio.py` / `trex_core/application/snapshot_scheduler.py` — `portfolio_hub_unavailable` 배선 - - `trex_core/runtime/reset.py` — `schema_verification_failed` / `migration_failed` 배선 (transport None 안전) - - `trex_core/runtime/daemon.py` — `AlarmEmitter` 생성·주입 - - `tests/application/test_alarm_emitter.py` — AlarmEmitter 단위 테스트 신규 - - `tests/adapters/messaging/test_topology.py` 또는 `test_in_memory_transport.py` — declare_topology trex.alerts assertion 추가 - - `docs/runtime-flows.md` — 알람 발신 흐름 섹션 추가 -- Out of Scope: - - trex.events / trex.intent.req / trex.slots.req / trex.control.req 변경 — 가산·격리 불변(REQ-099 AC-2) - - 텔레그램 봇 토큰·chat_id 등 외부 알람 시크릿 — trex-notifier 담당 - - INFO·WARN code 배선 — CRITICAL 우선(dcy-033 범위 경계) - - 통합/e2e(실 RabbitMQ) 테스트 — 팀장이 실인프라로 수행 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- 이 batch는 신규 AMQP exchange(`trex.alerts`) 추가이지만 기존 wire 계약(exchange/큐/라우팅)을 변경하지 않는 가산·격리 추가이므로 wire 계약 변경 에스컬레이션 대상이 아님. -- `publish_unavailable_after_reconnect` / `dlq_publish_failed`는 rabbitmq.py 어댑터에서 발생 → 콜백 패턴으로 상위에 전파. 설계 결정 details는 design.md 참조. -- `reset.py`는 transport 미연결 가능 → `AlarmEmitter`가 `None` transport를 안전하게 처리. -- R-1(Low): trex.alerts exchange 선언 후 notifier 미연결 시 알람 발행은 `mandatory=False`로 silent drop — 운영상 무해. -- R-2(Low): rabbitmq.py 어댑터 콜백 주입 시 생성 시점 의존성 순환 가능성 → daemon.py에서 생성 순서 주의. diff --git a/docs/batches/bat-049_20260621_alarm-system-dcy-033/planning.md b/docs/batches/bat-049_20260621_alarm-system-dcy-033/planning.md deleted file mode 100644 index e74c6f2..0000000 --- a/docs/batches/bat-049_20260621_alarm-system-dcy-033/planning.md +++ /dev/null @@ -1,54 +0,0 @@ -# BAT-049 Planning - -- Status: confirmed -- Batch: BAT-049 -- Profile: standard - -## Included REQ - -- REQ-098: 알람 envelope wire 계약 정의 (Interface — 문서 전용) -- REQ-099: trex.alerts AMQP exchange 토폴로지 + 가산·격리 불변식 (Integration) -- REQ-100: trex-core 알람 emitter — CRITICAL code 발신 배선 (Integration) -- REQ-101: 알람 발신 흐름 baseline 문서 정합 (Documentation) - -## Delivery Plan - -REQ-098(문서), REQ-099(topology), REQ-100(emitter + 배선), REQ-101(문서 정합)은 단일 인과 사슬을 형성한다: -- REQ-098 wire 계약 문서가 선행 확정되어야 REQ-100 emitter 구현의 필드/타입 근거가 생긴다. -- REQ-099 topology 선언이 선행되어야 REQ-100 발행 경로가 확보된다. -- REQ-101은 REQ-098~100 구현이 완료된 후 baseline 문서를 정합한다. - -구현 순서: -1. **REQ-098 먼저**: `docs/interface-contract.md`에 `Interface: trex.alerts (알람 채널, dcy-033)` 섹션 추가 (기존 섹션 불변). 이 문서가 이후 구현의 단일 출처. -2. **REQ-099**: topology 상수 + `declare_topology()` 확장 + `publish_alarm_event()` 포트/어댑터 추가. -3. **REQ-100**: `AlarmEmitter` 구현 + 8개 CRITICAL 발생 지점 배선. `rabbitmq.py` 콜백 설계 병행. -4. **REQ-101**: `docs/runtime-flows.md` 알람 발신 섹션 추가 + `docs/interface-contract.md` Change Log + Last Updated 갱신. -5. **테스트**: AlarmEmitter 단위 테스트(8 code × 2 케이스) + declare_topology assertion + 기존 테스트 회귀. - -## Dependencies - -- BAT-047(trex.events 재연결 신뢰성): 완료(release-candidate) — `RabbitMQTransport.publish_event()` 재시도 패턴을 `publish_alarm_event()`에서 참조. -- BAT-048(체결가 백필/알람 스파이크): 완료 — REQ-097 스파이크에서 trex.alerts AMQP 채널이 최우선 채택됨. -- `MessagingTransport` 포트 (BAT-007): `publish_alarm_event()` 메서드 추가로 확장. 기존 메서드 시그니처 불변. - -## Design Gate - -**Design 필요: yes (standard — application 레이어 신규 컴포넌트 + 어댑터 확장 + 의존성 역전 설계 결정)** - -- `publish_unavailable_after_reconnect` / `dlq_publish_failed`가 어댑터(`rabbitmq.py`)에서 발생하는데 `AlarmEmitter`는 application에 있다 → 콜백 패턴 vs 이벤트 버스 선택을 design에서 확정해야 함. -- `reset.py`(CLI 진입점)에서 transport 미연결 가능 → None-safe 처리 방식 설계 필요. -- `MessagingTransport` 프로토콜 확장이 기존 `InMemoryTransport` 호환성을 유지하는지 확인 필요. - -## Milestones - -1. REQ-098 문서 완성 (interface-contract.md 신규 섹션) -2. REQ-099 topology + publish_alarm_event 포트/어댑터 구현 -3. REQ-100 AlarmEmitter + 8개 배선 완성 -4. REQ-101 baseline 문서 정합 -5. 단위 테스트 전부 통과 + ruff clean - -## Risks - -- R-1(Low): `publish_alarm_event` 시그니처 추가 시 `InMemoryTransport`에 stub 구현 누락 → Protocol mismatch. 완화: design 에서 확인 + test에서 isinstance 체크 추가. -- R-2(Low): rabbitmq.py 어댑터 콜백 주입 시 daemon.py의 생성 순서 순환 가능성. 완화: design에서 콜백 늦은 주입(setter) 패턴 채택. -- R-3(Low): 기존 단위 테스트 InMemoryTransport 사용분이 declare_topology 호출 없이 통과해야 함. 완화: in_memory declare_topology는 no-op 유지. diff --git a/docs/batches/bat-049_20260621_alarm-system-dcy-033/verification.md b/docs/batches/bat-049_20260621_alarm-system-dcy-033/verification.md deleted file mode 100644 index bbd9048..0000000 --- a/docs/batches/bat-049_20260621_alarm-system-dcy-033/verification.md +++ /dev/null @@ -1,102 +0,0 @@ -# BAT-049 Verification - -- Status: confirmed -- Ready for Release: true - -## Human Approval Memo - -- Approved by: 팀장 -- Approved at: 2026-06-21 (KST) -- Approved scope: BAT-049 confirm-batch-verification 승인 — QC PASS-WITH-NOTES (blocker 0) + 팀장 실인프라 e2e 통과 -- Residual risk accepted: - - reset.py 2 code(`schema_verification_failed`/`migration_failed`) 간접 검증(기존 reset 테스트 회귀) — 직접 AC 매핑 minor 갭. 정직 공개됨. - - 배포 시 운영 AMQP 설정은 코드 아닌 ops 항목. -- Approval rationale: - - 단위: 608 passed / 47 skipped / 0 fail. 신규 AlarmEmitter 29 테스트 포함. ruff clean. - - 독립 opus QC=PASS-WITH-NOTES (blocker 0): best-effort 비차단 · 서비스 생성 순서 재배치 안전 · 가산격리(기존 exchange/큐 불변) · domain 순수성 · 시크릿 0 확인. - - 팀장 실인프라 e2e(2026-06-21): 실 RabbitMQ에 `trex.alerts`(topic,durable) 선언·발행(`alarm.critical.trex-core`) → trex-notifier 수신 → CRITICAL 분류 → 텔레그램 sendMessage HTTP 200. 기존 토폴로지 불변 확인. -- Batch: BAT-049 - -## Test Execution Results - -``` -pytest tests/application/test_alarm_emitter.py -v -→ 29 passed, 0 failed, 0 skipped - -pytest --tb=short -q (전체 스위트) -→ 608 passed, 47 skipped, 0 failed - -ruff check trex_core/ tests/ -→ All checks passed! -``` - -실행 환경: Python 3.12.3, pytest 8.4.2, 2026-06-21 - -## Acceptance Criteria Mapping - -### REQ-098: 알람 envelope wire 계약 정의 - -| AC | 검증 방법 | 결과 | -|---|---|---| -| AC-1: `docs/interface-contract.md`에 `Interface: trex.alerts` 섹션이 7개 필드 포함 | 수동 확인 | PASS — 섹션 추가됨, 7필드 명시 | -| AC-2: severity 값이 3 리터럴 제한 + schema_version="1" 명시 | 수동 확인 | PASS — 명시됨 | -| AC-3: 기존 섹션 내용 불변 (diff=신규 섹션 추가뿐) | 수동 확인 (기존 텍스트 비교) | PASS — 기존 섹션 변경 없음 | -| AC-4: routing key `alarm.{severity}.{source}` 형식 명시 | 수동 확인 | PASS — 섹션에 명시됨 | -| AC-5: Update Trigger 포함 | 수동 확인 | PASS — `Update Trigger (알람 envelope 스키마)` 서브섹션 존재 | -| AC-6: 다른 모듈 개발자가 `trex_core` import 없이 envelope 정보 획득 가능 | 수동 확인 | PASS — 섹션에 모든 필드·타입·routing key 포함 | - -### REQ-099: trex.alerts AMQP exchange 토폴로지 + 가산·격리 불변식 - -| AC | 검증 방법 | 결과 | -|---|---|---| -| AC-1: `declare_topology()` 호출 시 `trex.alerts`(topic, durable=True) 선언 | `test_alerts_exchange_constant` + `test_alarm_routing_key_format` (topology 상수 검증) + rabbitmq.py 코드 확인 | PASS (단위) / 미실행 (실 RabbitMQ 통합) | -| AC-2: 기존 exchange/큐 type·durable·인수 불변 | 기존 테스트 스위트 608 통과 (회귀 없음) | PASS | -| AC-3: mandatory=False → unroutable 시 silent success | `publish_alarm_event()` 구현 + `test_publish_alarm_event_records_event` (InMemory) | PASS (단위) / 미실행 (실 RabbitMQ 통합) | -| AC-4: interface-contract.md 토폴로지 명세(exchange명/타입/durable/routing key/발행자/소비자) | 수동 확인 | PASS — 섹션에 모두 명시 | -| AC-5: 기존 topology 단위 테스트 + 신규 trex.alerts assertion 통과 | `test_topology.py` 5개 테스트 (신규 2개 포함) 통과 | PASS | -| AC-6: transport disconnect 시 알람 silently skip | `test_publish_alarm_transport_unavailable_no_reraise` 8 code × PASS | PASS | - -### REQ-100: trex-core 알람 emitter — CRITICAL code 발신 배선 - -| AC | 검증 방법 | 결과 | -|---|---|---| -| AC-1: 8개 code 각각 올바른 envelope (`source/severity/schema_version`) | `test_publish_alarm_envelope_fields` 8 code parametrize | PASS | -| AC-2: TransportUnavailableError 시 re-raise 없음 + ERROR 로그 | `test_publish_alarm_transport_unavailable_no_reraise` 8 code | PASS | -| AC-3: 임의 예외 시 re-raise 없음 + ERROR 로그 | `test_publish_alarm_arbitrary_exception_swallowed` 8 code | PASS | -| AC-4: 알람 발행 실패가 core 본업 경로 비차단 | 코드 확인: try/except 삼킴 패턴, 기존 execution/killswitch/ledger/reconcile 테스트 모두 통과 | PASS | -| AC-5: TELEGRAM_* 참조 0건 | `test_alarm_emitter_no_telegram_reference` + `grep -r TELEGRAM trex_core/` → 0건 | PASS | -| AC-6: 8 code × 2 케이스 (정상 + 예외) 모두 통과 | 29개 test 통과 | PASS | -| AC-7: pika 직접 import 없음 | `test_alarm_emitter_no_pika_import` | PASS | -| AC-8: domain 레이어 messaging import 없음 | `test_domain_import_sweep.py` 기존 통과 유지 | PASS | - -### REQ-101: 알람 발신 흐름 baseline 문서 정합 - -| AC | 검증 방법 | 결과 | -|---|---|---| -| AC-1: `docs/runtime-flows.md`에 `Flow: 알람 발신 (trex.alerts, dcy-033)` 섹션 존재 | 수동 확인 | PASS | -| AC-2: AlarmEmitter → MessagingTransport → trex.alerts 흐름 + best-effort 정책 명시 | 수동 확인 | PASS | -| AC-3: CRITICAL code 8종 + 발생 모듈 경로 기술 | 수동 확인 (표 형식으로 10개 항목 포함 — schema_verification_failed/migration_failed 별도 행) | PASS | -| AC-4: trex.events와의 차이(독립 exchange, mandatory=False, DLQ 없음) 명시 | 수동 확인 (비교 표 포함) | PASS | -| AC-5: `docs/interface-contract.md` Change Log에 dcy-033 항목 기록 | 수동 확인 | PASS | -| AC-6: 두 문서 `Last Updated (KST)` 갱신 | 수동 확인 (2026-06-21 BAT-049) | PASS | - -## Regression Evidence - -- 기존 608개 테스트 전부 통과 (47 skipped 유지 — DB/Redis/실RabbitMQ 의존 테스트) -- `tests/domain/test_domain_import_sweep.py` — domain 순수성 불변식 통과 -- execution/reconciliation/killswitch/ledger_outbox 관련 기존 테스트 전부 통과 - -## Missing Evidence (통합 — 팀장 실인프라 수행) - -1. **실 RabbitMQ `trex.alerts` exchange 선언**: `declare_topology()` 호출 후 RabbitMQ 관리 콘솔에서 exchange 존재·속성(topic, durable=True) 확인. -2. **실 RabbitMQ mandatory=False 발행**: notifier 큐 미바인딩 상태에서 `publish_alarm_event()` 호출 → 예외 없이 성공 확인. -3. **실 RabbitMQ 기존 topology 불변**: 기존 exchange/큐 목록이 변경 없이 유지되는지 확인. - -## Blocking Issues - -없음 (단위 테스트 전부 통과, 통합은 팀장 실인프라 수행으로 위임됨). - -## Notes - -- 이 verification은 단위 테스트 결과와 코드 수동 검토만 포함한다. 실인프라 통합 테스트는 `Missing Evidence` 3건으로 명시했으며 confirm-batch-verification 전에 팀장이 수행한다. -- `schema_verification_failed` / `migration_failed`는 ALARM-INVENTORY.md §A에 단일 항목(`schema_verification_failed / migration_failed`)으로 기재되어 있으나, 구현에서는 두 개별 code로 분리 배선하고 테스트에서는 `schema_verification_failed`와 `migration_failed` 대신 `CRITICAL_CODES` 리스트 8개만 parametrize했다. reset.py 배선은 `test_reset.py` 기존 테스트 회귀로 간접 검증. diff --git a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/design.md b/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/design.md deleted file mode 100644 index ba2f3b5..0000000 --- a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/design.md +++ /dev/null @@ -1,145 +0,0 @@ -# BAT-050 Design - -- Status: confirmed -- Batch: BAT-050 - -## 1. ReconcileWarningStore 포트 위치 결정 - -**결정: `application/portfolio.py` 내에 Protocol + InMemory 통합.** pause_store 선례와 동형으로, 포트와 인메모리 구현을 `portfolio.py`에 함께 둔다. PostgreSQL 어댑터는 `adapters/portfolio/reconcile_warning_store.py`에 별도 파일. - -이유: `PauseStore Protocol`과 `InMemoryPauseStore`가 `portfolio.py`에 있는 패턴을 그대로 따른다. 파일 분산 없이 선례 일관성 유지. - -## 2. ReconcileWarningStore 인터페이스 설계 - -```python -class ReconcileWarningStore(Protocol): - def set_warned(self, slot_id: str, *, portfolio_id: str, case_code: str, - actor: str = "reconcile", reason: str = "") -> None: ... - def clear_warned(self, slot_id: str) -> None: ... - def is_warned(self, slot_id: str) -> bool: ... - def list_warned(self) -> set[str]: ... # set of slot_id strings -``` - -- PK: `slot_id` (문자열 UUID) -- `case_code`: `'B'` (슬롯↔브로커 qty 불일치) 또는 `'C'` (원장↔브로커 불일치) -- `list_warned()`: set[str] 반환 — bulk 조회 후 `slot_to_dict`에서 in-operator 체크 - -## 3. SQL 마이그레이션 스키마 - -```sql -CREATE TABLE IF NOT EXISTS slot_reconcile_warning ( - slot_id TEXT PRIMARY KEY, - portfolio_id TEXT NOT NULL, - case_code TEXT NOT NULL, - actor TEXT NOT NULL DEFAULT 'reconcile', - reason TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -``` - -ON CONFLICT DO UPDATE: 동일 slot_id의 중복 삽입 시 case_code/actor/reason/updated_at 갱신. - -## 4. reconcile_positions C 케이스 slot_id 조회 - -**결정: reconcile_positions에 `slot_repo` Optional 파라미터 추가.** - -현재 `reconcile_positions`는 portfolio_id 단위로 동작. C 케이스 차단 시 slot_id가 필요. `slot_repo.find_slot_by_portfolio(instance_key, portfolio_id)` 로 조회. - -- `slot_repo=None`이면 기존 동작(in-memory warning만) 유지 — 하위 호환. -- `instance_key` 파라미터도 추가 (Optional, default="default"). - -## 5. 안전망 경로 분리 구조 확인 - -**확인: safety-sell / kill-switch / watchdog는 `_admit_*` 게이트를 거치지 않는다.** - -- `SafetySellService`: `SlotService.sell()` 직접 호출 → `transit_slot(HOLDING → PENDING_SELL)` 경유. `IntentService._admit_sell` 미경유. -- `KillSwitchService.fire()`: `SlotService` 직접 → `IntentService` 미경유. -- Watchdog 만료 → `SafetySellService` 경유 → 위와 동일. - -따라서 `IntentService._admit_buy`/`_admit_sell`에만 reconcile-warning 게이트를 추가하면 구조적으로 안전망 차단 예외가 보장된다. 별도 우회 플래그 불필요. - -## 6. IntentService warn_store 의존성 추가 - -```python -class IntentService: - def __init__( - self, - ..., - warn_store: Optional[ReconcileWarningStore] = None, - ) -> None: - ... - self._warn_store = warn_store -``` - -`_admit_buy`/`_admit_sell`에서: -```python -if self._warn_store is not None and self._warn_store.is_warned(str(slot.slot_id)): - return IntentDecision(intent.intent_id, "rejected", None, "reconcile_warned") -``` - -게이트 순서: pause 체크 → reconcile_warned 체크 → 기존 capacity/state 체크. - -## 7. slot_to_dict reconcile_warned 필드 - -bulk 조회 패턴 채택 (N+1 방지): - -```python -def slot_to_dict( - s, - *, - snapshot_store=None, - watchdog_store=None, - pause_store=None, - warn_store=None, # ReconcileWarningStore (선택) - watchdog_timeout_sec=120, -) -> dict: - ... - warned_set = warn_store.list_warned() if warn_store is not None else set() - d["reconcile_warned"] = str(s.slot_id) in warned_set -``` - -`make_slots_rpc_handler`에서 `warn_store`를 한 번 받아 `slot_to_dict` 호출 시 전달. - -## 8. IntentService pause 게이트 현황 확인 - -현재 `_admit_buy`/`_admit_sell`에는 pause 체크가 없다. pause 차단은 `messaging_handlers.py`의 handler 레벨에서 처리된다고 예상했으나 실제 코드 확인 결과 `IntentService` 내부에는 pause 체크가 없고, `make_intent_rpc_handler`에도 pause 체크가 없다. - -**실제 pause 차단 위치 재확인 필요**: 기존 pause 게이트가 `_admit_*` 외부에 있을 수 있다. 구현 시 pause 체크와 동일 위치에 reconcile_warned 체크를 배치한다. - -REQ-104 AC-1: "IntentService._admit_buy 및 _admit_sell 수행 시 ... reconcile_warned ... rejected" → IntentService 내부에 두는 것이 REQ 명세를 그대로 따르는 방향. pause 게이트도 이 기회에 IntentService 내부로 통합. - -## 9. daemon.py 배선 범위 - -- `ReconcileWarningStore` 인스턴스 생성 (PostgreSQL 또는 InMemory — 환경) -- `SnapshotScheduler` 생성 시 `warn_store` 주입 -- `IntentService` 생성 시 `warn_store` 주입 -- `make_slots_rpc_handler` 호출 시 `warn_store` 주입 - -## 10. wire 계약 영향 (NFR-4 확인) - -- `SlotState` enum: 변경 없음. -- `ALLOWED_TRANSITIONS`: 변경 없음. -- `trex.slots.req` 응답: `reconcile_warned: bool` 필드 **additive 추가**. 기존 클라이언트(orb/tsl)가 이 필드를 무시해도 JSON 파싱은 정상 동작(추가 필드는 파이썬 dict에서 무시됨). trex-orb-v1/trex-tsl-v1이 slots 응답을 `pydantic` 또는 `TypedDict`로 엄격 파싱하는지 확인 필요 — 불가피하면 팀장에게 보고. (현재 추정: dict 직접 접근 방식으로 추가 필드 무시 → 하위 호환) -- `trex.intent.req` 응답: `reason="reconcile_warned"` 신규 reason 코드 추가. 기존 클라이언트는 unknown reason에 대해 rejected로만 처리하므로 하위 호환. - -## 11. 로깅 구조 (REQ-107) - -차단 전이 시 (`set_warned` 호출 지점, `reconciliation.py`): -```python -_log.warning( - "reconcile_warning set: slot_id=%s portfolio_id=%s symbol=%s " - "case_code=%s broker_qty=%s slot_qty=%s actor=%s", - slot_id, portfolio_id, symbol, case_code, broker_qty, slot_qty, actor, -) -``` - -해제 전이 시 (`clear_warned` 호출 지점, 향후 sync/verify CLI — core-B): -```python -_log.info( - "reconcile_warning cleared: slot_id=%s portfolio_id=%s symbol=%s actor=%s", - slot_id, portfolio_id, symbol, actor, -) -``` - -REQ-084(구조화 로깅) 기반: `logging_setup.py`의 JSON formatter와 일관. key=value 형식으로도 필터링 가능. diff --git a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/implementation.md b/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/implementation.md deleted file mode 100644 index 1b820b6..0000000 --- a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/implementation.md +++ /dev/null @@ -1,64 +0,0 @@ -# BAT-050 Implementation - -- Status: done -- Batch: BAT-050 - -## 변경 파일 목록 - -### 신규 파일 -- `trex_core/adapters/persistence/sql/016_create_slot_reconcile_warning.sql` — DB 마이그레이션 -- `trex_core/adapters/portfolio/reconcile_warning_store.py` — PostgreSQLReconcileWarningStore -- `tests/application/test_reconcile_warning_store.py` — ReconcileWarningStore 단위 테스트 -- `tests/application/test_reconcile_warning_blocking.py` — B/C 차단 + 게이트 통합 테스트 - -### 변경 파일 -- `trex_core/application/portfolio.py` — ReconcileWarningStore Protocol + InMemoryReconcileWarningStore + slot_to_dict warn_store 파라미터 + reconcile_warned 필드 -- `trex_core/application/reconciliation.py` — reconcile_core_holdings B케이스 durable 차단 + reconcile_positions C케이스 durable 차단 + 구조화 로그 -- `trex_core/application/snapshot_scheduler.py` — warn_store 파라미터 추가 + reconcile 호출 시 전달 -- `trex_core/application/intents.py` — warn_store 파라미터 + _admit_buy/_admit_sell reconcile-warning 게이트 -- `trex_core/application/messaging_handlers.py` — make_slots_rpc_handler warn_store 파라미터 + slot_to_dict 전달 -- `trex_core/adapters/portfolio/__init__.py` — PostgreSQLReconcileWarningStore export -- `trex_core/runtime/daemon.py` — warn_store 인스턴스 생성 + intent_service/snapshot_scheduler/slots_rpc_handler 배선 - -## 핵심 구현 요약 - -### 1. ReconcileWarningStore (REQ-102) -- Protocol: `set_warned(slot_id, *, portfolio_id, case_code, actor, reason)`, `clear_warned(slot_id)`, `is_warned(slot_id)`, `list_warned() -> set[str]` -- InMemoryReconcileWarningStore: 테스트용, threading.Lock 보호, dict 기반 upsert -- PostgreSQLReconcileWarningStore: `slot_reconcile_warning` 테이블, ON CONFLICT DO UPDATE, PK=slot_id -- SQL 마이그레이션 `016_create_slot_reconcile_warning.sql` - -### 2. B/C 차단 전이 (REQ-103) -- `reconcile_core_holdings`: elif bq != 0 and slot.qty != bq (B case) → `warn_store.set_warned(case_code='B')` -- `reconcile_positions`: hub_total != broker_total (C case) + slot_repo 주입 시 → `warn_store.set_warned(case_code='C')` -- A case (broker flat): 기존 SlotState.ERROR 전이 유지, warn_store 미사용 -- 모든 파라미터 Optional — None 시 기존 WARN 로그 동작 유지 (하위 호환) - -### 3. 전략 차단 게이트 (REQ-104) -- `IntentService.__init__`: `warn_store=None` Optional 파라미터 추가 -- `_admit_buy`: slot 조회 직후 `warn_store.is_warned(slot_id)` → rejected("reconcile_warned") -- `_admit_sell`: slot 조회 직후 동일 게이트 -- 안전망 경로 차단 예외(OQ-B): safety_sell/kill_switch/watchdog → SlotService.transit_slot() 직접 호출 → IntentService._admit_* 미경유 → 구조적 우회 보장 -- `slot_to_dict`: `warn_store.list_warned()` 벌크 조회, `reconcile_warned: bool` 필드 추가 (additive, NFR-4) -- `make_slots_rpc_handler`: `warn_store: Optional[ReconcileWarningStore]` 파라미터 추가 - -### 4. 구조화 로그 (REQ-107) -- B 케이스: `WARNING reconcile_warning set: slot_id=... portfolio_id=... symbol=... case_code=B broker_qty=... slot_qty=... actor=reconcile` -- C 케이스: `WARNING reconcile_warning set: slot_id=... portfolio_id=... symbol=... case_code=C hub_qty=... broker_qty=... actor=reconcile` -- A 케이스 알람: 기존 REQ-100 경로 그대로 (확인: 기존 alarm_emitter.publish_alarm("core_holding_broker_flat", ...) 유지) - -### 5. daemon.py 배선 순서 조정 -- warn_store 생성을 pause_store 다음으로 배치 -- intent_service 생성을 warn_store 생성 이후로 이동 (warn_store 주입 가능하게) -- snapshot_scheduler, slots_rpc_handler에 warn_store 전달 - -## wire 계약 영향 (NFR-4 확인) -- `SlotState` enum: 변경 없음 (INACTIVE/PENDING_BUY/HOLDING/PENDING_SELL/ERROR 5개 유지) -- `ALLOWED_TRANSITIONS`: 변경 없음 (13개 전이 유지) -- `trex.slots.req` 응답: `reconcile_warned: bool` additive 추가. 기존 클라이언트가 무시해도 JSON dict 파싱 정상 동작 -- `trex.intent.req` 응답: `reason="reconcile_warned"` 신규 코드 — rejected 케이스이므로 기존 클라이언트는 rejected 처리만 하면 됨 - -## 범위 밖 (core-B) -- REQ-105 sync CLI: 미구현 -- REQ-106 verify CLI: 미구현 -- SlotState 추가, INACTIVE→HOLDING 전이 추가: 미구현 (범위 밖) diff --git a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/index.md b/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/index.md deleted file mode 100644 index 68fefdb..0000000 --- a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# BAT-050: reconcile-warning 차단형 게이트 (core-A) - -- Status: release-candidate -- Profile: standard -- Discovery: dcy-034 -- Created: 2026-06-26 - -## Included REQ - -- REQ-102: reconcile-warning durable store — slot_id/pid 키 별도 테이블 -- REQ-103: 차단형 reconcile — B/C 불일치 슬롯 reconcile-warning 전이 -- REQ-104: 전략 차단 게이트 — pause ∨ reconcile-warning 중 하나라도 걸리면 차단 -- REQ-107: 차단/해제 전이 식별 로깅 + 고영향 불일치 알람 -- REQ-108: 차단형 reconcile 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습 - -## Excluded REQ (core-B) - -- REQ-105: sync CLI 명령 — core-B 별도 batch -- REQ-106: verify CLI 명령 — core-B 별도 batch - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) diff --git a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/planning.md b/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/planning.md deleted file mode 100644 index 55c5c35..0000000 --- a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/planning.md +++ /dev/null @@ -1,65 +0,0 @@ -# BAT-050 Planning - -- Status: confirmed -- Batch: BAT-050 -- Profile: standard - -## Included REQ - -- REQ-102: reconcile-warning durable store — slot_id/pid 키 별도 테이블 -- REQ-103: 차단형 reconcile — B/C 불일치 슬롯 reconcile-warning 전이 -- REQ-104: 전략 차단 게이트 — pause ∨ reconcile-warning 중 하나라도 걸리면 차단 -- REQ-107: 차단/해제 전이 식별 로깅 + 고영향 불일치 알람 -- REQ-108: 차단형 reconcile 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습 - -## Delivery Plan - -REQ-102 → REQ-103 → REQ-104 → REQ-107 → REQ-108 인과 사슬: - -1. **REQ-102 먼저**: `ReconcileWarningStore` 포트(Protocol) + `InMemoryReconcileWarningStore` (테스트용) + `PostgreSQLReconcileWarningStore` (어댑터) + SQL 마이그레이션. pause_store 동형 설계. -2. **REQ-103**: `reconcile_core_holdings`와 `reconcile_positions`에 `warn_store: Optional[ReconcileWarningStore]` 파라미터 추가. B 케이스(qty mismatch) → `set_warned`, C 케이스(원장↔브로커) → `set_warned`. `SnapshotScheduler.run_once()`에 warn_store 주입. -3. **REQ-104**: `IntentService._admit_buy`/`_admit_sell`에 reconcile-warning 게이트 추가. `slot_to_dict`에 `reconcile_warned: bool` 필드. `make_slots_rpc_handler`에 `warn_store` 파라미터 추가. 안전망 경로(safety-sell/kill-switch/watchdog)는 `_admit_*` 게이트를 우회하므로 구조적으로 차단 예외. -4. **REQ-107**: 차단/해제 전이 시 구조화 로그(`WARNING`/`INFO`) 추가. A 케이스 알람은 기존 구현 확인. -5. **REQ-108**: 불변 확인 테스트 + 회귀 방지 테스트. `SlotState`·`ALLOWED_TRANSITIONS` 불변 검증. - -구현 순서: -1. SQL 마이그레이션 (`016_create_slot_reconcile_warning.sql`) -2. `adapters/portfolio/reconcile_warning_store.py` (포트 + InMemory + PostgreSQL) -3. `application/portfolio.py` (`ReconcileWarningStore` Protocol + InMemory 통합) -4. `application/reconciliation.py` (B/C 차단 전이 + 로그) -5. `application/snapshot_scheduler.py` (warn_store 주입) -6. `application/intents.py` (게이트 추가 + `ReconcileWarningStore` 의존) -7. `application/portfolio.py` (`slot_to_dict` reconcile_warned 필드) -8. `application/messaging_handlers.py` (`make_slots_rpc_handler` warn_store 파라미터) -9. `runtime/daemon.py` (warn_store 배선) -10. 테스트 작성 - -## Dependencies - -- BAT-049 (alarm-system-dcy-033): 완료(release-candidate) — `AlarmEmitter.publish_alarm` 경로가 REQ-100으로 구현됨. A 케이스 알람은 기존 경로 재사용. -- `pause_store` (BAT-008/020): 동형 설계의 선례. -- `reconcile_core_holdings`, `reconcile_positions` (BAT-029): 차단 전이 추가 대상. - -## Design Gate - -**Design 필요: yes (standard — 포트/어댑터 신설, 게이트 확장, 안전망 경로 분리 구조 확인)** - -- ReconcileWarningStore 포트 위치 결정: `application/portfolio.py` 내 통합 vs 별도 파일. -- `reconcile_positions`에서 slot_id를 얻기 위한 slot_repo 주입 필요 여부 설계. -- `IntentService` 생성자에 warn_store 의존성 추가 방식 설계. -- 안전망 경로가 `_admit_*` 게이트를 구조적으로 우회하는지 명시 확인. -- slots RPC `reconcile_warned` 필드: bulk 조회 vs N+1 trade-off. - -## Milestones - -1. SQL 마이그레이션 + ReconcileWarningStore 포트/어댑터 -2. reconcile_core_holdings/reconcile_positions B/C 차단 전이 -3. IntentService 게이트 + slot_to_dict reconcile_warned 필드 -4. make_slots_rpc_handler warn_store 주입 + daemon.py 배선 -5. 전체 테스트 통과 (신규 + 회귀) - -## Risks - -- R-1(Medium): reconcile_positions에서 C 케이스 slot_id 조회 — portfolio_id로 slot을 찾아야 함. slot_repo 주입이 필요할 수 있음. 완화: design에서 조회 방식 확정. -- R-2(Low): warn_store Optional 처리 누락 시 기존 테스트 더블 깨짐. 완화: 모든 파라미터 Optional로 추가, None 시 기존 동작 유지. -- R-3(Low): slots RPC reconcile_warned 필드가 기존 클라이언트(orb/tsl)에서 unknown 필드로 수신될 때 파싱 오류 발생 가능성. 완화: additive only, 클라이언트가 unknown 필드를 무시하는지 design에서 확인. diff --git a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/verification.md b/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/verification.md deleted file mode 100644 index 20195b1..0000000 --- a/docs/batches/bat-050_20260626_reconcile-warning-blocking-gate-core-a/verification.md +++ /dev/null @@ -1,104 +0,0 @@ -# BAT-050 Verification - -- Status: done -- Batch: BAT-050 - -## pytest 실행 결과 - -실행 명령: `pytest tests/ -q` - -``` -642 passed, 47 skipped in 1.45s -``` - -- 기존 608개 테스트 전부 통과 (회귀 0) -- 신규 34개 테스트 전부 통과 -- 47개 스킵 (DB 연결 필요한 통합 테스트 — 기존과 동일, 변화 없음) -- 실패 0 - -## 신규 테스트 파일 - -### tests/application/test_reconcile_warning_store.py (7개) -- `test_set_warned_is_warned_true` — AC-1 (REQ-102): set/is_warned 기본 동작 -- `test_clear_warned_returns_false` — AC-4: clear 후 False -- `test_set_warned_upsert_same_slot` — AC-3: 동일 slot_id 재삽입 시 1행 유지 -- `test_list_warned_returns_set` — AC-5: 복수 slot_id 조회 -- `test_is_warned_unknown_slot_false` — 미등록 slot → False -- `test_clear_warned_unknown_slot_noop` — 미등록 slot clear → no-op -- `test_list_warned_empty` — 빈 상태 → empty set - -### tests/application/test_reconcile_warning_blocking.py (27개) -- B 케이스 차단 (REQ-103 AC-1): `test_reconcile_core_holdings_b_case_sets_warned`, `..._b_case_code` -- 정상 케이스 오발동 없음 (REQ-103 AC-3, S-4): `test_reconcile_core_holdings_matching_qty_no_warn` -- A 케이스 기존 동작 유지 (REQ-103 AC-4): `test_reconcile_core_holdings_a_case_error_not_warned` -- 하위 호환 (REQ-103 AC-5): `test_reconcile_core_holdings_no_warn_store_backward_compat` -- C 케이스 차단 (REQ-103 AC-2): `test_reconcile_positions_c_case_sets_warned`, `..._c_case_matching_no_warn`, `..._no_warn_store_backward_compat` -- buy intent 차단 (REQ-104 AC-1): `test_intent_service_buy_rejected_when_reconcile_warned` -- sell intent 차단 (REQ-104 AC-2, 외부): `test_intent_service_sell_rejected_when_reconcile_warned` -- 정상 intent 오발동 없음 (REQ-104 AC-3): `test_intent_service_no_warn_admitted_normally` -- 하위 호환 (REQ-104): `test_intent_service_no_warn_store_backward_compat` -- 안전망 우회 확인 (REQ-104 OQ-B): `test_safety_sell_bypasses_reconcile_warning_gate` — SlotService.transit_slot 직접 호출 시 warned 슬롯도 정상 전이 -- slots RPC reconcile_warned 필드 (REQ-104 AC-4~8): 5개 테스트 -- 구조화 로그 (REQ-107): B케이스/C케이스 WARNING 로그 필드 검증 -- SlotState 불변 (REQ-108 NFR-4): `test_slot_state_enum_unchanged`, `test_allowed_transitions_unchanged` -- 단기 인스턴스 오발동 없음 (REQ-108 NFR-2, S-4): `test_short_term_instance_normal_no_warn` -- 자동 정정 금지 (REQ-108 NFR-3): `test_reconcile_no_auto_fix_slot_qty` -- slot_to_dict 필드 (REQ-104): 3개 테스트 - -## AC 검증 매핑 - -| AC | 테스트 | 결과 | -| --- | --- | --- | -| REQ-102 AC-1 (마이그레이션 테이블 생성) | SQL 파일 존재 확인 (DB 스킵) | SQL 파일 생성됨 | -| REQ-102 AC-2 (durable — set 후 재시작 후 True) | InMemory 동치 테스트 / PG는 DB 스킵 | PASS | -| REQ-102 AC-3 (ON CONFLICT DO UPDATE) | `test_set_warned_upsert_same_slot` | PASS | -| REQ-102 AC-4 (clear_warned → False) | `test_clear_warned_returns_false` | PASS | -| REQ-102 AC-5 (list_warned) | `test_list_warned_returns_set` | PASS | -| REQ-102 AC-6 (SlotState 불변) | `test_slot_state_enum_unchanged` | PASS | -| REQ-103 AC-1 (B case → set_warned case_code=B) | `test_reconcile_core_holdings_b_case_sets_warned` | PASS | -| REQ-103 AC-2 (C case → set_warned case_code=C) | `test_reconcile_positions_c_case_sets_warned` | PASS | -| REQ-103 AC-3 (정상 케이스 오발동 없음) | `test_reconcile_core_holdings_matching_qty_no_warn`, `test_reconcile_positions_c_case_matching_no_warn` | PASS | -| REQ-103 AC-4 (A case → ERROR, warn_store 미사용) | `test_reconcile_core_holdings_a_case_error_not_warned` | PASS | -| REQ-103 AC-5 (warn_store=None 하위 호환) | 양 함수 backward_compat 테스트 | PASS | -| REQ-104 AC-1 (buy intent → reconcile_warned 차단) | `test_intent_service_buy_rejected_when_reconcile_warned` | PASS | -| REQ-104 AC-2 (sell intent → reconcile_warned 차단 + 안전망 예외) | `test_intent_service_sell_rejected_when_reconcile_warned`, `test_safety_sell_bypasses_reconcile_warning_gate` | PASS | -| REQ-104 AC-3 (정상 → 오발동 없음) | `test_intent_service_no_warn_admitted_normally` | PASS | -| REQ-104 AC-4 (pause only: paused=T, reconcile_warned=F) | `test_slots_rpc_paused_only` | PASS | -| REQ-104 AC-5 (warn only: reconcile_warned=T) | `test_slots_rpc_reconcile_warned_true` | PASS | -| REQ-104 AC-6 (both: paused=T, reconcile_warned=T) | `test_slots_rpc_both_pause_and_reconcile_warned` | PASS | -| REQ-104 AC-7 (warn_store=None → reconcile_warned=F) | `test_slots_rpc_no_warn_store_reconcile_warned_false` | PASS | -| REQ-104 AC-8 (기존 warned 필드 불변) | `test_slots_rpc_existing_warned_field_unchanged` | PASS | -| REQ-107 AC-1 (B case WARNING 로그 필드) | `test_reconcile_core_holdings_b_case_logging` | PASS | -| REQ-107 AC-2 (C case WARNING 로그 필드) | `test_reconcile_positions_c_case_logging` | PASS | -| REQ-107 AC-3 (sync 해제 INFO 로그) | core-B 범위 — 미검증 (REQ-105/106 미구현) | 해당 없음 | -| REQ-107 AC-4 (verify 해제 INFO 로그) | core-B 범위 | 해당 없음 | -| REQ-107 AC-5 (A case CRITICAL 알람 기존 동작) | 기존 test_alarm_emitter.py 회귀 확인 | PASS (608 기존 통과) | -| REQ-108 AC-1 (durable) | InMemory 동치 + PG 스킵 | PASS | -| REQ-108 AC-2 (정상 단기 오발동 없음, NFR-2, S-4) | `test_short_term_instance_normal_no_warn` | PASS | -| REQ-108 AC-3 (자동 정정 없음, NFR-3) | `test_reconcile_no_auto_fix_slot_qty` | PASS | -| REQ-108 AC-4 (additive 하위 호환, NFR-4) | `test_slots_rpc_no_warn_store_reconcile_warned_false` | PASS | -| REQ-108 AC-5 (SlotState enum 불변, NFR-4) | `test_slot_state_enum_unchanged` | PASS | -| REQ-108 AC-6 (ALLOWED_TRANSITIONS 불변, NFR-4) | `test_allowed_transitions_unchanged` | PASS | - -## wire 계약 무회귀 근거 - -- `SlotState` 5개 값 불변 확인: `test_slot_state_enum_unchanged` PASS -- `ALLOWED_TRANSITIONS` 13개 불변 확인: `test_allowed_transitions_unchanged` PASS -- `trex.slots.req` 기존 필드: 기존 test_slots_handler.py 전부 통과 (회귀 0) -- `reconcile_warned` additive 필드: 기존 클라이언트가 무시 가능 (JSON dict extra key) - -## 미검증 항목 (핸들링) - -- PostgreSQLReconcileWarningStore durable 검증: DB 연결 필요 — InMemory 동치 테스트로 계약 검증, PG 어댑터는 pause_store 동형 패턴으로 구현됨. -- sync/verify CLI clear_warned 해제 로그 (REQ-107 AC-3/4): core-B 범위 (REQ-105/106 미구현). - -## 롤백 - -```bash -git revert # 단일 커밋 롤백 -``` - -또는 마이그레이션 롤백: -```sql -DROP TABLE IF EXISTS slot_reconcile_warning; -``` diff --git a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/design.md b/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/design.md deleted file mode 100644 index b2761bc..0000000 --- a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/design.md +++ /dev/null @@ -1,169 +0,0 @@ -# BAT-051 Design - -- Status: done -- Batch: BAT-051 - -## 1. 전이 엣지 추가 (OQ-A) - -### 결정: `_SYNC_TRANSITIONS` + `ALLOWED_TRANSITIONS` 확장 - -```python -# state.py 추가 -_SYNC_TRANSITIONS: frozenset[tuple[SlotState, SlotState]] = frozenset( - { - (SlotState.INACTIVE, SlotState.HOLDING), # OQ-A: cold-start onboarding (actor=sync) - } -) - -ALLOWED_TRANSITIONS = ( - _BASE_TRANSITIONS | _TO_ERROR_TRANSITIONS | _FROM_ERROR_TRANSITIONS | _SYNC_TRANSITIONS -) -``` - -- `_SYNC_TRANSITIONS`는 `ALLOWED_TRANSITIONS`에 포함(SlotService.transit_slot이 허용 체크함) -- actor=sync, reason=onboarding 으로 transition_log에 식별 가능하게 기록 -- 일반 전략 경로(INACTIVE→PENDING_BUY)는 그대로 유지 -- wire 무영향: SlotState enum 5개 불변, slots RPC 상태값 변화 없음 - -### actor 제한 구현 - -SlotService.transit_slot은 `is_allowed_transition`만 체크하고 actor를 검증하지 않는다. -actor 제한은 **호출 레이어(sync 함수)** 에서 강제: `sync_slot_to_broker`만 이 전이를 호출하고, -일반 전략 경로(IntentService._admit_buy)는 INACTIVE→PENDING_BUY를 사용한다. -transition_log에 `actor="sync"`, `reason="onboarding"` 으로 사후 감사 가능. - -## 2. PortfolioHubCostBasisPort (FR-7) - -```python -# application/ports.py 추가 -class PortfolioHubCostBasisPort(Protocol): - """FR-7: portfolio-hub WAC read port (REQ-043 GET /v1/portfolios/{id}/holdings/cost-basis).""" - def get_cost_basis(self, portfolio_id: str) -> Optional[float]: - """Return WAC avg_price or None (degrade gracefully).""" - ... -``` - -- 어댑터: `adapters/portfolio/hub_read.py`에 `get_cost_basis` 메서드 추가 -- graceful degrade: 포트 없음(None) 또는 예외 → 수량만 갱신 + WARN - -## 3. sync_slot_to_broker (application/reconciliation.py) - -```python -def sync_slot_to_broker( - *, - slot_id: str, - slot_repo, - slot_service, - broker: BrokerPort, - instance_key: str, - warn_store: ReconcileWarningStore, - cost_basis_port=None, # Optional[PortfolioHubCostBasisPort] - dry_run: bool = False, -) -> dict: - """ - Returns: { - "slot_id": str, - "symbol": str, - "portfolio_id": str, - "before": {"qty": ..., "status": ..., "avg_price": ...}, - "after": {"qty": ..., "status": ..., "avg_price": ...}, - "warning_cleared": bool, - "dry_run": bool, - "skipped": bool, # 브로커-only 슬롯(자동 편입 금지) — 해당 안됨(슬롯 있으므로) - "unmanaged": bool, # 브로커에만 있고 슬롯 없음 → 미편입 경고 - } - """ -``` - -### 동작 흐름 -1. `fetch_slot(slot_id)` → 슬롯 확인 -2. `broker.get_balance()` → 해당 symbol 수량(bq) -3. bq == 0: 슬롯 qty가 0 또는 None → WARN, skip (A 케이스 보호) -4. bq > 0: - - INACTIVE 슬롯: `transit_slot(INACTIVE→HOLDING, actor="sync", reason="onboarding")` - + qty=bq, avg_price=cost_basis(if available) - - HOLDING 슬롯: qty만 갱신(slot_service 직접 update 또는 transit_slot 재진입 없이 update_slot) - - PENDING_* 슬롯: skip + WARN (in-flight, 수정 불가) -5. 갱신 후 slotqty == bq → `clear_warned(slot_id)` -6. dry_run: 실제 쓰기 없이 before/after 예상치 반환 - -## 4. verify_slot_reconcile (application/reconciliation.py) - -```python -def verify_slot_reconcile( - *, - portfolio_id: str, - store: SnapshotStore, - broker: BrokerPort, - slot_repo, - instance_key: str, - warn_store: ReconcileWarningStore, -) -> dict: - """ - Returns: { - "portfolio_id": str, - "slot_id": str | None, - "result": "cleared" | "rejected" | "not_c_case" | "not_warned" | "slot_not_found", - "hub_qty": int, - "broker_qty": int, - "reason": str | None, - } - """ -``` - -### 동작 흐름 -1. `slot_repo.find_slot_by_portfolio(instance_key, portfolio_id)` → 슬롯 확인 -2. `warn_store.is_warned(slot_id)`: - - False → "not_warned" (이미 해제, 0 종료코드) - - True → warn_store.get_case_code() 필요 → 단순화: list_warned_with_meta 또는 is_warned의 리턴값 확장 - -### case_code 조회 설계 -`ReconcileWarningStore.get_warning_info(slot_id)` 추가: -```python -def get_warning_info(self, slot_id: str) -> Optional[dict]: - """Returns {"portfolio_id", "case_code", "actor", "reason"} or None.""" -``` -InMemoryReconcileWarningStore는 `_warned[slot_id]` dict 반환. -PostgreSQLReconcileWarningStore는 SELECT 1행 반환. - -3. case_code != 'C' → "not_c_case" + 안내 + 0 종료코드 -4. `reconcile_positions`(store, broker) 재실행 → hub_qty, broker_qty 대조 -5. 일치: `clear_warned(slot_id)` → "cleared" -6. 불일치: warning 유지 → "rejected" + hub_qty/broker_qty/차이 + 비-0 종료코드 - -## 5. CLI 서브커맨드 (runtime/cli.py) - -### sync -``` -trex-core sync [--portfolio-id ] [--all] [--dry-run] [--yes] [--live] [--json] - [--config ] [--instance ] -``` -- `--portfolio-id` 또는 `--all` 중 하나 필수 -- direct-DB: `PostgreSQLReconcileWarningStore`, `BrokerPort`(KIS/paper), `slot_repo` -- broker/slot_repo 조립은 `DaemonConfig`에서 구성 - -### verify -``` -trex-core verify [--portfolio-id ] [--json] - [--config ] [--instance ] -``` -- `--portfolio-id` 없으면 모든 C-warned 슬롯 일괄 재대조 -- direct-DB + read-중심 - -## 6. Cross-service 영향 - -- `(INACTIVE, HOLDING)` 전이 추가: wire 상태값 불변, trex-orb-v1/tsl-v1 영향 없음 -- slots RPC 필드 변경 없음 (reconcile_warned 필드는 BAT-050에서 이미 추가됨) -- `PortfolioHubCostBasisPort`: portfolio-hub REQ-043(`GET /v1/portfolios/{id}/holdings/cost-basis`) 의존. graceful degrade로 미가용 시 동작은 유지됨. - -## 7. ReconcileWarningStore 확장 - -기존 Protocol에 `get_warning_info` 추가: -```python -def get_warning_info(self, slot_id: str) -> Optional[dict]: - """Return {"portfolio_id", "case_code", "actor", "reason"} or None.""" - ... -``` -- InMemoryReconcileWarningStore: `_warned.get(slot_id)` 복사본 반환 -- PostgreSQLReconcileWarningStore: SELECT row → dict 반환 -- 기존 메서드 시그니처 불변 (additive) diff --git a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/implementation.md b/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/implementation.md deleted file mode 100644 index 8430f6d..0000000 --- a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/implementation.md +++ /dev/null @@ -1,56 +0,0 @@ -# BAT-051 Implementation - -- Status: done -- Batch: BAT-051 - -## 변경 파일 목록 - -| 파일 | 변경 유형 | 내용 | -|------|-----------|------| -| `trex_core/domain/slot/state.py` | 수정 | `_SYNC_TRANSITIONS` + `ALLOWED_TRANSITIONS` 확장 (INACTIVE→HOLDING OQ-A) | -| `trex_core/application/portfolio.py` | 수정 | `ReconcileWarningStore` Protocol + `InMemoryReconcileWarningStore`에 `get_warning_info` 추가 | -| `trex_core/application/ports.py` | 수정 | `PortfolioHubCostBasisPort` Protocol 추가 (FR-7) | -| `trex_core/application/reconciliation.py` | 수정 | module docstring 정리(case_code 혼동 수정), `sync_slot_to_broker` 신규, `verify_slot_reconcile` 신규 | -| `trex_core/application/intents.py` | 수정 | `_check_reconcile_warned` 죽은 메서드 제거 (core-A QC minor) | -| `trex_core/adapters/portfolio/hub_read.py` | 수정 | `get_cost_basis` 메서드 추가 | -| `trex_core/adapters/portfolio/reconcile_warning_store.py` | 수정 | `get_warning_info` 메서드 추가 (PostgreSQL) | -| `trex_core/runtime/cli.py` | 수정 | `sync`/`verify` 서브커맨드 + `_cmd_sync`/`_cmd_verify` 핸들러 추가 | -| `tests/domain/slot/test_state.py` | 수정 | INACTIVE→HOLDING 전이 추가 반영, disallowed 케이스 변경, 새 테스트 3개 추가 | -| `tests/domain/slot/test_service.py` | 수정 | `test_disallowed_transition_raises` — INACTIVE→PENDING_SELL로 변경 | -| `tests/application/test_reconcile_warning_blocking.py` | 수정 | `test_allowed_transitions_unchanged` → `test_allowed_transitions_count` (14개로 갱신) | -| `tests/application/test_sync.py` | 신규 | REQ-105 AC 전체 + OQ-A + NFR-3 커버 (19개 테스트) | -| `tests/application/test_verify.py` | 신규 | REQ-106 AC 전체 + OQ-C + get_warning_info 커버 (14개 테스트) | -| `tests/runtime/test_cli_operator.py` | 수정 | sync/verify 파서 + 게이트 테스트 추가 (13개 테스트) | - -## 핵심 구현 결정 - -### 1. 전이 엣지 (OQ-A) -- `_SYNC_TRANSITIONS = frozenset({(INACTIVE, HOLDING)})`를 `state.py`에 추가 -- `ALLOWED_TRANSITIONS`에 합집합으로 포함 (13→14개) -- actor 제한은 호출 레이어: `sync_slot_to_broker`만 이 전이를 호출, transition_log에 `actor="sync"` `reason="onboarding"` 기록 -- SlotState enum 5개 불변, wire 계약 무영향 - -### 2. sync 동작 -- INACTIVE + broker_qty > 0: `transit_slot(INACTIVE→HOLDING, actor="sync", reason="onboarding")` -- HOLDING + qty 불일치: `update_slot` 직접 (transit_slot 재진입 없음) -- PENDING_*: skip + WARN (in-flight 보호, NFR-3) -- broker_qty == 0: skip + WARN (A-case guard) -- after qty == broker_qty: `clear_warned(slot_id)` → `warning_cleared=True` - -### 3. verify 동작 -- `get_warning_info(slot_id)` 로 case_code 확인 -- case_code != 'C': `not_c_case` + 0 종료 (OQ-C) -- hub_qty vs broker_qty: snapshot_store vs broker.get_balance() -- 일치: `clear_warned` + `store.set_warning(False)` → "cleared" -- 불일치: warning 유지 → "rejected" + 사유 (강제 해제 없음) -- 슬롯 qty 변경 없음 (read-centric 불변) - -### 4. core-A QC cleanup -- `intents.py`의 `_check_reconcile_warned` 제거: 실제 게이트는 `_admit_buy`/`_admit_sell`에 인라인으로 구현돼 있었음 (BAT-050) -- `reconciliation.py` module docstring: case_code='B'/'C' 설명 명확화 - -### 5. CLI sync guard -- `--portfolio-id` 또는 `--all` 중 하나 필수 -- dry-run 아닐 때: `--yes` 필수 (확인 프롬프트) -- live 계좌: `--yes --live` 이중 확인 -- verify: read-centric이므로 `--yes`/`--live` 게이트 없음 diff --git a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/index.md b/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/index.md deleted file mode 100644 index edd81fa..0000000 --- a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# BAT-051: sync/verify CLI cold-start onboarding (core-B) - -- Status: release-candidate -- Profile: standard -- Discovery: dcy-034 -- Created: 2026-06-26 - -## Included REQ - -- REQ-105: trex-core sync — 슬롯 수량을 브로커에 맞춰 갱신 + reconcile-warning 해제 -- REQ-106: trex-core verify — C 불일치 슬롯 재대조 + warning 해제 - -## Also in scope (cleanup) - -- core-A QC minor 1: `trex_core/application/intents.py` `_check_reconcile_warned` 죽은 메서드 제거 -- core-A QC minor 2: `trex_core/application/reconciliation.py` docstring 혼동 문구 수정 - -## Prerequisites (core-A, BAT-050, merged) - -- `ReconcileWarningStore` (set_warned / clear_warned / is_warned / list_warned) -- reconcile B/C → set_warned -- IntentService reconcile-warning gate - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) diff --git a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/planning.md b/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/planning.md deleted file mode 100644 index 4cfd3e6..0000000 --- a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/planning.md +++ /dev/null @@ -1,59 +0,0 @@ -# BAT-051 Planning - -- Status: done -- Batch: BAT-051 - -## Scope - -### REQ-105 (sync) -- `trex-core sync [--portfolio-id | --all]` 서브커맨드 (direct-DB, pause/resume 방식) -- 대상: portfolio-hub 유니버스에 있어 슬롯이 생성된 종목(B-warned + INACTIVE 포함) -- 동작: 슬롯 qty ← 브로커, INACTIVE + broker qty>0 → HOLDING 전이(actor=sync, reason=onboarding) -- avg_price seed: PortfolioHubCostBasisPort(`GET /v1/portfolios/{id}/holdings/cost-basis`) — graceful degrade -- 갱신 후 qty 일치 시 `clear_warned` -- --dry-run, --yes, --live 이중 확인 게이트 -- before/after 출력 - -### REQ-106 (verify) -- `trex-core verify [--portfolio-id ]` 서브커맨드 (direct-DB) -- 동작: `reconcile_positions` 재사용(브로커 경유 2-대조), 일치 → clear_warned, 불일치 → 거부 -- read-중심: 슬롯 qty 변경 없음 -- C 전용: B-warned 슬롯에 호출 시 "C 케이스 대상 아님" + 0 종료코드 -- --json 출력 지원 - -### OQ-A 전이엣지 -- `ALLOWED_TRANSITIONS`에 `(INACTIVE, HOLDING)` 추가 -- `_SYNC_TRANSITIONS` 별도 집합(sync/onboarding actor 제한 용도) -- 일반 전략 경로(INACTIVE→PENDING_BUY→HOLDING) 유지 - -### core-A QC cleanup -- `intents.py` `_check_reconcile_warned` 죽은 메서드 제거 -- `reconciliation.py` docstring 혼동 문구 수정 - -## 파일 영향 - -| 파일 | 변경 유형 | -|------|-----------| -| `domain/slot/state.py` | ALLOWED_TRANSITIONS에 (INACTIVE, HOLDING) 추가 | -| `application/ports.py` | PortfolioHubCostBasisPort 포트 정의 | -| `application/reconciliation.py` | sync_slot_to_broker 함수 신규, verify_slot_reconcile 함수 신규, docstring 수정 | -| `application/intents.py` | _check_reconcile_warned 제거 | -| `adapters/portfolio/hub_read.py` | get_cost_basis 메서드 추가 | -| `runtime/cli.py` | sync, verify 서브커맨드 추가 | -| `tests/domain/test_slot_transitions.py` | 전이엣지 actor 제한 테스트 | -| `tests/application/test_sync.py` | sync 동작 테스트 | -| `tests/application/test_verify.py` | verify 동작 테스트 | -| `tests/runtime/test_cli_operator.py` | sync/verify CLI 파싱 + 동작 테스트 추가 | - -## 불변 사항 - -- SlotState enum 5개 불변(wire 무영향) -- (INACTIVE, HOLDING) 전이 추가는 actor 식별 가능(reason/actor log) — 일반 전략 경로와 구분 -- 기존 B/C reconcile-warning 게이트 회귀 없음 -- sync는 브로커-only(슬롯 없음) 종목 자동 편입 금지 - -## 완료 기준 - -- pytest 전체 통과(기존 회귀 0) -- AC-1~10(REQ-105), AC-1~6(REQ-106) 전부 테스트로 커버 -- 전이엣지 불변: enum 5개 + (INACTIVE, HOLDING) 추가만 diff --git a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/verification.md b/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/verification.md deleted file mode 100644 index 0ec0d60..0000000 --- a/docs/batches/bat-051_20260626_sync-verify-cli-cold-start-core-b/verification.md +++ /dev/null @@ -1,109 +0,0 @@ -# BAT-051 Verification - -- Status: done -- Batch: BAT-051 - -## pytest 실행 결과 - -실행 명령: `pytest tests/ -q` - -``` -683 passed, 47 skipped in 1.56s -``` - -- 기존 642개 테스트 전부 통과 (회귀 0) -- 신규 41개 테스트 전부 통과 -- 47개 스킵 (DB 연결 필요한 통합 테스트 — 기존과 동일, 변화 없음) -- 실패 0 - -## 신규 테스트 파일 - -### tests/application/test_sync.py (19개) -- `test_sync_inactive_to_holding_on_cold_start` — AC-1: INACTIVE→HOLDING, qty=broker, warning cleared -- `test_sync_inactive_to_holding_sets_transition_log` — AC-10: actor=sync, reason=onboarding -- `test_sync_holding_qty_mismatch_updated` — AC-2: HOLDING qty 갱신 + warning cleared -- `test_sync_seeds_avg_price_from_cost_basis_port` — AC-4: WAC seed (INACTIVE) -- `test_sync_seeds_avg_price_holding_with_cost_basis` — AC-4: WAC seed (HOLDING) -- `test_sync_degrades_gracefully_no_cost_basis_port` — AC-5: no port → qty only -- `test_sync_degrades_gracefully_cost_basis_port_raises` — AC-5: port exception → WARN + qty -- `test_sync_degrades_gracefully_cost_basis_returns_none` — AC-5: port returns None → unchanged avg -- `test_sync_dry_run_no_write` — AC-6: no write, before/after preview -- `test_sync_warning_not_cleared_when_qty_still_mismatched` — AC-8: broker_qty=0 skip → not cleared -- `test_sync_pending_buy_slot_skipped` — NFR-3: PENDING_BUY skip -- `test_sync_pending_sell_slot_skipped` — NFR-3: PENDING_SELL skip -- `test_sync_invalid_slot_id_returns_error` — invalid UUID → error -- `test_sync_missing_slot_returns_not_found` — slot not found → not_found -- `test_sync_unwarned_slot_still_syncs_qty` — unwarned slot sync (no error) -- `test_general_strategy_path_still_uses_pending_buy` — OQ-A: 일반 경로 유지 - -### tests/application/test_verify.py (14개) -- `test_verify_c_case_match_clears_warning` — AC-1: C-case 일치 → cleared -- `test_verify_c_case_mismatch_rejected` — AC-2: C-case 불일치 → rejected + reason -- `test_verify_does_not_change_slot_qty_on_clear` — AC-3: qty 불변 (cleared 경우) -- `test_verify_does_not_change_slot_qty_on_reject` — AC-3: qty 불변 (rejected 경우) -- `test_verify_not_warned_returns_not_warned` — AC-4: 경고 없음 → not_warned (idempotent) -- `test_verify_b_warned_slot_returns_not_c_case` — OQ-C: B-warned → not_c_case + sync 안내 -- `test_verify_slot_not_found` — 슬롯 없음 → slot_not_found -- `test_get_warning_info_returns_dict` — get_warning_info 기본 동작 -- `test_get_warning_info_returns_none_for_missing` — 미등록 slot → None -- `test_get_warning_info_after_clear_returns_none` — clear 후 None -- `test_verify_empty_snapshot_treats_hub_qty_as_zero` — 빈 snapshot → hub_qty=0 → rejected - -### tests/runtime/test_cli_operator.py (13개 추가) -- sync 파서 테스트 (portfolio_id, all, dry-run, live): 4개 -- sync 게이트 테스트 (no target, no-yes, live gate): 3개 -- verify 파서 테스트 (portfolio_id, no-pid, json): 3개 -- verify 게이트 테스트 (no live flag needed): 1개 - -### tests/domain/slot/test_state.py (3개 추가) -- `test_sync_transition_inactive_to_holding_is_allowed` — OQ-A: 전이 허용 확인 -- `test_slot_state_enum_still_5_values` — wire 불변 (enum 5개) -- `test_normal_strategy_path_unchanged` — 일반 경로(INACTIVE→PENDING_BUY) 유지 - -## 수정된 기존 테스트 - -| 파일 | 테스트 | 변경 이유 | -|------|--------|-----------| -| `tests/domain/slot/test_state.py` | `test_allowed_transitions_set_size` | 13→14 (INACTIVE→HOLDING 추가) | -| `tests/domain/slot/test_state.py` | `test_disallowed_transition` parametrize | INACTIVE→HOLDING 제거(허용됨), 나머지 유지 | -| `tests/domain/slot/test_state.py` | `test_allowed_transition` parametrize | INACTIVE→HOLDING 추가 | -| `tests/domain/slot/test_service.py` | `test_disallowed_transition_raises` | INACTIVE→PENDING_SELL로 변경 (INACTIVE→HOLDING 이제 허용) | -| `tests/application/test_reconcile_warning_blocking.py` | `test_allowed_transitions_unchanged` → `test_allowed_transitions_count` | 14개 + INACTIVE→HOLDING now in ALLOWED | - -## AC 검증 매핑 (REQ-105) - -| AC | 테스트 | 결과 | -|----|--------|------| -| AC-1 | test_sync_inactive_to_holding_on_cold_start | PASS | -| AC-2 | test_sync_holding_qty_mismatch_updated | PASS | -| AC-3 | 브로커-only 미편입 — parser layer (--portfolio-id 필수) | PASS (parser test) | -| AC-4 | test_sync_seeds_avg_price_from_cost_basis_port × 2 | PASS | -| AC-5 | test_sync_degrades_gracefully_* × 3 | PASS | -| AC-6 | test_sync_dry_run_no_write | PASS | -| AC-7 | test_sync_live_requires_live_flag | PASS | -| AC-8 | test_sync_warning_not_cleared_when_qty_still_mismatched | PASS | -| AC-9 | parser: test_sync_parser_all_accepted | PASS | -| AC-10 | test_sync_inactive_to_holding_sets_transition_log | PASS | - -## AC 검증 매핑 (REQ-106) - -| AC | 테스트 | 결과 | -|----|--------|------| -| AC-1 | test_verify_c_case_match_clears_warning | PASS | -| AC-2 | test_verify_c_case_mismatch_rejected | PASS | -| AC-3 | test_verify_does_not_change_slot_qty_* × 2 | PASS | -| AC-4 | test_verify_not_warned_returns_not_warned | PASS | -| AC-5 | test_verify_parser_json_accepted (구조) | PASS | -| AC-6 | test_verify_parser_no_portfolio_id_accepted | PASS | - -## Wire/전이 불변 입증 - -- SlotState enum 5개 불변: `test_slot_state_enum_still_5_values` PASS -- ALLOWED_TRANSITIONS 14개 (13+1 sync-only 엣지): `test_allowed_transitions_set_size` PASS -- (INACTIVE, HOLDING) 전이 추가: `test_sync_transition_inactive_to_holding_is_allowed` PASS -- 일반 전략 경로 무영향: `test_normal_strategy_path_unchanged` PASS -- slots RPC 스키마 필드 변경 없음 (reconcile_warned 필드는 BAT-050에서 이미 추가) - -## 롤백 1줄 - -`git revert ` — 모든 변경은 단일 커밋, 기존 wire 계약 불변이므로 revert 후 trex-orb-v1/tsl-v1 영향 없음. diff --git a/docs/batches/bat-052_20260626_wac-realized-pnl-report/design.md b/docs/batches/bat-052_20260626_wac-realized-pnl-report/design.md deleted file mode 100644 index 725a282..0000000 --- a/docs/batches/bat-052_20260626_wac-realized-pnl-report/design.md +++ /dev/null @@ -1,106 +0,0 @@ -# BAT-052 Design - -## OQ-2 — 기간(from_dt/to_dt) 기본값 - -결정: **오늘(KST 일 경계)**. -`kst_day_bounds_utc()`가 반환하는 UTC [start, end) 범위에서 -`from_dt = start.date().isoformat()`, `to_dt = (end - timedelta(seconds=1)).date().isoformat()` -= 오늘 KST date 문자열을 portfolio-hub에 전달한다. -`--from`/`--to` CLI 옵션은 이 batch에서 추가하지 않음(후속). - -## OQ-3 — 매수 전용 자산 수수료 표시 (CHG-20260626-02로 결정 변경) - -초안 결정: ledger_outbox 당일 매수 fee 합산 → `buy_fee_only_assets` 표시. - -**변경(CHG-20260626-02)**: portfolio-hub WAC는 매수 fee를 cost basis에 편입한다. 따라서 매수 전용일에 별도 수수료 라인을 출력하면 이중계상이 발생한다. `buy_fee_only_assets`·`buy_fee_by_asset` 헬퍼는 최종 구현에서 제거됐다. 매수 전용일에 실현 PnL = 0이고 별도 수수료 라인 없음. 수수료는 매도 시 cost basis를 통해 실현손익에 반영된다. - -## OQ-4 — 조회 단위 - -결정: **portfolio_id별 N콜 (슬롯↔portfolio 1:1)**. -`_cmd_report`에서 DB에서 읽은 portfolio_id 목록(holdings + pending)을 union해 -각 portfolio_id별로 `get_realized_pnl(portfolio_id, from_dt, to_dt)` 호출. -종목 필터 없이 전체 조회(symbol=None). 자산별 합산은 RealizedPnlResult.items에서 수집. - -## 데이터 모델 (신규) - -```python -# trex_core/application/portfolio.py 추가 - -@dataclass(frozen=True) -class RealizedPnlItem: - symbol: str | None - realized_pnl: float - sell_qty: float - # portfolio-hub 응답 보존 필드 - cost_basis_per_unit: float = 0.0 - fee_amount: float = 0.0 - tax_amount: float = 0.0 - -@dataclass(frozen=True) -class RealizedPnlResult: - items: list[RealizedPnlItem] - cost_basis_from: str | None # ISO date string (portfolio-hub datetime -> date str) - data_completeness_warning: bool - total_realized_pnl: float -``` - -## OperatorReport 확장 - -```python -@dataclass -class OperatorReport: - # 기존 필드 유지 - holdings: list[Holding] = ... - pending: list[PendingOrder] = ... - fills_count: int = 0 - realized_pnl: float = 0.0 # 기존 당일 net (유지) - realized_pnl_by_asset: dict = ... # 기존 (유지) - # 신규 WAC 필드 - realized_pnl_wac: float = 0.0 - realized_pnl_wac_by_asset: dict[str, float] = ... - # buy_fee_only_assets: 제거됨 (CHG-20260626-02 — cost basis 편입, 이중계상 방지) - data_completeness_warning: bool = False - wac_unavailable: bool = False # REQ-111 degrade flag - wac_unavailable_reason: str = "" -``` - -## _cmd_report 흐름 - -``` -1. DB에서 holdings, pending, trades 읽기 (기존 유지) -2. build_report(holdings, pending, trades) -> rep (기존 당일 net, 기존 유지) -3. portfolio-hub base_url 설정 있으면: - a. from_dt, to_dt = 오늘 KST date - b. 대상 portfolio_id = {h.portfolio_id for h in holdings} | {p.portfolio_id for p in pending} - c. 각 pid별 get_realized_pnl(pid, from_dt, to_dt) 호출 - d. RealizedPnlResult 합산 → rep.realized_pnl_wac, realized_pnl_wac_by_asset - e. (제거됨 CHG-20260626-02) buy_fee_only_assets — 매수 fee cost basis 편입으로 별도 라인 불필요 - f. data_completeness_warning = any(result.data_completeness_warning) -4. portfolio-hub 미설정 or PortfolioHubUnavailableError: - → rep.wac_unavailable = True, wac_unavailable_reason = ... -5. render_text/render_json -``` - -## render_text WAC 섹션 (신규) - -``` -=== WAC 실현 PnL {total:,.0f} === (또는 "portfolio-hub unavailable — realized PnL unavailable") - {symbol}: {pnl:,.0f} - [!] 이력 불완전 경고 (data_completeness_warning=True일 때만) -``` - -> CHG-20260626-02: "매수 전용 자산 수수료" 섹션은 최종 구현에서 제거됐다. 매수 fee는 cost basis에 편입되므로 별도 라인 표시 시 이중계상이 발생한다. - -## 계약 매핑 (portfolio-hub 응답 필드명) - -| portfolio-hub 필드 | trex-core 필드 | -| --- | --- | -| total_realized_pnl | RealizedPnlResult.total_realized_pnl | -| items[].realized_pnl | RealizedPnlItem.realized_pnl | -| items[].quantity | RealizedPnlItem.sell_qty | -| items[].symbol | RealizedPnlItem.symbol | -| items[].cost_basis_per_unit | RealizedPnlItem.cost_basis_per_unit | -| items[].fee_amount | RealizedPnlItem.fee_amount | -| items[].tax_amount | RealizedPnlItem.tax_amount | -| cost_basis_from | RealizedPnlResult.cost_basis_from (datetime → isoformat str) | -| data_completeness_warning | RealizedPnlResult.data_completeness_warning (str|None → bool) | diff --git a/docs/batches/bat-052_20260626_wac-realized-pnl-report/implementation.md b/docs/batches/bat-052_20260626_wac-realized-pnl-report/implementation.md deleted file mode 100644 index 867d179..0000000 --- a/docs/batches/bat-052_20260626_wac-realized-pnl-report/implementation.md +++ /dev/null @@ -1,30 +0,0 @@ -# BAT-052 Implementation - -## 변경 파일 요약 - -| 파일 | 변경 내용 | -| --- | --- | -| `trex_core/application/portfolio.py` | RealizedPnlItem, RealizedPnlResult 데이터클래스 추가; PortfolioHubReadPort에 get_realized_pnl 메서드 추가 | -| `trex_core/application/operator_report.py` | OperatorReport에 WAC 3필드(realized_pnl_wac, realized_pnl_wac_by_asset, data_completeness_warning) + degrade 2필드(wac_unavailable, wac_unavailable_reason) 추가; apply_wac_to_report 헬퍼 추가; render_text/render_json WAC/degrade 섹션 추가. CHG-20260626-02: buy_fee_only_assets·buy_fee_by_asset 제거(cost basis 편입, 이중계상 방지) | -| `trex_core/adapters/portfolio/hub_read.py` | get_realized_pnl 구현 추가 (GET /v1/portfolios/{id}/pnl/realized, from/to/symbol query params) | -| `trex_core/adapters/portfolio/stub_read.py` | get_realized_pnl stub 추가 (고정 RealizedPnlResult, realized_pnl_result 교체 가능) | -| `trex_core/runtime/cli.py` | _cmd_report에 WAC 경로(portfolio-hub 설정 시) + degrade 경로(미설정/Unavailable) 통합 | -| `tests/application/test_operator_report.py` | TestApplyWacToReport, TestWacDegrade, TestNfr2Regression 클래스 추가 | -| `tests/adapters/portfolio/test_hub_read.py` | get_realized_pnl HTTP 계약 테스트 7개 추가 | -| `tests/adapters/portfolio/test_stub_read.py` | get_realized_pnl stub 테스트 3개 추가 | -| `tests/runtime/test_cli_report_wac.py` | 신규 파일: CLI report WAC/degrade 통합 테스트 | -| `docs/batches/bat-052_*/` | 배치 문서 (index, planning, design, implementation, verification) | -| `docs/batches/index.md` | BAT-052 항목 추가 | - -## 주요 설계 결정 이행 - -- OQ-2: from_dt/to_dt = 오늘 KST 날짜 (kst_day_bounds_utc().start.astimezone(KST).date()) -- OQ-3(CHG-20260626-02): buy_fee_only_assets 제거. 매수 fee는 portfolio-hub cost basis에 편입되므로 별도 라인 불필요(이중계상 방지). NFR-2 엄격화: 동일날 매수+매도 시 WAC == 당일 net 성립. -- OQ-4: portfolio_id별 N콜 (holdings+pending union) -- NFR-2: realized_pnl(trades) 함수 및 기존 OperatorReport 필드 유지 - -## 유지된 기존 동작 - -- whoami, get_portfolios, get_cost_basis 동작 불변 -- realized_pnl(trades) 함수 유지 -- OperatorReport.realized_pnl, realized_pnl_by_asset 유지 diff --git a/docs/batches/bat-052_20260626_wac-realized-pnl-report/index.md b/docs/batches/bat-052_20260626_wac-realized-pnl-report/index.md deleted file mode 100644 index c3d94c2..0000000 --- a/docs/batches/bat-052_20260626_wac-realized-pnl-report/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# BAT-052: WAC 실현손익 리포트 통합 (portfolio-hub /pnl/realized) - -- Status: release-candidate -- Profile: standard -- Discovery: dcy-035 -- Created: 2026-06-26 - -## Included REQ - -- REQ-109: portfolio-hub /pnl/realized read 포트+어댑터+stub -- REQ-110: 운영리포트 WAC 실현손익 통합 (전 인스턴스 공통) -- REQ-111: portfolio-hub 미가용 degrade (폴백 없음) - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) diff --git a/docs/batches/bat-052_20260626_wac-realized-pnl-report/planning.md b/docs/batches/bat-052_20260626_wac-realized-pnl-report/planning.md deleted file mode 100644 index c0c8263..0000000 --- a/docs/batches/bat-052_20260626_wac-realized-pnl-report/planning.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-052 Planning - -## 목표 - -portfolio-hub WAC 실현손익 API(/pnl/realized)를 trex-core 운영 리포트에 통합한다. -기존 당일 net 방식을 대체하되 기존 함수는 유지한다. - -## 포함 REQ 요약 - -| REQ | 핵심 내용 | -| --- | --- | -| REQ-109 | get_realized_pnl 포트+어댑터+stub | -| REQ-110 | OperatorReport에 WAC 필드 추가, _cmd_report에 WAC 경로 통합 | -| REQ-111 | portfolio-hub 미가용 시 degrade (폴백 없음) | - -## 변경 파일 목록 (예상) - -| 파일 | 변경 종류 | -| --- | --- | -| `trex_core/application/portfolio.py` | RealizedPnlItem, RealizedPnlResult 데이터클래스 추가; PortfolioHubReadPort에 get_realized_pnl 추가 | -| `trex_core/application/operator_report.py` | OperatorReport에 WAC 필드 추가; render_text/render_json 갱신; build_report_wac 추가 | -| `trex_core/adapters/portfolio/hub_read.py` | get_realized_pnl 구현 추가 | -| `trex_core/adapters/portfolio/stub_read.py` | get_realized_pnl stub 추가 | -| `trex_core/runtime/cli.py` | _cmd_report에 WAC 경로+degrade 통합 | -| `tests/application/test_operator_report.py` | WAC 필드 테스트, completeness 경고, degrade 테스트 추가 | -| `tests/adapters/portfolio/test_hub_read.py` | get_realized_pnl HTTP 계약 테스트 추가 | -| `tests/adapters/portfolio/test_stub_read.py` | get_realized_pnl stub 테스트 추가 | -| `tests/runtime/test_cli_report.py` (신규) | WAC 경로·degrade·JSON 형태 통합 테스트 | - -## 위험 / 고려사항 - -1. `PortfolioHubReadPort` Protocol 확장: 기존 구현체(StubPortfolioRead, PortfolioHubReadAdapter) 모두 갱신 필요. -2. `data_completeness_warning`은 portfolio-hub에서 `str | None`으로 오지만 trex-core에서는 `bool`로 변환한다(truthy check). -3. CHG-20260626-02: 매수 fee가 portfolio-hub cost basis에 편입되므로 매수 전용일에 별도 수수료 라인을 출력하지 않는다. `buy_fee_only_assets`·`buy_fee_by_asset` 헬퍼는 최종 구현에서 제거됐다. -4. 기존 `realized_pnl(trades)` 함수 및 기존 OperatorReport 필드 삭제하지 않는다(NFR-2 회귀 보존). -5. _cmd_report에서 portfolio-hub base_url 미설정 시 REQ-111 degrade 경로로 진입한다. - -## 의존성 확인 - -- httpx: 기존 어댑터 패턴과 동일 -- psycopg: 기존 _cmd_report에서 이미 사용 -- portfolio-hub 계약: `GET /v1/portfolios/{portfolio_id}/pnl/realized` query from_dt(alias=from), to_dt(alias=to), symbol(optional) - - 응답: RealizedPnlResponse { total_realized_pnl, items, cost_basis_from(datetime|None), data_completeness_warning(str|None) } - - 각 item: { symbol, executed_at, quantity, sell_price, cost_basis_per_unit, fee_amount, tax_amount, realized_pnl, order_id, execution_id } diff --git a/docs/batches/bat-052_20260626_wac-realized-pnl-report/verification.md b/docs/batches/bat-052_20260626_wac-realized-pnl-report/verification.md deleted file mode 100644 index 93b644f..0000000 --- a/docs/batches/bat-052_20260626_wac-realized-pnl-report/verification.md +++ /dev/null @@ -1,71 +0,0 @@ -# BAT-052 Verification - -## 실행 명령 - -``` -python3 -m pytest --tb=short -q -``` - -## 결과 - -``` -717 passed, 47 skipped in 2.34s -``` - -- 통과: 717 -- 스킵: 47 (DB/네트워크 필요 통합 테스트 — 기존 패턴) -- 실패: 0 - -## 검증 완료 판정 - -- QC 결과: PASS -- confirm-batch-verification: 완료 (2026-06-26) -- PR: #33 -- 머지 커밋: 81d06d9 -- 대상 브랜치: main -- 머지 일자: 2026-06-26 - -## 신규 테스트 커버리지 (51개) - -| 파일 | 테스트 수 | 대상 REQ | -| --- | --- | --- | -| test_domain_import_sweep.py | 1 | REQ-109 AC-5 | -| test_operator_report.py (신규 클래스) | 22 | REQ-110 AC-1~6, REQ-111 AC-1~4, NFR-2 | -| test_hub_read.py (신규) | 7 | REQ-109 AC-1/2/3/6 | -| test_stub_read.py (신규) | 3 | REQ-109 AC-4 | -| test_cli_report_wac.py (신규 파일) | 11 | REQ-110/111 통합 | - -## REQ AC 충족 매핑 - -### REQ-109 - -| AC | 테스트 | -| --- | --- | -| AC-1 (200→RealizedPnlResult) | test_get_realized_pnl_200_parses_correctly | -| AC-2 (symbol=포함) | test_get_realized_pnl_symbol_param_included, test_get_realized_pnl_no_symbol_param_absent | -| AC-3 (네트워크/비2xx→Unavailable) | test_get_realized_pnl_network_error_raises_unavailable, test_get_realized_pnl_non_2xx_raises_unavailable | -| AC-4 (stub 무네트워크) | test_stub_get_realized_pnl_returns_fixed_result_without_network, test_stub_realized_pnl_result_overridable | -| AC-5 (domain import sweep) | test_domain_has_no_forbidden_imports | -| AC-6 (data_completeness_warning) | test_get_realized_pnl_data_completeness_warning_true | - -### REQ-110 - -| AC | 테스트 | -| --- | --- | -| AC-1 (전일매수+당일매도→WAC 정확값) | TestApplyWacToReport::test_wac_differs_from_net_when_prior_day_buy | -| AC-2 (동일날→WAC≈net) | TestApplyWacToReport::test_wac_equals_net_when_same_day_buy_sell | -| AC-3 (매수만→실현 0, 별도 수수료 라인 없음) | TestApplyWacToReport::test_buy_only_asset_no_fee_line (CHG-20260626-02: buy_fee_only_assets 제거, cost basis 편입) | -| AC-4 (completeness→text·json 경고) | TestApplyWacToReport::test_completeness_warning_in_text_and_json, TestReportWacPath::test_completeness_warning_in_text_and_json | -| AC-5 (자산별 분해) | TestApplyWacToReport::test_per_asset_breakdown_in_text | -| AC-6 (json 4필드) | TestApplyWacToReport::test_render_json_has_all_four_wac_fields, TestReportWacPath::test_wac_json_has_all_four_fields | -| AC-7 (base_url 미설정→degrade) | TestReportDegradeNoBaseUrl::* | - -### REQ-111 - -| AC | 테스트 | -| --- | --- | -| AC-1 (Unavailable→text 에러+타섹션 정상) | TestReportWacPath::test_unavailable_error_degrade | -| AC-2 (json 에러+타섹션 정상) | TestReportWacPath::test_unavailable_json_null_wac_other_sections_present | -| AC-3 (폴백 미발생) | TestReportWacPath::test_no_fallback_to_daily_net | -| AC-4 (base_url 미설정 degrade) | TestReportDegradeNoBaseUrl::test_text_shows_unavailable, test_json_shows_null_wac_and_error | -| AC-5 (예외 데몬 비전파) | TestReportIsolation::test_unavailable_does_not_raise | diff --git a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/design.md b/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/design.md deleted file mode 100644 index d6425e4..0000000 --- a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/design.md +++ /dev/null @@ -1,122 +0,0 @@ -# BAT-053: Design - -## Key Design Decisions - -### D1: `entry_allowed` 상태 위치 — SnapshotScheduler + 공유 callable - -**결정**: `SnapshotScheduler`에 `entry_allowed: bool = True` (opt-in=False 기본 통과) 상태를 두고, IntentService에 `entry_allowed_fn: Callable[[], bool] | None` 게이트를 주입한다. - -**근거**: -- `entry_allowed`는 그날-시작 체크포인트(SnapshotScheduler.run_once())에서 설정되므로 소유자는 SnapshotScheduler가 자연스럽다. -- IntentService는 이미 `warn_store`, `max_active_slots` 등 주입형 게이트 패턴을 사용한다. 동일 패턴으로 `entry_allowed_fn: Optional[Callable[[], bool]] = None`을 추가하면 기존 코드 변경 최소화. -- `entry_allowed_fn=None`(기본)이면 게이트 완전 통과 → `credential_fetch_enabled=False`(기본)일 때 기존 동작 byte-for-byte 보존(회귀 0). -- daemon.py에서 `scheduler.get_entry_allowed` 메서드를 `IntentService`에 주입하는 방식으로 조립. - -**대안 검토**: -- 별도 `DayStartState` 객체: 추가 클래스 생성 필요, SnapshotScheduler와 결합 불필요하게 증가. -- IntentService 내부 setter: 의존 방향 역전(application이 application 내부를 조율하는 순환 구조) — 거부. - -### D2: `credential_fetch_enabled` 플래그 전파 경로 - -**결정**: `DaemonInstanceConfig.credential_fetch_enabled: bool = False` → `DaemonConfig.from_config()`에서 `inst.credential_fetch_enabled`를 읽어 `DaemonConfig.credential_fetch_enabled: bool = False`에 설정. `SnapshotScheduler` 생성자에 `credential_fetch_enabled: bool = False` 인자 추가. - -**근거**: -- TOML → `DaemonInstanceConfig` → `DaemonConfig` → `SnapshotScheduler` 흐름이 기존 `safety_sell_enabled`, `watchdog_timeout_sec` 패턴과 동일. 일관성 유지. -- `from_env()` 는 변경 없음(always `False`). `from_config()`만 TOML 값을 읽어 전파. - -### D3: REQ-113 fetch 소스 우선순위 구현 위치 - -**결정**: 우선순위 로직은 `DaemonConfig.from_config()`에 두지 않고 **`SnapshotScheduler.run_once()`의 그날-시작 후크**에 둔다. - -**근거**: -- REQ-113 Notes: "`base_url`/timeout/allow_live는 env 유지". fetch는 app_key/app_secret/account_ref만 가져온다. -- `DaemonConfig.from_config()`는 정적 구성 해결 단계 — 네트워크 I/O(fetch)를 여기에 두면 의존성 역전 위반(runtime이 portfolio-hub를 직접 호출). -- `SnapshotScheduler.run_once()`는 이미 portfolio-hub를 호출하는 지점이고, `credential_fetch_enabled=True`일 때 추가 fetch가 자연스럽다. -- break-glass(`TREX_KIS_BROKER_BREAK_GLASS=true`) 감지는 `SnapshotScheduler` 초기화 시 `os.environ`을 읽어 `_break_glass: bool` 플래그로 보관. -- fetch 결과(`BrokerCredentialPayload`)는 `SnapshotScheduler._cached_credential`에 그날 고정 캐시. -- **KISConfig 재조립 경로**: `SnapshotScheduler`에 `kis_config_callback: Optional[Callable[[BrokerCredentialPayload], None]]`를 주입. daemon.py에서 이 callback이 `KISBroker`를 재초기화하거나 config를 갱신하는 역할을 한다. 단, M3에서 중장기 인스턴스 추가 전까지는 단기 07:30 로드에서만 필요하고, BAT-053 범위에서는 `entry_allowed` 플래그 설정 + 알람까지만 구현. KISBroker 재조립은 M3 별도 batch. - -**BAT-053 범위 명확화**: -- REQ-113 AC-1~7 중 AC-2(fetch 결과로 KISConfig 구성)의 "KISBroker에 실제 적용" 부분은 M3 KISBroker 재조립 batch에 defer. 이번 batch에서는 fetch→캐시→entry_allowed 게이트 + 알람까지. -- 단, AC-2 테스트는 TradingContext가 올바르게 반환됨을 stub으로 검증. - -### D4: 그날 고정 캐시 설계 (REQ-115) - -**결정**: `SnapshotScheduler`에 인스턴스 변수 추가: -- `_cached_credential: Optional[BrokerCredentialPayload] = None` -- `_credential_date: Optional[date] = None` -- `_entry_allowed: bool = True` (opt-in=False면 항상 True = 차단 안 함) - -`run_once()` 시 `credential_fetch_enabled=True`이면 `_maybe_fetch_credential()` 메서드 호출. -`_maybe_fetch_credential()`: -1. `_credential_date == today` → 그날 고정 캐시 반환(재fetch 안 함, REQ-115 AC-3) -2. break-glass → env-only, 캐시 없음, `_entry_allowed=True` -3. `fetch_broker_credential()` 호출 → 성공: 캐시 + `_entry_allowed=True`; 실패(3가지 조건): `_entry_allowed=False` + ERROR + CRITICAL 알람 - -`get_entry_allowed() -> bool` 메서드 노출 → daemon.py에서 IntentService.`entry_allowed_fn`에 주입. - -### D5: 중장기 인스턴스 후크 위치 (REQ-114 Notes) - -**결정**: BAT-053에서는 단기 07:30 KST 경로(SnapshotScheduler.run_once())만 구현. 중장기 인스턴스(dcy-027 리밸런싱 주기)의 동등 후크는 BAT-053 구현 후 그 위치를 확인하고 설계에 명시. 현재 코드상 중장기 후크는 SnapshotScheduler._maybe_tick()과 run_once()를 공유하므로 `credential_fetch_enabled=True`이면 동일 경로로 처리됨. dcy-027 추가 인스턴스가 별도 run_once() 트리거 주기를 가질 경우 그때 조정. - -### D6: AlarmEmitter `DAY_START_LOAD_FAILED` - -**결정**: AlarmEmitter 자체는 변경 없음(코드는 문자열 파라미터로 전달). `SnapshotScheduler`에서 `self._alarm_emitter.publish_alarm("DAY_START_LOAD_FAILED", ...)` 직접 호출. ALARM-INVENTORY.md 또는 별도 코드 상수 추가는 BAT-053 범위 외(dcy-033/REQ-100 패턴 재사용). - -## Module Interface Summary - -### `application/portfolio.py` 추가 -```python -@dataclass(frozen=True) -class BrokerCredentialPayload: - account_ref: str - app_key: str - app_secret: str - credential_version: Optional[int] = None - -@dataclass(frozen=True) -class TradingContext: - broker: Optional[BrokerCredentialPayload] - trading_enabled: bool - -# PortfolioHubReadPort에 추가: -def fetch_broker_credential(self) -> TradingContext: ... -``` - -### `adapters/portfolio/hub_read.py` 변경 -- `whoami()` 내부 로직을 `_fetch_user_info() -> dict` 헬퍼로 추출 -- `whoami()` = `_fetch_user_info()`에서 `daemon_id` 필드 반환 (외부 동작 불변) -- `fetch_broker_credential()` = `_fetch_user_info()`에서 `broker`, `trading_enabled` 파싱 - -### `adapters/portfolio/stub_read.py` 변경 -- `trading_context: Optional[TradingContext] = None` 생성자 인자 추가 -- `fetch_broker_credential()` stub 구현 - -### `runtime/config.py` 변경 -- `DaemonInstanceConfig.credential_fetch_enabled: bool = False` 추가 + `from_toml()` 파싱 -- `DaemonConfig.credential_fetch_enabled: bool = False` 추가 -- `DaemonConfig.from_config()`: `inst.credential_fetch_enabled` → `DaemonConfig.credential_fetch_enabled` 전파 - -### `application/snapshot_scheduler.py` 변경 -- 생성자: `credential_fetch_enabled: bool = False` 인자 추가 -- 인스턴스 변수: `_cached_credential`, `_credential_date`, `_entry_allowed` -- `_maybe_fetch_credential()` 비공개 메서드 추가 -- `run_once()`: `credential_fetch_enabled=True`이면 `_maybe_fetch_credential()` 호출 -- `get_entry_allowed() -> bool` 노출 - -### `application/intents.py` 변경 (IntentService) -- 생성자: `entry_allowed_fn: Optional[Callable[[], bool]] = None` 추가 -- `_admit_buy()`: `entry_allowed_fn is not None and not entry_allowed_fn()` → reject `day_start_load_failed` - -### `runtime/daemon.py` 변경 -- `SnapshotScheduler` 생성 시 `credential_fetch_enabled=config.credential_fetch_enabled` 전달 -- `IntentService` 생성 시 `entry_allowed_fn=self.snapshot_scheduler.get_entry_allowed` 전달 → SnapshotScheduler가 먼저 생성되어야 함 (순서 조정 필요 확인) - -## Layer Compliance - -- `domain/` 변경 없음 — 도메인 순수성 불변 -- `application/portfolio.py`: 새 dataclass(frozen=True) — IO/네트워크 없음 (import sweep 통과) -- `application/snapshot_scheduler.py`: portfolio 포트 호출(이미 포트 인터페이스) — 신규 import 없음 -- `application/intents.py`: callable 주입 — 추가 import 없음 -- `adapters/`: 구체 구현만 변경 -- `runtime/`: 조립 레이어 변경 diff --git a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/implementation.md b/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/implementation.md deleted file mode 100644 index 1b19b83..0000000 --- a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/implementation.md +++ /dev/null @@ -1,91 +0,0 @@ -# BAT-053: Implementation - -## 변경 파일 목록 - -### 신규/확장 모듈 - -| 파일 | 변경 유형 | 관련 REQ | -| --- | --- | --- | -| `trex_core/application/portfolio.py` | 확장 | REQ-112 | -| `trex_core/adapters/portfolio/hub_read.py` | 확장 | REQ-112 | -| `trex_core/adapters/portfolio/stub_read.py` | 확장 | REQ-112 | -| `trex_core/runtime/config.py` | 확장 | REQ-113 | -| `trex_core/application/snapshot_scheduler.py` | 확장 | REQ-113/114/115 | -| `trex_core/application/intents.py` | 확장 | REQ-114 | -| `trex_core/runtime/daemon.py` | 확장 (조립) | REQ-113/114 | - -### 신규 테스트 - -| 파일 | 관련 REQ | -| --- | --- | -| `tests/adapters/portfolio/test_hub_read.py` (확장) | REQ-112 AC-1~7 | -| `tests/adapters/portfolio/test_stub_read.py` (확장) | REQ-112 AC-6 | -| `tests/runtime/test_instance_config.py` (확장) | REQ-113 AC-1,7 | -| `tests/application/test_snapshot_scheduler.py` (확장) | REQ-114 AC-1~9, REQ-115 AC-3/6 | - -## REQ별 구현 요약 - -### REQ-112: BrokerCredentialPayload + TradingContext + 포트 메서드 + 어댑터 - -- `application/portfolio.py`: - - `BrokerCredentialPayload(account_ref, app_key, app_secret, credential_version)` frozen dataclass 추가 - - `TradingContext(broker, trading_enabled)` frozen dataclass 추가 - - `PortfolioHubReadPort.fetch_broker_credential() -> TradingContext` 포트 메서드 추가 -- `adapters/portfolio/hub_read.py`: - - `whoami()` 내부 로직을 `_fetch_user_info() -> dict` 헬퍼로 추출 — 외부 동작 불변(AC-5 회귀 보존) - - `fetch_broker_credential()` 구현: broker null → TradingContext(broker=None), credential_payload null → TradingContext(broker=None), 성공 → BrokerCredentialPayload 반환 - - NFR-2: app_secret 평문 로그 없음 — 어댑터 내 로깅 코드에 app_secret 미포함 -- `adapters/portfolio/stub_read.py`: - - `trading_context: Optional[TradingContext]`, `fetch_broker_raises: bool` 인자 추가 - - `fetch_broker_credential()` stub 구현 — 네트워크 없이 결정론적 반환/예외 - -### REQ-113: credential_fetch_enabled opt-in + break-glass - -- `runtime/config.py`: - - `DaemonInstanceConfig.credential_fetch_enabled: bool = False` 추가 (TOML opt-in, 기본 False) - - `DaemonInstanceConfig.from_toml()`: `credential_fetch_enabled` 파싱 (부재 시 False) - - `DaemonConfig.credential_fetch_enabled: bool = False` 추가 - - `DaemonConfig.from_config()`: `inst.credential_fetch_enabled` → `DaemonConfig` 전파 - - `DaemonConfig.from_env()`: 변경 없음 → 항상 False (AC-1 회귀 0) -- `application/snapshot_scheduler.py`: - - 생성자 `credential_fetch_enabled: bool = False` 인자 추가 - - 초기화 시 `TREX_KIS_BROKER_BREAK_GLASS` env 감지(break-glass 플래그) - - break-glass: INFO 로그 후 env-only 경로(fetch 없이 entry_allowed=True) - -### REQ-114: 그날-시작 로드 게이트 + fail-closed + 알람 - -- `application/snapshot_scheduler.py`: - - `_entry_allowed: bool = True` 상태 추가 - - `_maybe_fetch_credential()`: fail-closed 3조건 처리 + CRITICAL 알람 `DAY_START_LOAD_FAILED` 발신 - - `run_once()` 선두: `credential_fetch_enabled=True`이면 `_maybe_fetch_credential()` 호출 - - `get_entry_allowed() -> bool` 노출 -- `application/intents.py`: - - `IntentService` 생성자 `entry_allowed_fn: Optional[Callable[[], bool]] = None` 추가 - - `_admit_buy()` 선두: `entry_allowed_fn is not None and not entry_allowed_fn()` → reject `day_start_load_failed` - - SellIntent(`_admit_sell`) 변경 없음 — 청산 경로 영향 없음 (AC-5) -- `runtime/daemon.py`: - - `SnapshotScheduler` 생성 후 `entry_allowed_fn = scheduler.get_entry_allowed`를 IntentService에 주입 - - `credential_fetch_enabled=False`(기본) → `entry_allowed_fn=None` → 게이트 비활성 - -### REQ-115: 그날 고정 캐시 + 장중 무중단 - -- `application/snapshot_scheduler.py`: - - `_cached_credential: Optional[BrokerCredentialPayload]`, `_credential_date: Optional[date]` 추가 - - `_maybe_fetch_credential()`: `_credential_date == today` 시 재fetch 생략 (그날 고정 캐시, AC-3) - - 성공 시 캐시 저장 + INFO 로그(account_ref/trading_enabled/credential_version; app_secret 미포함) - - Redis 등 외부 저장소 기록 없음 — 메모리 한정 (AC-5) - -## 설계 결정 반영 확인 - -- D1(entry_allowed 위치): SnapshotScheduler 내 + IntentService callable 주입 — 구현됨 -- D2(credential_fetch_enabled 전파): DaemonInstanceConfig → DaemonConfig → SnapshotScheduler — 구현됨 -- D3(우선순위 로직 위치): SnapshotScheduler._maybe_fetch_credential() — 구현됨 -- D4(그날 고정 캐시): `_credential_date` 비교로 동일 날짜 재fetch 방지 — 구현됨 -- D5(중장기 후크): BAT-053에서는 단기 07:30 경로(run_once())만 구현. 중장기는 동일 run_once() 경유로 처리됨 — 확인 -- D6(AlarmEmitter): publish_alarm("DAY_START_LOAD_FAILED", ...) 직접 호출 패턴 — 구현됨 - -## 주요 설계 메모 - -- `credential_fetch_enabled=False`(기본)일 때: `_maybe_fetch_credential()` 미호출 → `_entry_allowed=True` 고정 → IntentService `entry_allowed_fn=None` → 게이트 완전 비활성. 기존 동작 byte-for-byte 보존(회귀 0). -- KISBroker 재조립(fetch 결과를 실제 KIS API에 적용)은 BAT-053 범위 외 — M3 별도 batch. 현재 `_cached_credential`이 메모리에 저장되어 있으며 다음 batch에서 KISBroker 재초기화에 활용. -- REQ-115 AC-2(ledger_outbox 무손실): 기존 REQ-019 구현 속성 재확인. 별도 구현 불필요, 기존 테스트 그린(745 passed). diff --git a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/index.md b/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/index.md deleted file mode 100644 index 572d322..0000000 --- a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# BAT-053: 그날-시작 KIS 인증정보 fetch + fail-safe 로드 - -- Status: release-candidate -- Profile: standard -- Discovery: dcy-036 -- Created: 2026-06-28 - -## Included REQ - -- REQ-112: portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터 -- REQ-113: KISConfig 소스 우선순위 — portfolio-hub fetch 1차 + env break-glass + opt-in -- REQ-114: 그날-시작 로드 게이트 — fail-closed 신규 진입 차단 + 알람 -- REQ-115: 그날-시작 장중 무중단 보장 — 그날 고정 인증정보 캐시 + portfolio-hub 장애 비중단 - -## Excluded REQ - -없음 — dcy-036 인증정보 fetch 4개 REQ 전체 포함. - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) - -## Notes - -- Source: dcy-036 (그날-시작 KIS 인증정보 fetch + fail-safe 로드). REQ-112~115 모두 같은 discovery. -- 사장님 지시: 스텁 기반으로 구현해 실 portfolio-hub 없이 테스트 가능하게. -- 워크트리 격리 배치(origin/main 분기). 코드 변경은 이 워크트리 브랜치에 커밋. -- AMQP wire 계약 변경 없음 — trex-orb-v1/trex-tsl-v1 파급 없음. -- confirm-req-implemented(2026-06-28): PR #34 머지(01655ab), QC PASS-WITH-NOTES(opus, blocker 0), 745 passed/0 회귀. REQ-112/114 Implemented 완료. **양의 경로(KISBroker 재조립, REQ-113 AC-2+REQ-115 AC-1) + AC-9 전용 테스트 보강 + break-glass 로그 assert = M3 후속 batch.** REQ-113/115는 스캐폴드 완료·Approved 유지. diff --git a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/planning.md b/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/planning.md deleted file mode 100644 index 9af1e67..0000000 --- a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/planning.md +++ /dev/null @@ -1,57 +0,0 @@ -# BAT-053: Planning - -## Scope Summary - -그날-시작 시 portfolio-hub `/v1/user_info`에서 KIS 인증정보를 fetch하고, 실패 시 fail-closed로 신규 진입을 차단하며, 성공 시 그날 캐시로 KIS API 장중 연속성을 보장한다. - -4개 REQ 모두 같은 delivery 단위: -- REQ-112: 트랜스포트 계층 (포트 인터페이스 + HTTP 어댑터 + stub) -- REQ-113: 설정 계층 (TOML opt-in + fetch/break-glass/env-only 우선순위) -- REQ-114: 그날-시작 로드 게이트 (fail-closed + 알람 + BuyIntent 차단) -- REQ-115: 그날 고정 캐시 + 장중 무중단 NFR - -## Task Breakdown - -### T1: REQ-112 — 포트/어댑터 계층 -- `application/portfolio.py`: `BrokerCredentialPayload`, `TradingContext` 데이터클래스 + `PortfolioHubReadPort.fetch_broker_credential()` 포트 메서드 추가 -- `adapters/portfolio/hub_read.py`: `_fetch_user_info()` 헬퍼 추출 + `fetch_broker_credential()` 구현 -- `adapters/portfolio/stub_read.py`: `fetch_broker_credential()` stub 추가 -- 테스트: `tests/adapters/portfolio/test_hub_read.py` AC-1~7, `test_stub_read.py` AC-6 - -### T2: REQ-113 — 설정 계층 -- `runtime/config.py`: `DaemonInstanceConfig.credential_fetch_enabled: bool = False` + `from_toml()` 파싱 -- `DaemonConfig.from_config()` 또는 new helper: fetch/break-glass/env-only 우선순위 로직 -- 테스트: `tests/runtime/test_instance_config.py` AC-1~7(추가) - -### T3: REQ-114 + REQ-115 — 그날-시작 게이트 + 그날 고정 캐시 -- `application/snapshot_scheduler.py`: - - `run_once()` 앞에 `credential_fetch_enabled=True` 경로에서 `fetch_broker_credential()` 호출 - - `entry_allowed: bool` in-process 상태 + `_cached_credential: BrokerCredentialPayload | None` + `_credential_date: date | None` - - fail-closed 3 조건(PortfolioHubUnavailableError / broker None / trading_enabled False) → ERROR 로그 + CRITICAL 알람 DAY_START_LOAD_FAILED - - 그날 고정 — 장중 재fetch 방지 -- `application/alarm_emitter.py`: `DAY_START_LOAD_FAILED` 알람 코드 추가(기존 패턴 재사용 — 실제로 코드 자체는 문자열이므로 사용처 추가) -- `application/intents.py` (IntentService): `entry_allowed` 게이트를 BuyIntent admission에 추가 -- 테스트: `tests/application/test_snapshot_scheduler.py` AC-1~9 + REQ-115 AC-1~6 -- 테스트: `tests/application/test_execution.py` 또는 test_intent 계열 AC-4, AC-5 - -## Design Gate - -**design.md 필요함(standard profile)**: 다음 판단이 필요. -1. `entry_allowed` 상태를 어디에 보관할지: SnapshotScheduler 내부 vs 별도 `DayStartState` 객체 vs IntentService에 주입. -2. IntentService에 `entry_allowed` 게이트를 어떻게 연결할지(생성자 주입 callable vs setter vs 공유 객체). -3. `credential_fetch_enabled` 플래그가 False(기본)일 때 기존 경로 byte-for-byte 보존 검증 방법. -4. 중장기 인스턴스(dcy-027)의 그날-시작 후크 위치 확인(현 코드에서 run_once() 위치 분석 필요). -5. `fetch_broker_credential()` 반환값이 `DaemonConfig.from_config()`에서 `KISConfig` 재조립으로 이어지는 흐름 설계. - -## Risks - -- R1: IntentService에 `entry_allowed` 게이트를 연결할 때 기존 pass-through 테스트 회귀 가능성 → `credential_fetch_enabled=False` 기본 경로에서 게이트를 비활성화하면 회귀 방지 -- R2: `app_secret` 로그 노출 — 마스킹 로직 누락 주의 -- R3: `stub_read.py`의 stub이 포트 인터페이스와 불일치 → pytest import sweep으로 조기 발견 -- R4: REQ-113의 `DaemonConfig.from_config()` 변경이 기존 `test_instance_config.py` 회귀 유발 가능 → AC-1(opt-in off → env-only, fetch 미호출) 케이스가 핵심 회귀 가드 - -## Dependencies - -- portfolio-hub REQ-049 (dcy-010): `/v1/user_info` 확장 계약 — 머지 완료(미배포). 스텁 기반으로 실 portfolio-hub 없이 구현. -- REQ-099/100 (dcy-033, BAT-049): AlarmEmitter `trex.alerts` 계약 — 이미 구현됨. -- REQ-019: ledger_outbox 무손실 — 기존 구현 재확인(REQ-115 AC-2). diff --git a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/verification.md b/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/verification.md deleted file mode 100644 index 8f2ce18..0000000 --- a/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/verification.md +++ /dev/null @@ -1,89 +0,0 @@ -# BAT-053: Verification - -- Status: draft-batch-verification -- Test run: 2026-06-28 -- Test method: pytest (스텁 기반 단위/통합 테스트 — 실 portfolio-hub 없이 실행) - -## 테스트 실행 결과 - -``` -745 passed, 47 skipped in 1.61s (0 failed) -``` - -전체 스위트 실행: 기존 회귀 없음, 신규 케이스 전부 통과. - -## REQ별 AC 커버리지 - -### REQ-112: portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터 - -| AC | 테스트 | 결과 | -| --- | --- | --- | -| AC-1: 유효한 broker+trading_enabled=True → TradingContext 반환 | `test_fetch_broker_credential_returns_trading_context` | PASS | -| AC-2: broker:null → TradingContext(broker=None) | `test_fetch_broker_credential_broker_null_returns_none_broker` | PASS | -| AC-3: credential_payload:null → TradingContext(broker=None) | `test_fetch_broker_credential_credential_payload_null_returns_none_broker` | PASS | -| AC-4: 네트워크 오류/403/500 → PortfolioHubUnavailableError | `test_fetch_broker_credential_network_error_raises_unavailable`, `test_fetch_broker_credential_403_raises_unavailable`, `test_fetch_broker_credential_500_raises_unavailable` | PASS | -| AC-5: whoami() 계약 불변 (회귀 없음) | `test_fetch_broker_credential_whoami_still_works` + 기존 whoami 테스트 전부 통과 | PASS | -| AC-6: stub 결정론 반환 (네트워크 없음) | `test_stub_fetch_broker_credential_default_happy_path`, `test_stub_fetch_broker_credential_overridable_context`, `test_stub_fetch_broker_credential_raises_when_configured` | PASS | -| AC-7: app_secret 평문 로그 미노출 | `test_fetch_broker_credential_app_secret_not_in_logs` | PASS | -| AC-8: domain import sweep | `test_domain_has_no_forbidden_imports` | PASS | - -### REQ-113: KISConfig 소스 우선순위 — opt-in + break-glass - -| AC | 테스트 | 결과 | -| --- | --- | --- | -| AC-1: credential_fetch_enabled=false(부재 포함) → env-only, fetch 미호출 | `test_run_once_no_credential_fetch_when_disabled`, `test_credential_fetch_enabled_false_propagates`, `test_from_env_credential_fetch_enabled_always_false` | PASS | -| AC-2: credential_fetch_enabled=true + 유효한 fetch → KISConfig fetch 결과로 구성 | `test_run_once_credential_fetch_success_sets_entry_allowed` (TradingContext 캐시 검증) | PASS | -| AC-3: credential_fetch_enabled=true + BREAK_GLASS=true → fetch 미시도, env 경로 | `test_break_glass_skips_fetch` | PASS | -| AC-4: break-glass 활성화 시 INFO 로그 | `test_break_glass_skips_fetch` (caplog 미검사, 로그 코드 확인) | PARTIAL (로그 내용 assert 없음) | -| AC-5: app_secret 마스킹 | `test_app_secret_not_in_logs` | PASS | -| AC-6: fetch 성공 시 INFO 소스 로깅 | `test_app_secret_not_in_logs` (로그 출력 확인) | PASS | -| AC-7: TOML 부재 시 False 파싱 | `test_credential_fetch_enabled_default_false_from_toml`, `test_credential_fetch_enabled_true_from_toml` | PASS | - -**주의 (AC-4)**: break-glass INFO 로그 내용을 assert하는 전용 테스트가 없음. 코드상 `_log.info(...)` 호출이 있음을 확인. 단순 문자열 로그이므로 동작 보증 범위 내. - -### REQ-114: 그날-시작 로드 게이트 — fail-closed + 알람 - -| AC | 테스트 | 결과 | -| --- | --- | --- | -| AC-1: PortfolioHubUnavailableError → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_portfolio_hub_unavailable_sets_fail_closed` | PASS | -| AC-2: broker=None → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_broker_null_sets_fail_closed` | PASS | -| AC-3: trading_enabled=False → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_trading_disabled_sets_fail_closed` | PASS | -| AC-4: entry_allowed=False → BuyIntent admission 거부 | `test_intent_service_buy_rejected_when_entry_not_allowed` | PASS | -| AC-5: entry_allowed=False → SellIntent 통과 | `test_intent_service_sell_passes_when_entry_not_allowed` | PASS | -| AC-6: 성공 → entry_allowed=True + INFO 로그(account_ref, app_secret 제외) | `test_run_once_credential_fetch_success_sets_entry_allowed`, `test_alarm_not_emitted_on_success`, `test_app_secret_not_in_logs` | PASS | -| AC-7: credential_fetch_enabled=false → fetch 없이 기존 스냅샷 로직 | `test_run_once_no_credential_fetch_when_disabled` | PASS | -| AC-8: 알람 envelope에 DAY_START_LOAD_FAILED + instance_key 포함 | `test_run_once_portfolio_hub_unavailable_sets_fail_closed`, `test_run_once_broker_null_sets_fail_closed`, `test_run_once_trading_disabled_sets_fail_closed` | PASS | -| AC-9: fail-closed 후 entry_allowed 그날 고정 | `test_entry_allowed_fixed_for_day_no_refetch` | PASS | - -### REQ-115: 그날-시작 장중 무중단 — 그날 고정 캐시 - -| AC | 테스트 | 결과 | -| --- | --- | --- | -| AC-1: 그날 성공 후 장중 portfolio-hub 장애 → KIS 계속(캐시 활용) | In-process 캐시 설계 + `test_successful_credential_cached_for_day` | PARTIAL (KISBroker 실제 적용은 M3 defer, 캐시 저장 자체는 검증) | -| AC-2: 장중 장애 후 체결 → ledger_outbox durable 기록 | 기존 REQ-019 테스트 전부 통과 (745 passed 포함) | PASS (회귀 재확인) | -| AC-3: 그날 성공 후 동일 날짜 재fetch 없음 | `test_successful_credential_cached_for_day` (fetch_count 검증) | PASS | -| AC-4: credential_fetch_enabled=false → 기존 회귀 0 | 전체 스위트 745 passed | PASS | -| AC-5: app_secret Redis 미기록 | 메모리 전용 저장(코드 검토), Redis 기록 코드 없음 | PASS (코드 검토 기반) | -| AC-6: 성공 후 INFO 로그에 app_secret 없음 + account_ref/캐시 날짜 포함 | `test_app_secret_not_in_logs` | PASS | - -**주의 (AC-1)**: KISBroker 실제 재조립(fetch된 app_key/app_secret를 KIS API 호출에 적용)은 M3 후속 batch에서 구현. 이번 batch에서는 캐시 저장(`_cached_credential`)까지 완료, KISBroker 연결은 미구현. - -## 회귀 검증 - -- 전체 스위트 745 passed, 47 skipped — **기존 테스트 0 실패** (회귀 없음) -- `credential_fetch_enabled=False`(기본) 경로: fetch 코드가 실행되지 않아 기존 동작 byte-for-byte 보존 확인 -- domain import sweep 통과 — domain 오염 없음 -- whoami() 기존 테스트 전부 통과 — REQ-112 AC-5 보존 확인 - -## 스텁 기반 테스트 명시 - -- 이 batch의 모든 테스트는 `StubPortfolioRead.fetch_broker_credential()`과 MockTransport 기반으로 작성됨. -- 실 portfolio-hub(REQ-049 dcy-010 배포 완료 이후) 통합 검증은 이 batch 범위 밖. -- Step2 운영 적용 후 실 환경에서 별도 통합검증 필요. - -## 미해결/위험 - -1. **KISBroker 재조립 미구현**: `_cached_credential`이 메모리에 저장되나 실제 KIS API 호출에 적용되지 않음. REQ-115 AC-1의 완전한 충족은 M3 batch에서 연결 필요. -2. **REQ-113 AC-4 break-glass INFO 로그 assert 미작성**: 코드상 구현됨, 단위 테스트에서 로그 내용을 assert하지 않음. 리스크 낮음. -3. **중장기 인스턴스 후크**: 현재 `run_once()`를 단기/중장기 모두 공유하나, dcy-027 중장기 인스턴스 추가 시 별도 트리거 주기 확인 필요. -4. **실 portfolio-hub 통합 검증**: dcy-010 REQ-049 배포 완료 시점에 별도 E2E 검증 필요. diff --git a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/design.md b/docs/batches/bat-054_20260628_daystart-credential-kis-inject/design.md deleted file mode 100644 index 6b934dc..0000000 --- a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/design.md +++ /dev/null @@ -1,108 +0,0 @@ -# BAT-054: Design - -- Status: confirmed -- Batch: BAT-054 - -## Architecture Summary - -그날-시작 체크포인트에서 fetch된 `BrokerCredentialPayload`를 실제 `KISBroker` 인스턴스에 주입하는 경로를 완성한다. - -설계 핵심 두 가지: -1. `KISBroker.reinitialize(credential)` 메서드: `app_key/app_secret/account_ref`를 원자적으로 교체, `base_url/timeout/allow_live/mock`은 유지. L1 token 캐시 무효화 포함. -2. `SnapshotScheduler`에 `kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None` 추가. 그날-시작 성공 시 callback 호출 → KISBroker.reinitialize 트리거. - -## Changed Areas - -- Docs: docs/batches/bat-054_.../ (이번 batch 문서) -- Modules: - - `trex_core/adapters/broker/kis.py` — `reinitialize()` 메서드 추가 - - `trex_core/application/snapshot_scheduler.py` — `kis_reinit_callback` 인자 추가 + 성공 시 호출 - - `trex_core/runtime/daemon.py` — `_build_kis_reinit_callback()` + SnapshotScheduler 조립 수정 -- Interfaces: 없음 (AMQP wire 계약 변경 없음) - -## Key Decisions - -### D1: KISBroker.reinitialize() — 인스턴스 변수 직접 교체 - -**결정**: `KISBroker`에 `reinitialize(credential: BrokerCredentialPayload) -> None` 메서드를 추가한다. -메서드 내에서: -- `self._app_key`, `self._app_secret`, `self._account_ref`를 새 값으로 교체 -- L1 token 캐시(`self._token = None`, `self._token_exp = datetime.min`) 무효화 -- L2 token cache(Redis, `token_cache.invalidate`가 있으면 호출; 없으면 skip — fallback graceful) -- `base_url/timeout/mock/allow_live`는 **불변** (env에서 온 인프라 설정) - -**근거**: -- `KISBroker`는 이미 실행 중인 여러 스레드(execution loop, safety-sell)가 공유하는 인스턴스다. 새 객체로 교체하면 기존 참조가 stale해진다. -- 인스턴스 변수 교체는 CPython GIL 하에서 atomic하며, 진행 중인 HTTP 요청이 완료된 후 다음 _ensure_token() 호출부터 새 자격증명을 사용한다. 그날-시작(07:30 KST)은 장 시작 전이므로 경쟁 조건 위험이 낮다. -- threading.Lock은 추가하지 않는다(reinitialize가 그날-시작 1회만 호출되고 장 시작 전이라 안전). 만약 향후 장중 재초기화가 필요해지면 그때 lock을 추가한다(REQ-115 설계: 그날 고정 캐시로 장중 재fetch 없음). - -### D2: SnapshotScheduler.kis_reinit_callback — Optional callable 주입 - -**결정**: `SnapshotScheduler.__init__`에 `kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None` 추가. -`_maybe_fetch_credential()` 성공 후, 캐시 저장 + INFO 로그 이후에 callback을 호출한다. - -```python -# 성공 경로 -self._cached_credential = ctx.broker -self._credential_date = today -self._entry_allowed = True -_log.info("DAY_START credential loaded: account_ref=%s ...", ...) -if self._kis_reinit_callback is not None: - try: - self._kis_reinit_callback(ctx.broker) - _log.info("DAY_START KIS broker reinitialized with fetched credential (instance=%s)", ...) - except Exception as exc: - _log.error("DAY_START KIS broker reinitialize failed: %s (instance=%s) — entry remains allowed", ...) - # 재초기화 실패는 entry_allowed를 False로 전환하지 않는다(기존 env 인증정보로 계속 운행) -``` - -**근거**: -- callback 실패가 entry_allowed를 뒤집지 않는다: 재초기화 실패 시 기존 env 인증정보로 운행 계속 (fail-safe: 이미 기동 중인 브로커가 있으므로). -- 새 인증정보 주입에 성공하면 INFO 로그로 가시화. -- `credential_fetch_enabled=False`(기본)일 때 callback이 None이므로 코드 경로 완전 미진입 → 기존 동작 byte-for-byte 보존. - -### D3: daemon.py — callback 조립 - -**결정**: `daemon.py`에 private helper `_build_kis_reinit_callback(config, broker)` 추가: -```python -def _build_kis_reinit_callback(config, broker): - if config.broker != "kis" or not config.credential_fetch_enabled: - return None - if not hasattr(broker, "reinitialize"): - return None - return broker.reinitialize -``` -이를 `SnapshotScheduler` 생성 시 `kis_reinit_callback=_build_kis_reinit_callback(config, self.broker)` 로 전달. - -**근거**: -- `broker == 'paper'`이면 callback=None → 기존 경로 보존. -- `credential_fetch_enabled=False`이면 callback=None. -- `KISBroker`가 아닌 브로커가 `reinitialize`를 가지지 않을 경우 graceful skip. - -### D4: access token cache (Redis, REQ-016) 무효화 - -**결정**: `reinitialize()` 내에서: -1. L1 in-memory token 무효화: `self._token = None`, `self._token_exp = datetime.min(UTC)`. -2. L2 Redis: `token_cache`에 `invalidate(account_ref, app_key)` 메서드가 있으면 호출, 없으면 skip. `RedisTokenCache`는 기존 key scheme이 `(account_ref, app_key)` 기반이므로 기존 key가 자연스럽게 miss로 처리된다(새 account_ref/app_key로 조회 시 miss → 새 토큰 발급). 명시적 invalidate API는 추가하지 않는다. - -**근거**: 새 `account_ref/app_key`로 L2 조회 시 miss → 새 토큰 발급. 구 key는 TTL로 자연 만료. 인증정보 변경이 1일 1회이므로 수동 무효화 복잡성 불필요. - -### D5: REQ-113 AC-4 보강 — break-glass 로그 assert - -**결정**: `tests/application/test_snapshot_scheduler.py`에 break-glass 활성화 시 로그에 "break-glass" 키워드가 포함됨을 `caplog`로 검증하는 케이스 추가. - -## Edge Cases - -- `credential_fetch_enabled=True`이고 `broker='paper'`인 경우: callback=None이므로 재초기화 없음. entry_allowed는 fetch 결과에 따라 설정됨(논리적으로 paper broker에 fetch가 의미 없으나 테스트 가능). -- `reinitialize()` 호출 중 in-flight HTTP 요청: CPython GIL 하에서 변수 할당은 atomic, 다음 `_ensure_token()` 호출부터 새 값 사용. 경쟁 조건 없음(그날-시작 = 장 전). -- `credential_fetch_enabled=True`이고 break-glass: callback 호출 없음(재초기화 없이 env 인증정보 사용) — D2 기술. - -## Architecture Impact - -- runtime-flows: KISBroker 재초기화 경로 추가 (경미한 갱신) -- project-structure: 변경 없음 - -## Reference Doc Update Plan - -- project-structure.md: none (구조 변경 없음) -- runtime-flows.md: DAY_START 흐름에 KISBroker reinitialize 스텝 추가 설명이 유용하나 이번 batch 필수 아님 — verification에서 판단 diff --git a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/implementation.md b/docs/batches/bat-054_20260628_daystart-credential-kis-inject/implementation.md deleted file mode 100644 index 5fdc9c0..0000000 --- a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/implementation.md +++ /dev/null @@ -1,79 +0,0 @@ -# BAT-054: Implementation - -- Status: completed -- Batch: BAT-054 - -## Plan Summary - -BAT-053에서 스캐폴드로 처리된 양의 경로를 완성: -1. `KISBroker.reinitialize(credential)` 메서드 추가 — app_key/app_secret/account_ref 교체 + L1 token 무효화 -2. `SnapshotScheduler`에 `kis_reinit_callback` 주입 지점 추가 — 그날-시작 성공 시 callback 호출 -3. `daemon.py`에 `_build_kis_reinit_callback()` 헬퍼 + SnapshotScheduler 조립 수정 -4. 신규 AC 테스트 12개 추가 (REQ-113 AC-2/AC-4 보강, REQ-115 AC-1) - -## Changed Files - -| 파일 | 변경 유형 | 관련 REQ | -| --- | --- | --- | -| `trex_core/adapters/broker/kis.py` | 확장 (reinitialize 메서드 추가) | REQ-113 AC-2, REQ-115 AC-1 | -| `trex_core/application/snapshot_scheduler.py` | 확장 (kis_reinit_callback 인자 + 성공 경로 callback 호출) | REQ-113 AC-2, REQ-115 AC-1 | -| `trex_core/runtime/daemon.py` | 확장 (_build_kis_reinit_callback 헬퍼 + SnapshotScheduler 조립) | REQ-113 AC-2, REQ-115 AC-1 | -| `tests/application/test_snapshot_scheduler.py` | 확장 (BAT-054 테스트 12개 추가) | REQ-113 AC-2/AC-4, REQ-115 AC-1 | - -## Execution Log - -1. `KISBroker.reinitialize()` 구현: - - `self._account_ref/app_key/app_secret` 교체 - - `self._token = None`, `self._token_exp = datetime.min(UTC)` L1 무효화 - - `base_url/timeout/mock/allow_live` 불변 (env-sourced 인프라 설정) - - docstring에 L2 Redis token cache 처리 방침(새 key miss → 자연 재발급) 기술 - -2. `SnapshotScheduler.__init__` 확장: - - `kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None` 추가 - - `self._kis_reinit_callback` 저장 - -3. `SnapshotScheduler._maybe_fetch_credential()` 성공 경로 확장: - - 기존 INFO 로그 뒤에 callback 호출 블록 추가 - - callback 실패 시 ERROR 로그 + entry_allowed 유지(fail-safe) - - break-glass 경로: callback 호출 없음 (env 인증정보 사용) - -4. `daemon.py` 확장: - - `_build_kis_reinit_callback(config, broker)` 헬퍼 추가 - - `config.broker != 'kis'` → None - - `not config.credential_fetch_enabled` → None - - `broker.reinitialize` 없음 → None - - 조건 충족 시 `broker.reinitialize` callable 반환 - - `SnapshotScheduler` 생성 시 `kis_reinit_callback=_build_kis_reinit_callback(config, self.broker)` 전달 - -5. 테스트 추가 (12개): - - `test_kis_reinit_callback_called_on_success` — AC-2: fetch 성공 시 callback 호출 - - `test_kis_reinit_callback_not_called_when_fail_closed` — AC-2: fail-closed 시 callback 미호출 - - `test_kis_reinit_callback_not_called_when_disabled` — AC-1 회귀: opt-in off 시 callback 미호출 - - `test_kis_reinit_callback_failure_does_not_flip_entry_allowed` — 설계 안전망: reinit 실패 시 entry_allowed 유지 - - `test_kis_reinit_callback_cached_for_day_not_called_twice` — REQ-115 AC-3: 그날 두 번째 run_once에서 재호출 없음 - - `test_kis_reinit_callback_none_when_break_glass` — AC-3+AC-2: break-glass 시 callback 없음 - - `test_break_glass_log_contains_break_glass_keyword` — REQ-113 AC-4 보강: break-glass 로그 content - - `test_kis_broker_reinitialize_updates_credentials` — REQ-115 AC-1: 인증정보 교체 + L1 무효화 - - `test_kis_broker_reinitialize_preserves_base_url_and_flags` — REQ-115 AC-1: 인프라 설정 불변 - - `test_build_kis_reinit_callback_returns_none_for_paper` — AC-2: paper broker → None - - `test_build_kis_reinit_callback_returns_none_when_disabled` — AC-1: fetch disabled → None - - `test_build_kis_reinit_callback_returns_callable_for_kis` — AC-2/AC-1: kis+enabled → callable - -## Validation - -``` -cd /home/genichin/workspace/trex-lead/repos/trex-core/.claude/worktrees/agent-a0d6ff73d43f2e96b -python3 -m pytest tests/application/test_snapshot_scheduler.py -v -``` -결과: **29 passed** (BAT-053 기존 17 + BAT-054 신규 12) - -``` -python3 -m pytest tests/ -q -``` -결과: **757 passed, 47 skipped** (기존 베이스라인 745 대비 +12, 회귀 0) - -## Remaining Risks - -- R1 (Low): 실 portfolio-hub 없이 stub 기반으로만 검증. 실 `/v1/user_info` 응답 스키마 호환은 portfolio-hub dcy-010 배포 후 통합 테스트에서 확인 필요. -- R2 (Low): KISBroker.reinitialize()는 그날-시작(장 전) 1회 호출 설계이므로 in-flight HTTP 경쟁 조건 위험이 낮으나, 장중 reinitialize가 필요한 시나리오가 생기면 threading.Lock 추가 검토 필요. -- R3 (Low): L2 Redis token cache(REQ-016) 명시적 무효화 없음 — 새 (account_ref, app_key) key miss로 자연 재발급 처리. 동일 account_ref/app_key인데 app_secret만 바뀐 경우 구 토큰이 TTL 만료 전까지 유효할 수 있음. 운영상 계좌 인증정보 전체가 함께 교체되는 구조라 실용적으로 문제 없음. diff --git a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/index.md b/docs/batches/bat-054_20260628_daystart-credential-kis-inject/index.md deleted file mode 100644 index 9fa238e..0000000 --- a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# BAT-054: 그날-시작 KIS 인증정보 — 양의 경로 주입 (KISBroker 재조립) - -- Status: in-progress -- Profile: standard -- Discovery: dcy-036 -- Created: 2026-06-28 - -## Included REQ - -- REQ-113: KISConfig 소스 우선순위 — portfolio-hub fetch 1차 + env break-glass + opt-in (AC-2, AC-4, AC-9 미구현 부분) -- REQ-115: 그날-시작 장중 무중단 보장 — 그날 고정 인증정보 캐시 + portfolio-hub 장애 비중단 (AC-1 미구현 부분) - -## Excluded REQ - -- REQ-112: Implemented (BAT-053) -- REQ-114: Implemented (BAT-053) -- REQ-113 AC-1/3/5/6/7: Implemented (BAT-053) -- REQ-115 AC-2/3/4/5/6: Implemented (BAT-053) - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) - -## Notes - -- BAT-053 (PR #34 머지, commit 01655ab)에서 스텁/스캐폴드로 처리된 **양의 경로 미구현 부분**을 이번 batch에서 완성한다. -- 핵심 잔여 작업: - - REQ-113 AC-2: fetch 성공 시 `BrokerCredentialPayload`로 `KISConfig` 재조립 → `KISBroker` 재초기화 - - REQ-115 AC-1: 장중 portfolio-hub 장애 후에도 캐시된 인증정보로 KIS 주문 처리 지속 - - REQ-113 AC-4 보강: break-glass 로그 content assert (BAT-053 Notes: "AC-4 break-glass 로그 내용 assert 보강 후속") -- AMQP wire 계약 변경 없음 — trex-orb-v1/trex-tsl-v1 파급 없음. -- 실거래(real money) 주입 경로 — blast radius 크다. 팀장 검수 + QC + 사장님 머지 필요. diff --git a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/planning.md b/docs/batches/bat-054_20260628_daystart-credential-kis-inject/planning.md deleted file mode 100644 index 328d91a..0000000 --- a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/planning.md +++ /dev/null @@ -1,57 +0,0 @@ -# BAT-054: Planning - -- Status: confirmed -- Batch: BAT-054 -- Profile: standard - -## Included REQ - -- REQ-113 (Approved, 미충족 AC: AC-2 fetch→KISConfig 재조립, AC-4 break-glass 로그 assert) -- REQ-115 (Approved, 미충족 AC: AC-1 캐시 인증정보로 KIS 주문 지속) - -## Scope Summary - -BAT-053에서 스캐폴드로 처리된 "양의 경로(positive path)"를 완성한다. -- 그날-시작 체크포인트에서 fetch 성공 시 `BrokerCredentialPayload`로 `KISConfig`의 `app_key/app_secret/account_ref`를 재조립하고 이를 실제 `KISBroker` 인스턴스에 주입한다. -- 재조립 후 `KISBroker`는 그날 캐시된 인증정보로 장중 KIS API 호출(주문·체결 조회·토큰 갱신)을 처리한다. -- portfolio-hub 장중 장애는 이미 재조립된 KISBroker 운행에 영향을 주지 않는다. -- break-glass 로그 내용 assert 테스트를 추가한다(AC-4 보강). - -## Task Breakdown - -### T1: REQ-113 AC-2 — KISBroker 재조립 callback 설계 + 구현 - -- `SnapshotScheduler`에 `kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None` 추가 -- 그날-시작 성공 시 `_maybe_fetch_credential()`에서 callback 호출 → KISBroker 재초기화 -- `daemon.py`의 `_build_broker()`에서 KIS 브로커면 callback을 조립하여 주입 - -### T2: REQ-115 AC-1 — KISBroker 런타임 교체 메서드 - -- `KISBroker`(또는 daemon.py)에 `reinitialize(credential: BrokerCredentialPayload)` 같은 메서드 제공 -- 재초기화는 `app_key/app_secret/account_ref`만 교체; `base_url/timeout/allow_live`는 env 원본 유지 -- KIS access token 캐시(Redis, REQ-016)는 `app_key` 변경 시 무효화 필요 - -### T3: REQ-113 AC-4 보강 — break-glass 로그 assert 테스트 - -- `tests/runtime/test_config.py` 또는 `tests/application/test_snapshot_scheduler.py`에 AC-4 로그 content 검증 케이스 추가 - -## Design Gate - -- Design Required Before Implementation: yes -- Reason: KISBroker 재초기화 방법(메서드 추가 vs callback 교체 vs wrapper) + 재초기화 시 access token 캐시 무효화 정책이 구현 전에 명시되어야 한다. 기존 `KISBroker` 코드를 확인 후 결정. - -## Dependencies - -- Internal: - - BAT-053 완료(REQ-112 `BrokerCredentialPayload`/`TradingContext` 구현, REQ-114 fail-closed 스캐폴드) - - `adapters/broker/kis.py` (KISBroker) — reinitialize 진입점 - - `runtime/daemon.py` (`_build_broker`, SnapshotScheduler 조립) -- External: - - 없음 (portfolio-hub 실 연동은 스텁 기반 테스트로 커버) - -## Risks - -- R1 (High): KISBroker 재초기화 중 진행 중인 주문 경쟁 조건 — broker 레벨 lock 또는 재초기화 안전 구간 확인 필요 -- R2 (Medium): access token 캐시(Redis, REQ-016) 무효화 누락 → 구 account_ref 토큰이 신규 인증정보와 충돌 -- R3 (Medium): `daemon.py`에서 callback 조립 시 `DaemonConfig.broker == 'kis'` 여부 확인 — paper broker면 callback 불필요 -- R4 (Low): 테스트에서 실제 KIS API를 호출하지 않도록 mock/stub 격리 유지 — KISBroker.reinitialize()는 stub BrokerPort로 테스트 diff --git a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/verification.md b/docs/batches/bat-054_20260628_daystart-credential-kis-inject/verification.md deleted file mode 100644 index 89ee4c1..0000000 --- a/docs/batches/bat-054_20260628_daystart-credential-kis-inject/verification.md +++ /dev/null @@ -1,101 +0,0 @@ -# BAT-054: Verification - -- Status: draft -- Batch: BAT-054 - -## Acceptance Mapping - -### REQ-113 — KISConfig 소스 우선순위 (미충족 AC) - -**AC-2**: Given TOML에 `credential_fetch_enabled = true`이고 portfolio-hub fetch가 유효한 `BrokerCredentialPayload`를 반환, When 데몬 기동(그날-시작), Then `KISConfig.app_key`, `KISConfig.app_secret`, `KISConfig.account_ref`가 fetch 결과로 구성된다. - -- Evidence: `test_kis_reinit_callback_called_on_success` — fetch 성공 시 callback이 `BrokerCredentialPayload(account_ref="stub-account", app_key="stub-app-key", app_secret="stub-app-secret")`으로 호출됨 확인. PASSED. -- Evidence: `test_kis_broker_reinitialize_updates_credentials` — `KISBroker.reinitialize(cred)` 호출 후 `broker._account_ref/app_key/app_secret`이 cred 값으로 교체됨 + L1 token 무효화 확인. PASSED. -- Evidence: `test_build_kis_reinit_callback_returns_callable_for_kis` — broker=kis + fetch enabled 시 callback callable 반환 + 실제 broker.reinitialize 동작 확인. PASSED. -- Note: 실 KISBroker를 KIS API에 연결한 통합 테스트는 portfolio-hub dcy-010 배포 후 가능. 현재 stub 기반으로 핵심 경로 검증. - -**AC-4**: Given break-glass 활성화 상태, When 데몬 기동 로그 확인, Then break-glass 활성화 사실이 INFO로 기록된다. - -- Evidence (보강): `test_break_glass_log_contains_break_glass_keyword` — `TREX_KIS_BROKER_BREAK_GLASS=true` 시 로그에 "break-glass" 키워드 포함 확인. PASSED. -- Note: BAT-053에서 break-glass 경로는 구현되었으나 로그 content assert가 없었음. 이번 batch에서 추가. - -**REQ-113 AC-1, AC-3, AC-5, AC-6, AC-7**: BAT-053 Implemented. 회귀 없음 (전체 스위트 757 passed, 회귀 0). - -### REQ-115 — 그날-시작 장중 무중단 (미충족 AC) - -**AC-1**: Given 그날-시작 로드 성공(`credential_fetch_enabled = true`) 후 portfolio-hub를 내려(장중 장애 주입), When BuyIntent 또는 SellIntent를 처리, Then KIS 브로커 어댑터가 캐시된 인증정보로 주문을 처리하고 `PortfolioHubUnavailableError`로 인해 주문이 차단되지 않는다. - -- Evidence: `test_kis_reinit_callback_called_on_success` — 그날-시작에 callback(=broker.reinitialize)이 호출됨으로써 KISBroker가 fetched 인증정보로 업데이트됨. PASSED. -- Evidence: `test_kis_broker_reinitialize_updates_credentials` — reinitialize 후 broker가 새 인증정보 보유, 이후 KIS API 호출은 broker 자체 인증정보로 수행 (portfolio-hub 불필요). PASSED. -- Evidence: `test_kis_reinit_callback_failure_does_not_flip_entry_allowed` — reinit callback 실패 시에도 entry_allowed 유지 (기존 env 인증정보로 계속 운행). PASSED. -- Note: 장중 portfolio-hub 다운 상태에서 실 KISBroker 주문 처리를 끝까지 검증하려면 통합 테스트 환경이 필요. 현재 stub 기반으로 핵심 속성(reinitialize 완료 후 broker가 portfolio-hub와 분리됨) 검증. - -**AC-3**: Given 그날 로드 성공 후 portfolio-hub가 잠시 다운되었다가 복구, When 그날 중 재fetch 트리거 없음, Then 그날 인증정보는 재load 없이 최초 fetch 값 그대로 유지된다. - -- Evidence: `test_kis_reinit_callback_cached_for_day_not_called_twice` — 동일 날 두 번째 run_once에서 callback 재호출 없음(캐시 hit). PASSED. -- Evidence: `test_successful_credential_cached_for_day` (BAT-053) — fetch count = 1 유지. PASSED. - -**REQ-115 AC-2, AC-4, AC-5, AC-6**: BAT-053 Implemented. 회귀 없음. - -## Evidence - -### Test Results - -``` -pytest tests/ -q -757 passed, 47 skipped in 1.59s -``` - -기존 베이스라인: 745 passed, 47 skipped (BAT-053 완료 시점) -이번 batch 신규 테스트: +12 (29 total in test_snapshot_scheduler.py) -회귀: 0 - -### 신규 테스트 목록 (12개) - -| 테스트명 | 검증 AC | 결과 | -| --- | --- | --- | -| `test_kis_reinit_callback_called_on_success` | REQ-113 AC-2, REQ-115 AC-1 | PASS | -| `test_kis_reinit_callback_not_called_when_fail_closed` | REQ-113 AC-2 | PASS | -| `test_kis_reinit_callback_not_called_when_disabled` | REQ-113 AC-1 회귀 | PASS | -| `test_kis_reinit_callback_failure_does_not_flip_entry_allowed` | 설계 안전망 | PASS | -| `test_kis_reinit_callback_cached_for_day_not_called_twice` | REQ-115 AC-3 | PASS | -| `test_kis_reinit_callback_none_when_break_glass` | REQ-113 AC-3 + AC-2 | PASS | -| `test_break_glass_log_contains_break_glass_keyword` | REQ-113 AC-4 보강 | PASS | -| `test_kis_broker_reinitialize_updates_credentials` | REQ-115 AC-1, REQ-113 AC-2 | PASS | -| `test_kis_broker_reinitialize_preserves_base_url_and_flags` | REQ-115 AC-1 | PASS | -| `test_build_kis_reinit_callback_returns_none_for_paper` | REQ-113 AC-2 | PASS | -| `test_build_kis_reinit_callback_returns_none_when_disabled` | REQ-113 AC-1 | PASS | -| `test_build_kis_reinit_callback_returns_callable_for_kis` | REQ-113 AC-2, REQ-115 AC-1 | PASS | - -### fail-closed 불변식 회귀 확인 - -BAT-053 구현 테스트 17개 모두 PASS: -- `test_run_once_no_credential_fetch_when_disabled` — opt-in off 회귀 -- `test_run_once_portfolio_hub_unavailable_sets_fail_closed` — AC-1 -- `test_run_once_broker_null_sets_fail_closed` — AC-2 -- `test_run_once_trading_disabled_sets_fail_closed` — AC-3 -- `test_intent_service_buy_rejected_when_entry_not_allowed` — AC-4 -- `test_intent_service_sell_passes_when_entry_not_allowed` — AC-5 -- 기타 기존 12개 PASS - -## Result - -- Ready for Release: false -- Blocking Issues: - - 없음 (코드/테스트 레벨 구현 및 검증 완료) -- Missing evidence (비차단): - - REQ-115 AC-1 통합 검증: 실 portfolio-hub + 실 KIS mock(openapivts) 환경에서 그날-시작 fetch → KISBroker reinitialize → 장중 주문 처리까지의 end-to-end 검증은 portfolio-hub dcy-010 배포 후 가능. 현재 stub 기반 단위 검증으로 충분하다고 판단하나, 팀장 확인 필요. - - REQ-113 AC-9 (break-glass 전용 경보): BAT-053 Notes에 기재된 "AC-9 전용 테스트 보강"은 REQ-113에 AC-9 항목이 없음을 확인 (AC는 1~7까지). BAT-053 Notes의 "AC-9"는 REQ-114 AC-9(그날 고정 상태)를 지칭하며, 이는 `test_entry_allowed_fixed_for_day_no_refetch`(PASS)로 커버됨. 별도 break-glass 경보 요구사항 없음 확인. - -### 팀장 검수 요청 사항 - -1. REQ-115 AC-1 stub 기반 검증으로 `confirm-batch-verification` 진행 가능한지 판단 (portfolio-hub dcy-010 배포 전 단계에서의 기준) -2. REQ-113 Status 갱신 권한: 이번 batch 완료로 AC-2/AC-4 충족 → Status `Approved` → `Implemented` 갱신 여부 - -### QC 체크리스트 (qc-reviewer용) - -- [ ] BAT-054 신규 테스트 12개 독립 실행 PASS 확인 -- [ ] fail-closed 불변식 미파괴 확인 (opt-in=False 경로에서 gateway 비활성, `entry_allowed_fn=None`) -- [ ] app_secret 평문 로그 미노출 확인 (기존 AC-7 테스트 PASS, reinitialize 로그 없음) -- [ ] domain 순수성 불변 확인 (`trex_core/domain/` 변경 없음) -- [ ] AMQP wire 계약 변경 없음 확인 (trex-orb-v1/trex-tsl-v1 영향 없음) diff --git a/docs/batches/bat-055_20260704_slots-strategy-field-filter/design.md b/docs/batches/bat-055_20260704_slots-strategy-field-filter/design.md deleted file mode 100644 index 30ea67e..0000000 --- a/docs/batches/bat-055_20260704_slots-strategy-field-filter/design.md +++ /dev/null @@ -1,90 +0,0 @@ -# BAT-055 Design - -- Status: confirmed -- Batch: BAT-055 - -## 1. `PortfolioItem` 필드 확장 - -**결정**: `application/portfolio.py` 의 `PortfolioItem` dataclass에 두 필드 추가. - -```python -@dataclass(frozen=True) -class PortfolioItem: - ... - active_strategy_id: Optional[str] = None # 기존 유지 - strategy_params: dict = ... # 기존 유지 - strategy_name: Optional[str] = None # REQ-116 신규 - markets: list[str] = field(default_factory=list) # REQ-116 신규 -``` - -- `strategy_name`: portfolio-hub 응답의 `strategy_name` 필드. 미제공 시 None. -- `markets`: portfolio-hub 응답의 `markets` 배열. 미제공 시 []. -- 두 필드 모두 `frozen=True` dataclass 의 `default`/`default_factory` 로 하위 호환. 기존 생성자 호출(`PortfolioItem(portfolio_id=..., symbol=..., cash_balance=..., asset_quantity=...)`)은 수정 없이 동작. - -## 2. `hub_read.py` 파싱 확장 - -**결정**: `get_portfolios()` 의 `PortfolioItem(...)` 생성 시 두 필드 추가 파싱. - -```python -strategy_name = raw.get("strategy_name") or None # str or None -markets_raw = raw.get("markets") -markets = list(markets_raw) if isinstance(markets_raw, list) else [] -``` - -- `strategy_name`: 빈 문자열 → None 처리 (`or None`). -- `markets`: list 타입이 아닌 경우(None, 비배열) → `[]`. -- 구버전 hub 응답(키 미존재) → safe default. 데몬 비중단. - -## 3. `slot_to_dict()` 신규 필드 추가 - -**결정**: strategy 3필드는 `snapshot_store.get(portfolio_id)` 의 `PortfolioItem` 에서 읽어 row에 추가. - -```python -# strategy fields (REQ-116 additive — source: PortfolioItem via SnapshotStore) -strategy_name_val: Optional[str] = None -strategy_params_val: dict = {} -markets_val: list = [] -if snapshot_store is not None and s.portfolio_id: - snap = snapshot_store.get(s.portfolio_id) - if snap is not None: - strategy_name_val = snap.item.strategy_name - strategy_params_val = snap.item.strategy_params - markets_val = list(snap.item.markets) -d["strategy_name"] = strategy_name_val -d["strategy_params"] = strategy_params_val -d["markets"] = markets_val -``` - -- `asset_qty`/`cash_balance` 읽기(기존)와 동일 `snap` 참조 위치 — 중복 조회 없음. -- safe default: `PortfolioItem.strategy_params` 은 이미 `{}` default; `strategy_name` 은 None; `markets` 는 []. -- **REQ-074 AC-5 노트**: 기존 excluded 필드(`strategy_id`, `sell_strategy_id`, `active_strategy_id`) 는 여전히 제외. 이번에 추가하는 것은 `PortfolioItem` 출처의 `strategy_name`/`strategy_params`/`markets` — 의미적으로 다른 출처. - -## 4. `make_slots_rpc_handler()` strategy 필터 설계 - -**결정**: `strategy` 파라미터 파싱 + `slot_rows` 빌드 완료 후 Python 레벨 후처리 필터. - -흐름: -1. `available_slots` 계산 (필터 이전 — REQ-073 AC-6 불변) -2. `status_filter` → DB 레벨 `list_slots(status_filter=...)` 실행 (기존) -3. `slot_rows` 빌드 (기존 + strategy 3필드 포함) -4. **신규**: `strategy` 파라미터가 있으면 Python 레벨 필터 - ```python - strategy_filter = _opt_str(body.get("strategy")) - if strategy_filter is not None: - slot_rows = [r for r in slot_rows if r.get("strategy_name") == strategy_filter] - ``` -5. degraded/available_slots 응답 조립 (기존) - -`available_slots` 는 4번 이전에 이미 계산 완료 → strategy 필터 영향 없음. - -## 5. REQ-074 AC-5 호환성 - -- 기존 테스트 `test_strategy_fields_absent` 는 `strategy_id`, `sell_strategy_id`, `strategy_params`, `active_strategy_id` 를 체크. - - `strategy_id` / `sell_strategy_id` / `active_strategy_id`: 여전히 응답에 없음 (불변). - - `strategy_params`: **이번에 추가**. 단, 이는 slot 필드(REQ-074 AC-5 의 제외 대상)가 아니라 `PortfolioItem.strategy_params` — 다른 소스. REQ-116 은 REQ-074 AC-5 의 additive 재도입이므로 기존 테스트 `test_strategy_fields_absent` 를 REQ-116 범위에 맞게 갱신한다: `strategy_params`(hub 출처, 신규 노출 허용) 는 제외 체크 목록에서 제거하고, `strategy_id`/`sell_strategy_id`/`active_strategy_id` 만 여전히 체크. - -## 6. 계약 테스트 (`EXPECTED_SLOT_FIELDS`) - -- `EXPECTED_SLOT_FIELDS` 는 `Slot` 도메인 dataclass 필드 집합 — strategy 3필드는 `Slot` 에 없으므로 변경 불필요. -- `slot_to_dict()` row 필드는 `EXPECTED_SLOT_FIELDS` 와 1:1 매핑이 아니다(row에는 hub/overlay 필드 포함). `EXPECTED_SLOT_FIELDS` 는 `Slot` domain model 계약이므로 수정 대상 아님. -- REQ-116 AC-8 "계약 테스트 갱신" 은 `test_strategy_fields_absent` 갱신 + 신규 slot row 필드 단언 테스트로 충족. diff --git a/docs/batches/bat-055_20260704_slots-strategy-field-filter/implementation.md b/docs/batches/bat-055_20260704_slots-strategy-field-filter/implementation.md deleted file mode 100644 index b5414fe..0000000 --- a/docs/batches/bat-055_20260704_slots-strategy-field-filter/implementation.md +++ /dev/null @@ -1,63 +0,0 @@ -# BAT-055 Implementation - -- Status: complete -- Batch: BAT-055 - -## Changed Files - -### application (core logic) - -- `trex_core/application/portfolio.py` - - `PortfolioItem`: `strategy_name: Optional[str] = None`, `markets: list = field(default_factory=list)` 추가 (REQ-116) - - `slot_to_dict()`: strategy_name/strategy_params/markets 3필드를 SnapshotStore → PortfolioItem 경유로 row에 추가. safe default 적용(snapshot 없거나 item 없으면 null/{}/[]) - -- `trex_core/application/messaging_handlers.py` - - `make_slots_rpc_handler()` docstring: `strategy?: str` 파라미터 설명 추가 - - handler 내부: `strategy_filter = _opt_str(body.get("strategy"))` 파싱 추가 - - `available_slots` 계산 이후 Python 레벨 strategy 후처리 필터 추가 (REQ-073 AC-6 불변 보장) - -### adapters - -- `trex_core/adapters/portfolio/hub_read.py` - - `get_portfolios()`: `strategy_name` (or None), `markets` (list or []) 파싱 추가 - - `PortfolioItem(...)` 생성 시 두 필드 주입 - -### tests - -- `tests/application/test_slots_handler.py` - - `test_strategy_fields_absent`: REQ-116 additive 재도입 반영 — `strategy_params` 제외 체크 제거, 신규 필드 present 체크 추가 - -- `tests/application/test_slots_handler_strategy.py` (신규) - - AC-1~AC-8 전수 테스트 (16 케이스) - -- `tests/application/test_slots_handler_strategy_state.py` - - `test_no_strategy_reasoning_fields`: slot-origin 제외 유지하되, PortfolioItem-origin 필드 present 체크 추가 - -- `tests/adapters/portfolio/test_hub_read.py` - - 기존 `test_get_portfolios_parses_envelope_and_filters_by_daemon`: safe default 확인 어서션 추가 - - 신규 4개 테스트: REQ-116 파싱 AC-1/2 - -### docs - -- `docs/interface-contract.md` - - `trex.slots.req` 요청 payload: `strategy: str (optional)` 추가 - - 응답 슬롯 행: `strategy_name`/`strategy_params`/`markets` 추가 - - 필드 소스 테이블: 3필드 + 정본 소스 추가 - - REQ-074 AC-5 주석 갱신: slot-origin 제외 유지, PortfolioItem-origin 필드 구분 명시 - - `PortfolioHubReadPort.PortfolioItem Fields`: `strategy_name`/`markets` 추가 - - Change Log: BAT-055 항목 추가 - -## Additive-Only Invariants Confirmed - -1. 기존 `filter` 동작 변경 없음 — strategy 필터는 독립 파라미터 -2. `available_slots` 계산 위치 불변 — strategy 필터 이전(DB count 기반) -3. 기존 slot row 필드 값 불변 — 신규 3필드 추가만 -4. `EXPECTED_SLOT_FIELDS` (Slot domain dataclass 계약) 변경 없음 — slot_to_dict row 확장은 별도 -5. strategy_id/sell_strategy_id/active_strategy_id 는 여전히 row에 없음 (REQ-074 AC-5 유지) - -## REQ-074 CHG Reference (REQ-116 Notes) - -REQ-074 AC-5 의 "strategy 계열 제외" 불변식을 파괴하지 않는다. 본 배치가 도입하는 것은 -PortfolioItem(hub) 출처의 `strategy_name`/`strategy_params`/`markets` — slot 도메인 전략 필드가 아님. -REQ-116 이 별도 REQ 로 additive 재도입 근거를 남기므로 REQ-074 change-req 별도 생성 불필요 -(REQ-116 Notes 참조). diff --git a/docs/batches/bat-055_20260704_slots-strategy-field-filter/index.md b/docs/batches/bat-055_20260704_slots-strategy-field-filter/index.md deleted file mode 100644 index 0ff0b65..0000000 --- a/docs/batches/bat-055_20260704_slots-strategy-field-filter/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# BAT-055: slots 계약 strategy 필드 + 필터 (additive) - -- Status: implementing -- Profile: standard -- Discovery: dcy-001/dcy-002 v3 FR-8 (tvr-v1 배관 ②) -- Created: 2026-07-04 - -## Included REQ - -- REQ-116: `trex.slots.req` 전략 필드 노출 + `strategy` 요청 필터 (additive) - -## Documents - -- [planning.md](./planning.md) -- [design.md](./design.md) -- [implementation.md](./implementation.md) -- [verification.md](./verification.md) diff --git a/docs/batches/bat-055_20260704_slots-strategy-field-filter/planning.md b/docs/batches/bat-055_20260704_slots-strategy-field-filter/planning.md deleted file mode 100644 index e750dc8..0000000 --- a/docs/batches/bat-055_20260704_slots-strategy-field-filter/planning.md +++ /dev/null @@ -1,56 +0,0 @@ -# BAT-055 Planning - -- Status: confirmed -- Batch: BAT-055 -- Profile: standard - -## Included REQ - -- REQ-116: `trex.slots.req` 전략 필드 노출 + `strategy` 요청 필터 (additive) - -## Delivery Plan - -additive 확장 — 기존 필드/동작 불변 원칙 하에 아래 4가지를 구현한다. - -1. **`PortfolioItem` 필드 추가** (`application/portfolio.py`): - - `strategy_name: Optional[str]` 추가 (default None) - - `markets: list[str]` 추가 (default_factory=list) - - `strategy_params` 은 이미 존재 — 변경 불필요 - -2. **`hub_read.py` 파싱 추가** (`adapters/portfolio/hub_read.py`): - - `get_portfolios()` 에서 `strategy_name`, `markets` 파싱 - - 미존재 키 → safe default (None / []) - -3. **`slot_to_dict()` 신규 필드 추가** (`application/portfolio.py`): - - `strategy_name` (null), `strategy_params` ({}), `markets` ([]) 3필드를 슬롯 row에 추가 - - `snapshot_store`에서 해당 포트폴리오의 `PortfolioItem` 참조 - - `PortfolioItem` 없으면 safe default - -4. **`make_slots_rpc_handler()` strategy 필터** (`application/messaging_handlers.py`): - - 요청에서 `strategy: str` 선택적 파라미터 파싱 - - `available_slots` 계산 → strategy 필터 이전에 수행 (REQ-073 AC-6 불변) - - `slot_rows` 빌드 후 Python 레벨 strategy 필터 (status 필터는 DB, strategy 필터는 Python) - - 기존 status `filter` AND strategy → 두 필터 모두 만족하는 행만 - - 미지정 시 기존과 완전 동일 - -## Dependencies - -- BAT-050 (reconcile-warning): 완료. `slot_to_dict` 의 `warn_store` 파라미터 패턴 선례. -- REQ-074 AC-5 additive 재도입: REQ-116 Notes 참조. `strategy_id`/`sell_strategy_id`/`active_strategy_id` 는 여전히 제외 — `strategy_name`/`strategy_params`/`markets`(PortfolioItem 출처)만 추가. - -## Design Gate - -**Design 필요: yes (인터페이스 변경 + 필터 흐름 설계)** - -- `slot_to_dict` strategy 필드 읽기 위치: `snapshot_store.get(portfolio_id).item` 경유. -- `strategy_params` 는 `PortfolioItem.strategy_params` (이미 존재) — `slot_to_dict` 에서 읽기만 추가. -- `strategy` 필터 위치: `slot_rows` 빌드 완료 후 Python 후처리 필터. -- `available_slots` 는 필터 이전 계산 확인. - -## Milestones - -1. `PortfolioItem` 필드 추가 + `hub_read.py` 파싱 (AC-1/2 기반) -2. `slot_to_dict` 3필드 추가 (AC-1/2) -3. `make_slots_rpc_handler` strategy 필터 추가 (AC-3/4/5/6) -4. 테스트 작성: AC-1~8 + 회귀 (AC-7) -5. `interface-contract.md` 갱신 (AC-8) diff --git a/docs/batches/bat-055_20260704_slots-strategy-field-filter/verification.md b/docs/batches/bat-055_20260704_slots-strategy-field-filter/verification.md deleted file mode 100644 index 9ec42f5..0000000 --- a/docs/batches/bat-055_20260704_slots-strategy-field-filter/verification.md +++ /dev/null @@ -1,44 +0,0 @@ -# BAT-055 Verification - -- Status: draft-batch-verification -- Batch: BAT-055 - -## Pytest 실행 결과 - -``` -777 passed, 47 skipped in 1.71s -``` - -- 총 실행: 824건 (= 777 통과 + 47 스킵) -- 실패: 0 -- 스킵: 47 (TREX_POSTGRESQL_TEST_DSN/TREX_RABBITMQ_URL 미설정 — 인프라 연동 테스트, bat-055 범위 외) - -## AC 매핑 - -| AC | 테스트 | 결과 | -|----|--------|------| -| AC-1: 필드 노출 | `test_ac1_strategy_fields_present_when_hub_provides_them` | PASS | -| AC-2: safe-default | `test_ac2_safe_defaults_when_hub_does_not_provide_strategy_fields`, `test_ac2_no_snapshot_store_uses_safe_defaults` | PASS | -| AC-2 hub parsing | `test_get_portfolios_strategy_fields_absent_safe_default`, `test_get_portfolios_strategy_name_empty_string_becomes_none`, `test_get_portfolios_markets_non_list_becomes_empty` | PASS | -| AC-3: 필터 매칭 | `test_ac3_strategy_filter_matching`, `test_ac3_strategy_filter_no_match_returns_empty` | PASS | -| AC-4: 필터 미지정 | `test_ac4_no_strategy_param_returns_all_slots`, `test_ac4_response_keys_unchanged_without_strategy_param` | PASS | -| AC-5: AND 조합 | `test_ac5_strategy_and_status_filter_and_combination`, `test_ac5_strategy_and_status_no_match_returns_empty` | PASS | -| AC-6: available_slots 불변 | `test_ac6_available_slots_invariant_with_strategy_filter` | PASS | -| AC-7: orb·tsl 회귀 없음 | `test_ac7_orb_tsl_regression_no_strategy_param` | PASS | -| AC-8: 계약 테스트 갱신 | `test_ac8_contract_test_strategy_fields_in_slot_row`, `test_strategy_fields_absent`(갱신), `test_no_strategy_reasoning_fields`(갱신) | PASS | - -## 미지정 요청 응답 byte-수준 동일성 확인 근거 - -`strategy` 파라미터 미지정 시 `strategy_filter = None` → `slot_rows = [r for r in slot_rows if ...]` 블록 진입 안 함(if 조건 false). 슬롯 row 목록은 코드 경로상 동일. 신규 필드 3개(`strategy_name`/`strategy_params`/`markets`) 는 additive 추가이며 기존 필드 값에 영향 없음. - -AC-7 테스트 `test_ac7_orb_tsl_regression_no_strategy_param` 에서 기존 mandatory 필드 전수 확인 (`slot_id`, `status`, `symbol`, `qty`, `avg_price`, `entry_price`, `portfolio_id`, `forced_close_at`, `universe_detached`, `occupancy_seq`, `asset_quantity`, `cash_balance`, `cash_estimated`, `warned`, `watchdog`, `paused`, `reconcile_warned`) 모두 present 확인. - -## 기존 스위트 회귀 확인 - -bat-055 범위 외 기존 테스트 770+건 전부 통과 (slot domain / execution / reconcile / broker / messaging / runtime 포함). - -## 주의점 / 미해결 - -1. **배포 선행 조건**: portfolio-hub REQ-055(`strategy_name`/`markets`/`strategy_params` 노출)가 배포된 뒤 본 배치를 배포해야 한다. 선행 배포 전에는 AC-2 safe-default로 동작(정상). -2. `markets` 필드 타입: `PortfolioItem.markets: list` (런타임 엘리먼트 타입 미강제). portfolio-hub가 `list[str]` 를 보장하면 정상 동작. 타입 강제가 필요하면 별도 change-req. -3. `strategy_params` 는 이미 `PortfolioItem`에 존재했으나 `slot_to_dict` 에서 노출하지 않았다. 본 배치에서 노출하므로 기존 소비자(orb/tsl)가 unknown 필드로 무시하는 계약 준수 확인 필요(REQ-116 Notes — unknown 필드 무시 계약). diff --git a/docs/batches/bat-056_20260706_kis-broker-venue/batch.md b/docs/batches/bat-056_20260706_kis-broker-venue/batch.md deleted file mode 100644 index d9fa4b9..0000000 --- a/docs/batches/bat-056_20260706_kis-broker-venue/batch.md +++ /dev/null @@ -1,57 +0,0 @@ -# BAT-056: KIS Broker Venue Parameterization (NXT/KRX) - -- ID: bat-056 -- Slug: kis-broker-venue -- Status: In Progress -- Date: 2026-07-06 -- Owner: trex-core-dev -- Risk: HIGH (라이브 주문 경로 — worktree+PR+QC(opus) 플로) - -## REQs - -| REQ | Title | Status | -| --- | --- | --- | -| REQ-117 | OrderRequest venue additive 필드 | Approved | -| REQ-118 | KIS 브로커 어댑터 venue 파라미터화 (EXCG_ID_DVSN_CD) | Approved | - -## Scope - -- `trex_core/application/broker_port.py` — `OrderRequest.venue` 필드 additive 추가 -- `trex_core/adapters/broker/kis.py` — submit_order, cancel_order, poll_fill, get_current_price 변경 -- `trex_core/adapters/broker/paper.py` — cancel_order 시그니처 정합 -- `tests/adapters/broker/` — 신규 venue 테스트 + 기존 회귀 -- `docs/interface-contract.md` — OrderRequest venue 필드 + EXCG 명세 업데이트 -- `docs/runtime-flows.md` — venue 전달 경로 명시 - -## Out of Scope - -- AMQP wire 계약 변경 (intent 메시지 venue 전달은 tvr 팬-인 batch 별도 범위) -- orb/tsl 소비 경로 (intent, slots wire) 변경 -- SOR 동작 지원 -- VTS NXT 검증 (단위 테스트 + 실전 소액 스모크 별도) - -## Additive Constraint - -venue 미지정(기본 KRX) 경로에서 기존 orb/tsl 동작이 byte-동일 유지: -- submit_order: `EXCG_ID_DVSN_CD="KRX"` 추가만 다름 (기존 미전송 → KRX 명시) -- cancel_order: `EXCG_ID_DVSN_CD="KRX"` 추가만 다름 -- poll_fill: TR 유지(TTTC8001R/VTTC8001R) — NXT 체결 포함 여부 미확인(open item) -- get_current_price: market 기본값 "J" 유지 - -## AC Coverage - -| AC | REQ | 대상 테스트 위치 | -| --- | --- | --- | -| REQ-117 AC-1 | venue 기본값 KRX | tests/adapters/broker/test_venue.py | -| REQ-117 AC-2 | NXT 지정 | tests/adapters/broker/test_venue.py | -| REQ-117 AC-3 | 기존 회귀 | 전체 pytest | -| REQ-117 AC-4 | 위치 인수 순서 불변 | tests/adapters/broker/test_venue.py | -| REQ-117 AC-5 | paper broker venue 무영향 | tests/adapters/broker/test_venue.py | -| REQ-118 AC-1 | submit KRX EXCG 포함 | tests/adapters/broker/test_venue.py | -| REQ-118 AC-2 | submit NXT EXCG 포함 | tests/adapters/broker/test_venue.py | -| REQ-118 AC-3 | cancel NXT EXCG 포함 | tests/adapters/broker/test_venue.py | -| REQ-118 AC-4 | poll_fill TR 미확인 open item | design.md + 코드 주석 | -| REQ-118 AC-5 | get_current_price 기본 J | tests/adapters/broker/test_venue.py | -| REQ-118 AC-6 | get_current_price UN | tests/adapters/broker/test_venue.py | -| REQ-118 AC-7 | 기존 회귀 (EXCG 추가만 다름) | tests/adapters/broker/test_venue.py | -| REQ-118 AC-8 | paper cancel_order 시그니처 정합 | tests/adapters/broker/test_venue.py | diff --git a/docs/batches/bat-056_20260706_kis-broker-venue/design.md b/docs/batches/bat-056_20260706_kis-broker-venue/design.md deleted file mode 100644 index 7bdfa5a..0000000 --- a/docs/batches/bat-056_20260706_kis-broker-venue/design.md +++ /dev/null @@ -1,86 +0,0 @@ -# BAT-056 Design - -## 1. OrderRequest.venue 필드 (REQ-117) - -### 결정 -`OrderRequest` frozen dataclass의 **마지막 위치**에 `venue: str = "KRX"` 추가. - -```python -@dataclass(frozen=True) -class OrderRequest: - slot_id: str - side: str - symbol: str - qty: int - limit_price: Optional[int] = None - order_type: str = "00" - venue: str = "KRX" # additive (REQ-117): KRX | NXT | SOR -``` - -- 기존 위치 인수(`slot_id`, `side`, `symbol`, `qty`, `limit_price`, `order_type`) 순서 불변. -- venue 기본값 "KRX"로 기존 orb/tsl 미지정 호출 자동 KRX 적용. -- "SOR" 정의하되 현재 운영 비권고 (OQ-1 잔여, REQ-117 Notes 참조). - -## 2. cancel_order 시그니처 venue 파라미터 (REQ-118) - -### 결정: 시그니처 venue 파라미터 추가 방식 - -대안 검토: -- **A안(채택)**: `cancel_order(broker_order_id, *, side, symbol, qty, venue="KRX")` — 호출자가 원주문 venue를 명시 전달. 명시적이고 기본값으로 하위 호환. -- B안: execution context에서 venue 보존 — 더 복잡, 현재 OrderAck에 venue 저장 필요. - -A안 채택 이유: 단순성, 기본값 하위 호환, BrokerPort 프로토콜에서 명시적 의미론 확립. - -BrokerPort 프로토콜: -```python -def cancel_order(self, broker_order_id: str, *, side: str, symbol: str, qty: int, venue: str = "KRX") -> bool: -``` - -KISBroker.cancel_order body에 `"EXCG_ID_DVSN_CD": venue` 추가. -PaperBroker.cancel_order: 시그니처만 정합, venue 무시. - -## 3. poll_fill TR 선택 — open item (REQ-118 AC-4) - -### 현황 -- 구 TR `TTTC8001R` (mock: `VTTC8001R`): 현재 사용 중. NXT 체결 포함 여부 **미확인**. -- 신 TR `TTTC0081R`: EXCG=ALL 또는 venue 지정으로 NXT 체결 조회 가능(KIS 개발자 센터 문서 기준). - -### 확인 시도 결과 -웹 접근 불가 환경 — KIS 개발자 센터 문서 직접 조회 불가. 코드/기존 문서 내 관련 근거 없음. - -### 결정: 보수 구현 (기존 TR 유지) -- **기존 TR(`TTTC8001R`/`VTTC8001R`) 유지** — KRX 경로 동작 절대 불변. -- 코드 주석에 open item 명시. -- verification에 open item 기록: "NXT 체결 조회는 실측 확인 필요(운영 스모크)". - -### 향후 전환 조건 및 방법 (미구현, 운영 협의 후) -- 조건: NXT 체결 주문 발생 후 TTTC8001R 응답에 NXT 체결 미포함 확인(실측). -- 방법: `poll_fill` 파라미터 `venue` 추가 + venue="NXT"이면 TTTC0081R 사용. - - mock(VTS) 대응 주의: VTS TTTC0081R 지원 여부 미확인 → mock일 때 구 TR fallback 고려. -- 이 전환은 별도 batch로 처리한다(REQ-118 AC-4 follow-up). - -## 4. get_current_price market 파라미터 (REQ-118 AC-5,6) - -```python -def get_current_price(self, symbol: str, market: str = "J") -> Optional[Decimal]: -``` - -- `FID_COND_MRKT_DIV_CODE` → `market` 값 사용. -- 기본값 "J" → 기존 safety-sell 경로 무영향. -- "UN" 지정 시 나스닥/해외 시세 조회 가능(NXT 연장세션 활용 예정). - -BrokerPort 프로토콜: -```python -def get_current_price(self, symbol: str, market: str = "J") -> Optional[Decimal]: -``` - -## 5. 실측 근거 (design 인용) - -- 2026-07-06: 실전 계좌 NXT 주문(TTTC0012U + EXCG=NXT) rt_cd=0, 취소(TTTC0013U + EXCG=NXT) rt_cd=0·잔여 0건 확인. -- 토큰 재요청 1분 rate-limit 확인. 기존 kis.py 토큰 캐시 방식 유지 — 신 TR 전환 시에도 캐시 재사용 경로 유지 필요. - -## 6. additive 검증 전략 - -- venue 미지정 submit_order: 기존 대비 `EXCG_ID_DVSN_CD="KRX"` 필드 추가만 다름 → fixture로 body 캡처 검사(AC-7). -- 기존 `cancel_order` 호출자: keyword-only 파라미터이므로 venue 없이 호출 시 기본값 KRX 자동 적용 → 기존 테스트 회귀 없음. -- 기존 `get_current_price` 호출자: 파라미터 미지정 → market="J" 자동 적용 → 기존 safety-sell 경로 무영향. diff --git a/docs/batches/bat-056_20260706_kis-broker-venue/planning.md b/docs/batches/bat-056_20260706_kis-broker-venue/planning.md deleted file mode 100644 index 8d22b23..0000000 --- a/docs/batches/bat-056_20260706_kis-broker-venue/planning.md +++ /dev/null @@ -1,62 +0,0 @@ -# BAT-056 Planning - -## Task Breakdown - -### T-1: OrderRequest.venue 필드 추가 (REQ-117) -- 파일: `trex_core/application/broker_port.py` -- 작업: `OrderRequest` frozen dataclass에 `venue: str = "KRX"` 필드 추가 (마지막 위치) -- 제약: 기존 위치 인수 순서(`slot_id`, `side`, `symbol`, `qty`, `limit_price`, `order_type`) 불변 - -### T-2: BrokerPort.cancel_order 시그니처 venue 파라미터 추가 (REQ-118) -- 파일: `trex_core/application/broker_port.py` -- 작업: `cancel_order` 프로토콜 시그니처에 `venue: str = "KRX"` 파라미터 추가 -- 설계 결정: 시그니처 파라미터 추가 방식 채택(호출자 보존 방식 대비 명시적, 기본값으로 하위 호환) - -### T-3: KISBroker.submit_order EXCG 명시 전송 (REQ-118 AC-1,2) -- 파일: `trex_core/adapters/broker/kis.py` -- 작업: body에 `"EXCG_ID_DVSN_CD": req.venue` 항상 추가 - -### T-4: KISBroker.cancel_order venue 파라미터 + EXCG 전송 (REQ-118 AC-3,8) -- 파일: `trex_core/adapters/broker/kis.py` -- 작업: 시그니처에 `venue: str = "KRX"` 추가, body에 `"EXCG_ID_DVSN_CD": venue` 추가 - -### T-5: KISBroker.poll_fill TR 유지 + open item 기록 (REQ-118 AC-4) -- 파일: `trex_core/adapters/broker/kis.py` -- 작업: 구 TR(TTTC8001R/VTTC8001R) 유지, 코드 주석에 NXT 체결 포함 여부 미확인 명시 -- 이유: 웹 접근 불가 환경 — KIS 개발자 센터 문서 확인 불가. 보수 원칙: 기존 TR 유지, open item으로 기록 - -### T-6: KISBroker.get_current_price market 파라미터 추가 (REQ-118 AC-5,6) -- 파일: `trex_core/adapters/broker/kis.py` -- 작업: 시그니처에 `market: str = "J"` 추가, `FID_COND_MRKT_DIV_CODE` → market 값 사용 - -### T-7: PaperBroker.cancel_order 시그니처 정합 (REQ-118 AC-8, REQ-117 AC-5) -- 파일: `trex_core/adapters/broker/paper.py` -- 작업: cancel_order 시그니처에 `venue: str = "KRX"` 추가, 내부 로직 무변경(venue 무시) - -### T-8: 신규 테스트 작성 (REQ-117 AC-1~5, REQ-118 AC-1~8) -- 파일: `tests/adapters/broker/test_venue.py` (신규) -- 작업: 13개 AC 커버 테스트 - -### T-9: 기존 테스트 회귀 확인 (REQ-117 AC-3, REQ-118 AC-7) -- 기존 테스트가 `OrderRequest` venue 미지정 경로를 통과하는지 확인 -- 기존 submit_order fixture가 EXCG 필드 추가로 인한 페이로드 변화 감지 확인 - -### T-10: 문서 업데이트 -- `docs/interface-contract.md` — OrderRequest venue 필드 추가 -- `docs/runtime-flows.md` — venue 전달 경로 명시 - -## Dependency Order - -T-1 → T-2 → T-3, T-4, T-5, T-6 (병렬) → T-7 → T-8 → T-9 → T-10 - -## Risk Points - -1. `cancel_order` 시그니처 변경: BrokerPort 프로토콜 + KIS + paper 3개 파일 정합 필요 -2. 기존 테스트 중 `cancel_order` 호출하는 코드 — venue 파라미터 없이 호출해도 기본값 KRX로 동작해야 함 -3. `get_current_price` safety-sell 호출 경로: 기본값 "J" 유지로 무영향 확인 필요 - -## Estimation - -- 구현: T-1~T-7 → 약 1시간 -- 테스트: T-8~T-9 → 약 30분 -- 문서: T-10 → 약 20분 diff --git a/docs/batches/bat-056_20260706_kis-broker-venue/verification.md b/docs/batches/bat-056_20260706_kis-broker-venue/verification.md deleted file mode 100644 index 606b971..0000000 --- a/docs/batches/bat-056_20260706_kis-broker-venue/verification.md +++ /dev/null @@ -1,47 +0,0 @@ -# BAT-056 Verification - -- Status: Verified (draft-batch-verification) -- Date: 2026-07-06 -- Commit: 1d82ca0 - -## 테스트 결과 - -``` -794 passed, 47 skipped in 2.71s -``` - -기존 스위트: 777 passed, 47 skipped -신규 추가: 17개 (test_venue.py) -결과: 794 passed (+17), 47 skipped (불변) — 기존 회귀 없음 - -## AC 매핑 - -| AC | REQ | 테스트 | 결과 | -| --- | --- | --- | --- | -| REQ-117 AC-1 | venue 기본값 KRX | test_order_request_venue_default_krx | PASS | -| REQ-117 AC-2 | venue NXT 지정 | test_order_request_venue_nxt | PASS | -| REQ-117 AC-3 | 기존 회귀 없음 | 전체 pytest 794+47 | PASS | -| REQ-117 AC-4 | 위치 인수 순서 불변 | test_order_request_positional_args_unchanged, test_order_request_full_positional | PASS | -| REQ-117 AC-5 | paper venue 무영향 | test_paper_submit_nxt_accepted, test_paper_cancel_venue_ignored | PASS | -| REQ-118 AC-1 | submit KRX EXCG 포함 | test_submit_order_krx_includes_excg | PASS | -| REQ-118 AC-2 | submit NXT EXCG 포함 | test_submit_order_nxt_includes_excg | PASS | -| REQ-118 AC-3 | cancel NXT EXCG 포함 | test_cancel_order_nxt_includes_excg | PASS | -| REQ-118 AC-4 | poll_fill TR 미확인 | open item (코드 주석 + design.md 기록) | OPEN | -| REQ-118 AC-5 | get_price 기본 J | test_get_current_price_default_market_j | PASS | -| REQ-118 AC-6 | get_price UN | test_get_current_price_market_un | PASS | -| REQ-118 AC-7 | 기존 회귀 + EXCG 추가만 다름 fixture | test_submit_order_krx_only_adds_excg_field | PASS | -| REQ-118 AC-8 | paper cancel 시그니처 정합 | test_paper_cancel_venue_ignored, test_paper_cancel_default_venue, test_paper_cancel_backward_compat_no_venue | PASS | - -## Open Items - -### OI-1: REQ-118 AC-4 — poll_fill NXT 체결 포함 여부 미확인 - -- 현황: 웹 접근 불가 환경 — KIS 개발자 센터 TTTC8001R NXT 체결 포함 여부 문서 확인 불가. -- 보수 구현: 기존 TR(TTTC8001R/VTTC8001R) 유지. 코드 주석에 open item 명시. -- 향후: NXT 체결 주문 발생 후 실측 확인 → 미포함 시 TTTC0081R 전환 별도 batch. -- 운영 스모크 필요: NXT 주문 체결 후 poll_fill 응답에 해당 주문 포함 여부 확인. - -## 미확인 항목 정직 기록 - -1. poll_fill TTTC8001R NXT 체결 포함 여부 — 미확인 (OI-1, 운영 스모크 필요) -2. VTS(모의투자)에서 NXT EXCG_ID_DVSN_CD 동작 — 미확인 (REQ-118 Notes 참조). 단위 테스트는 HTTP 목킹으로 검증 완료. diff --git a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/design.md b/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/design.md deleted file mode 100644 index a5b0432..0000000 --- a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/design.md +++ /dev/null @@ -1,327 +0,0 @@ -# BAT-057 Design: operator broker truth 및 managed divergence emergency hotfix - -- Status: confirmed -- Batch: BAT-057 -- Design scope: REQ-119, REQ-120, REQ-121, REQ-122 -- Implementation authorization: 본 문서는 구현 설계만 정의한다. `sync --live`, production target 선택, credential/secret 접근 및 서비스 실행은 범위 밖이다. - -## Architecture Summary - -### 1. 설계 목표와 현재 결함 - -현재 daemon은 `runtime.daemon._build_broker(config)`에서 env/TOML `KISConfig`로 broker를 먼저 만들고, `SnapshotScheduler._maybe_fetch_credential()` 성공 후에만 동일 객체의 `reinitialize()`를 호출한다. 반면 CLI `sync`/`verify`도 `_build_broker(cfg)`를 직접 호출하므로 fetched mode에서도 operator read가 env/TOML source로 시작한다. 또한 `KISBroker.get_balance()`은 token/HTTP/JSON/KIS `rt_cd` 오류를 `[]`로 변환하여 실제 empty balance와 구별하지 못한다. 결과적으로 operator가 잘못된 source 또는 실패를 `broker_qty_zero`로 해석하고 write/skip/성공 판단으로 이어갈 수 있다. - -본 설계는 다음을 고정한다. - -1. daemon과 모든 operator broker-read (`sync`, `verify`, 이후 같은 provider를 쓰는 read path)가 하나의 credential-resolution 및 broker-construction 경계를 사용한다. -2. fetched mode의 credential fetch 또는 balance read 실패는 명시적 break-glass가 없는 한 **broker가 없는 실패**로 종료한다. env/config으로 조용히 fallback하거나 empty holdings로 바꾸지 않는다. -3. `get_balance()` 성공 empty와 실패를 typed/sanitized result로 분리하고, 실패 결과가 reconciliation, `sync`, `verify`의 write/clear/skip 판단에 도달하지 못하게 한다. -4. unmatched broker holding만 instance-scoped managed-slot 존재 여부를 조회하여 true unmanaged와 managed non-holding divergence를 additive하게 분류한다. - -## Changed Areas - -### 2. 모듈별 변경과 소유권 - -| 모듈 | 변경/소유권 | 구현 경계 | -| --- | --- | --- | -| `trex_core/runtime/broker_provider.py` | `BrokerProvider` protocol, source metadata, credential resolution, broker reuse/invalidation policy의 runtime composition ownership | portfolio-hub read port와 KIS/paper construction을 조립하되 CLI 출력/DB write를 소유하지 않는다. 현재 구현의 `ConfigBrokerProvider`가 daemon과 CLI의 공통 경계다. | -| `trex_core/runtime/daemon.py` | `DaemonConfig` → provider assembly 및 process-lifetime provider 주입 | runtime composition만 담당하며 credential 값을 로그/JSON으로 만들지 않는다. legacy `_build_broker`는 env-only compatibility helper로 남아 있으므로 새 broker-read 경로에서 직접 사용하지 않는다. | -| `trex_core/application/snapshot_scheduler.py` | day-start fetched credential 성공/실패 및 broker reload를 provider contract로 호출 | scheduler의 entry gate 역할은 유지하되, fetched failure 뒤 env credential으로 trading/broker-read를 계속하는 현재 fallback은 제거한다. | -| `trex_core/runtime/cli.py` | `--config`가 있으면 daemon과 동일한 `DaemonConfig.from_config(toml_path, env_file=...)`로 instance TOML을 로드하고, `_cmd_sync`/`_cmd_verify`가 그 config의 같은 provider를 호출 | broker read가 성공하기 전 slot/warning write, `broker_qty_zero`, verify success를 만들지 않으며 CLI가 TOML `credential_fetch_enabled`를 env-only default로 덮어쓰지 않는다. | -| `trex_core/adapters/broker/kis.py` | typed sanitized `BrokerBalanceError`, strict `get_balance()`, token/error mapping | credential/token/header/raw account/payload를 error message, log, CLI JSON에 싣지 않는다. | -| `trex_core/application/reconciliation.py` | balance failure 전파와 managed non-holding decision 분기 | REQ-121 분류 자체로 slot/warning persistence를 쓰거나 상태를 바꾸지 않는다. 기존 A/B/C behavior는 strict balance 성공 뒤에만 유지한다. | -| `trex_core/domain/slot/repository.py` | managed-slot representative lookup protocol 추가 | query는 `instance_key`와 symbol만 입력으로 받고 status/retired 여부를 임의로 제외하지 않으며, safe triage context를 위한 deterministic `Slot` 한 건 또는 `None`을 반환한다. | -| `trex_core/domain/slot/in_memory_repository.py`, `trex_core/adapters/persistence/postgresql.py` | 같은 managed-slot lookup 구현 | PostgreSQL은 instance predicate를 반드시 SQL에 넣고, InMemory와 동일한 representative-slot 의미를 제공한다. | -| `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, 필요 시 `docs/project-structure.md` | REQ-122 baseline 갱신 대상 | REQ-119~121 implementation/QC evidence가 확정되기 전에는 갱신하지 않는다. | - -## Key Decisions - -### 3. 공통 provider 계약 (REQ-119) - -### D1. source mode와 안전한 식별자 - -신규 application contract는 다음과 같이 제안한다. 실제 파일 위치는 implementation에서 import-cycle 검증 후 위 표의 신규 module 중 하나로 고정한다. - -```python -@dataclass(frozen=True) -class BrokerSource: - mode: Literal["env", "fetched", "break_glass"] - identity_label: str # stable, non-secret, masked/fingerprinted only - -class BrokerProvider(Protocol): - def get_broker_for_read(self) -> tuple[BrokerPort, BrokerSource]: - """Return a broker whose credential source was resolved successfully. - - Raise BrokerCredentialUnavailable on fetched resolution failure; never - return an env broker as an implicit replacement for fetched mode. - """ - ... - - def refresh_day_start_credential(self) -> BrokerSource: - """Resolve/reload the day-start source for daemon ownership. - - Raise BrokerCredentialUnavailable on a required fetched-source failure. - """ - ... -``` - -`BrokerSource.identity_label`은 credential version과 one-way masked fingerprint 등 비밀이 아닌 stable value만 허용한다. raw `account_ref`, app key, app secret, access token, Authorization header, raw credential JSON은 provider return value, exception, log, CLI JSON, fixture에 넣지 않는다. label 산출은 단일 helper로 소유하고 fixture의 고정 synthetic label도 같은 형식을 사용한다. - -### D2. source resolution 순서와 failure - -`credential_fetch_enabled`는 KIS credential source 정책이며 Paper broker의 의미를 바꾸지 않는다. provider는 먼저 `config.broker`를 분기한다. - -- `config.broker != "kis"`이면 기존 `PaperBroker`를 구성해 `mode="env"`의 safe source로 반환한다. flag가 `True`여도 portfolio-hub credential endpoint를 호출하거나 KIS로 재구성하지 않으며, 이 조합은 existing backward-compatible configuration으로 유지한다. -- `config.broker == "kis" and credential_fetch_enabled=False`이면 provider는 기존 env/TOML KIS construction을 사용하고 portfolio-hub credential endpoint를 호출하지 않는다. 이는 backward-compatible mode이다. -- `config.broker == "kis" and credential_fetch_enabled=True`이면 다음 순서만 허용한다. - -1. 명시적 `TREX_KIS_BROKER_BREAK_GLASS`가 활성화되면 env/TOML source를 선택하고 `mode="break_glass"`로 표시한다. fetch를 시도하지 않으며 activation fact만 secret-safe하게 관찰 가능해야 한다. -2. break-glass가 아니면 portfolio-hub `fetch_broker_credential()`을 호출해 `TradingContext.broker`와 `trading_enabled`를 검증한다. -3. fetch unavailable, malformed/absent credential, `trading_enabled=False`, provider/KIS 재구성 실패는 `BrokerCredentialUnavailable(code=...)`으로 종료한다. env broker를 만들거나 기존 env credential을 계속 사용하지 않는다. -4. 성공한 fetched payload로 KIS broker를 최초 생성하거나 기존 process broker를 `reinitialize()`한다. return source는 `mode="fetched"`이다. - -따라서 daemon과 CLI는 source mode가 아니라 provider API를 호출한다. daemon의 day-start scheduler는 `refresh_day_start_credential()`을 호출하고, CLI는 command마다 `get_broker_for_read()`을 호출한다. 같은 invocation에서 `sync`가 여러 slot을 순회하거나 `verify`가 여러 warning을 순회할 때는 provider 반환 broker 하나를 재사용한다. - -### D3. lifecycle/reuse 및 retry 결정 - -- **Ownership:** daemon은 하나의 process-lifetime provider와 그 provider가 소유하는 broker 하나를 갖는다. CLI는 command invocation당 provider 하나와 broker 하나를 갖는다. caller는 broker를 재생성하지 않는다. -- **Reuse scope:** KISBroker의 기존 L1 token과 existing token-cache L2(설정된 경우)를 그대로 reuse한다. credential version/source fingerprint 변경 또는 명시적 fetched refresh 성공 시에만 `reinitialize()`로 L1을 invalidate하고 새 `(account_ref, app_key)` cache key를 사용한다. -- **근거:** 현 코드의 `KISBroker._ensure_token()`은 L1→L2→issuance 경로와 credential-keyed L2를 이미 제공한다. BAT-057은 process-local cache나 새 shared cache를 선결정하지 않고, provider가 fresh broker/object를 반복 만드는 우회 경로를 제거한다. -- **Retry:** token issuance/auth authorization/KIS business failure/malformed response는 즉시 sanitized failure이며 동일 read 안에서 retry하지 않는다. transport timeout/connection failure의 retry 횟수·backoff는 existing HTTP client retry 정책이 없으므로 implementation에서 새 implicit retry를 넣지 않는다(0회). 상위 command가 재시도하려면 별도 invocation/explicit operator action이어야 한다. credential fetch도 이 batch에서는 one attempt fail-loud이다. -- **Invalidation:** source credential 성공 refresh만 credential/token invalidation trigger이다. token cache backend miss/error는 token issuance를 시도할 수 있으나, issuance 실패를 empty balance로 변환하지 않는다. - -## Edge Cases - -### 4. KIS balance 실패 계약과 no-write barrier (REQ-120) - -### D4. typed sanitized failure - -`KISBroker.get_balance()`의 반환 계약을 성공 list와 실패가 구별되게 변경한다. 권장 signature는 breaking change를 명시적으로 수행한다. - -```python -class BrokerBalanceError(RuntimeError): - def __init__(self, code: Literal[ - "token_issuance_failed", "authentication_failed", "authorization_failed", - "transport_failed", "malformed_response", "broker_rejected" - ]) -> None: ... - -def get_balance(self) -> list[BalanceItem]: - """Return holdings only after a validated successful KIS balance response. - Raise BrokerBalanceError(code) for any token/auth/transport/shape/KIS failure. - Return [] only for a validated successful response with no positive holdings. - """ -``` - -`BrokerPort`의 protocol/documentation 및 모든 fake broker를 이 exception contract에 맞춘다. `httpx.HTTPStatusError`와 token JSON/key errors는 `token_issuance_failed`; balance HTTP 401/403은 authentication/authorization; other network/timeout은 `transport_failed`; JSON, `rt_cd` shape, `output1` type/row shape 오류는 `malformed_response` 또는 `broker_rejected`로 map한다. code는 stable allow-list이며 upstream response text, URL query, header, body를 포함하지 않는다. - -### D5. propagation and barrier - -`reconcile_core_holdings`, `reconcile_positions`, `sync_slot_to_broker`, `verify_slot_reconcile`은 `broker.get_balance()`를 호출하기 전에 write를 하지 않으며 `BrokerBalanceError`를 swallowing하지 않는다. CLI command boundary만 이를 catch하여 `{ "error": {"code": ..., "message": "broker balance unavailable"} }` 형태(기존 `--json` 형식에 맞춰 조정 가능) 또는 stderr를 내고 exit code `1`을 반환한다. error object에는 source `mode`와 safe label은 포함할 수 있으나 secret은 포함하지 않는다. - -이 barrier로 다음은 failure path에서 금지된다. - -- `sync_slot_to_broker()`의 `broker_qty_zero`/`skip_reason` 생성, slot transition/update, `clear_warned()` 호출 -- `verify_slot_reconcile()`의 `cleared`, `not_warned`를 제외한 broker-derived success 판단 및 `clear_warned()` 호출 -- reconciliation의 false-flat A-case error quarantine, B/C warning write/clear, empty-holdings/unmanaged classification -- CLI의 verify success 또는 dry-run broker quantity output - -실제 successful empty `[]`만 기존 A-case/broker-zero/normal empty semantics에 도달한다. 이 구분은 `sync --dry-run`에도 동일하며 dry-run이라도 실패를 zero preview로 보여주지 않는다. - -## 5. managed non-holding divergence (REQ-121) - -### D6. repository parity contract - -`SlotRepository`에 다음 read-only query를 추가한다. REQ-121 AC-2의 triage context가 필요하므로 boolean existence API가 아니라 representative slot을 반환한다. - -```python -def find_managed_slot_by_symbol(self, instance_key: str, symbol: str) -> Optional[Slot]: - """Return one deterministic managed slot for exact instance+symbol, or None. - - Caller invokes only for unmatched broker holdings. The result is read-only; - it must not imply that the slot is holding, eligible, or remediated. - """ - ... -``` - -동일 instance에 historical duplicate symbol rows가 가능하므로 deterministic parity를 위해 PostgreSQL은 `ORDER BY updated_at DESC NULLS LAST, slot_id ASC LIMIT 1`로 고정하고, InMemory도 `updated_at is None`을 가장 오래된 값으로 취급한 동일 key `(updated_at DESC NULLS LAST, slot_id ASC)`를 적용한다. PostgreSQL query는 `WHERE instance_key = %s AND symbol = %s`만 사용하여 holding/inactive/error/pending/retired rows를 포함하며, InMemory도 same exact predicate를 전수 비교한다. `symbol is None` 또는 empty broker symbol은 caller input validation issue이며, query는 read-only라 schema/migration은 필요 없다. index 추가는 실제 production query-plan evidence가 나오기 전 본 hotfix에서 요구하지 않는다. 모든 candidate를 triage 해야 하는 requirement가 아니므로 one representative slot만 structured context에 사용한다. PostgreSQL과 InMemory가 같은 fixture에서 같은 slot identity를 반환하도록 test한다. - -### D7. reconciliation 분기와 decision code - -`reconcile_core_holdings()`의 holding-slot loop와 기존 A/B quantity handling은 그대로 유지한다. broker holdings loop에서 `bq > 0 and sym not in matched`일 때만 D6 query를 실행한다. - -```python -managed = slot_repo.find_managed_slot_by_symbol(instance_key, sym) -if managed is not None: - decisions[f"broker:{sym}"] = "managed_non_holding_divergence" - _log.warning( - "managed non-holding divergence: slot_id=%s portfolio_id=%s " - "symbol=%s status=%s instance_key=%s", - managed.slot_id, managed.portfolio_id, sym, managed.status.value, instance_key, - ) -else: - decisions[f"broker:{sym}"] = "unmanaged_position" - _log.warning("unmanaged position: symbol=%s qty=%d instance_key=%s", sym, bq, instance_key) -``` - -최종 분기는 다음 4-case matrix를 유지한다. - -| Input | Existing holding path | Expected decision | Write effect | -| --- | --- | --- | --- | -| broker holding, matching holding slot and same qty | match | `ok` | 없음 | -| broker holding, matching holding slot and qty mismatch | existing B case | `qty_warning` | 기존 warning write만 가능 | -| broker holding, target instance에 어떤 managed slot도 없음 | unmatched | `unmanaged_position` | 없음(기존 safe warning/log만) | -| broker holding, target instance에 `inactive`/`error` 등 non-holding managed slot 존재 | unmatched + managed query hit | `managed_non_holding_divergence` | 없음 | - -`managed_non_holding_divergence`는 new additive telemetry/structured warning code이다. slot state transition, warning 자동 해제/생성, auto trade/liquidation, unmanaged auto-slot creation, `sync` invocation을 하지 않는다. convergence는 release-approved existing `sync` path에만 남긴다. 다른 instance에 같은 symbol/portfolio slot이 있어도 query miss여야 한다. - -## 6. CLI와 daemon wiring - -1. CLI `main()`은 `--config`가 있으면 `resolve_config_toml()` 뒤 `DaemonConfig.from_config(toml_path, env_file=args.env_file)`를 호출하고, config가 없을 때만 `DaemonConfig.from_env()`를 쓴다. 따라서 `sync`/`verify`는 daemon과 같은 instance key, portfolio-hub credential setting 및 `credential_fetch_enabled`를 받는다. -2. `daemon.Daemon.__init__`은 provider를 한 번 assemble하고 `broker, _source = provider.get_broker_for_read()`로 얻은 broker를 `self.broker`에 보관한다. fetched mode startup에서 fetch가 불가능하면 daemon은 explicit safe degraded/entry-blocked state로 남고 env broker로 진행하지 않는다. 기존 scheduler의 `entry_allowed` alarm model은 유지하되 provider failure code를 secret-safe reason으로 전달한다. -3. `SnapshotScheduler`에는 raw `kis_reinit_callback` 대신 `credential_refresh: Callable[[], BrokerSource]` 또는 provider 자체를 dependency로 주입한다. fetched refresh success는 same long-lived broker reinitialize; failure는 `_entry_allowed=False` 및 safe alarm이며 silent env continuation이 아니다. Paper provider는 refresh에서 no-op safe source를 반환하며 fetch/reinitialize를 시도하지 않는다. -4. `_cmd_sync`와 `_cmd_verify`는 `build_broker_provider(cfg).get_broker_for_read()`를 command 초기에 한 번 호출한다. provider acquisition failure면 DB slot target enumeration, cost-basis fetch, warning mutation 이전에 return `1`한다. -5. `sync`의 live double-ack remains before any live write. BAT-057은 flag syntax, CLI command name, wire API를 추가/변경하지 않는다. `verify`는 read-centric라는 기존 boundary를 유지하되 failed read를 success/degrade로 바꾸지 않는다. -6. CLI structured output must use codes/source mode/safe label only. 정확한 output field name은 implementation test와 `--help` evidence 후 REQ-122 baseline에 기록한다. - -## 7. 테스트 seam과 검증 설계 - -| Seam | Double/fixture | Required assertions | -| --- | --- | --- | -| `BrokerProvider` | fake portfolio read with fetched credential, absent credential, fetch exception, break-glass, env-only KIS, Paper | daemon-equivalent/`sync --dry-run`/`verify` source label 및 holding set parity; no implicit fallback; env-only KIS and Paper do not call fetch; break-glass is distinguishable; enabled+Paper keeps PaperBroker. | -| CLI config loading | temp instance TOML with `credential_fetch_enabled=true`, config-dir `.env`, and monkeypatched provider factory | `--config` CLI config equals daemon `from_config()` result; `sync`/`verify` take fetched path when KIS enabled, while no-config stays existing env-only path. | -| KIS adapter | monkeypatched `httpx.post/get` for token, 401/403, timeout, invalid JSON, invalid `rt_cd`/`output1`, successful `[]` | each failure raises only allow-listed `BrokerBalanceError.code`; successful empty returns `[]`; token/header/account/payload never appears in exception/log/JSON. | -| CLI command boundary | provider factory seam injected/monkeypatched; fake repos/warn store recording writes | each failure returns nonzero; no `broker_qty_zero`, slot update/transition, warning clear/write, verify cleared/success; `sync --dry-run` also fails loud. | -| lifecycle | counted KIS/token endpoint + provider call sequence + `InMemoryTokenCache` | one broker per command/daemon lifecycle, L1/L2 reuse prevents fresh issuance; source refresh invalidates/rekeys as intended; throttle/transport still returns sanitized failure. | -| reconciliation | fake broker yielding balance or raising; `InMemorySlotRepository` and recording `SlotService`/warn store | 4-case table exactly; existing match/mismatch unchanged; divergence only logs/decision, no state/warning/trade/remediation writes. | -| repository parity | same fixtures against InMemory and PostgreSQL integration DB | same found/not-found and representative-slot identity; `inactive`/`error` hits; no slot and other-instance-only miss; query performs no write. | -| redaction | caplog/capsys/CLI JSON and source fixtures | deny-list scan/assertion for app key, app secret, token, `Authorization`, raw account number and raw credential payload. | - -PostgreSQL parity test may use the repository-local test database fixture only; it must not use production DSN, live broker, or credentials. If local PostgreSQL fixture is unavailable, implementation cannot claim REQ-121 AC-5 complete and must record the environment blocker in verification. - -## 8. invariants, compatibility, non-goals, rollback - -### Invariants - -- fetched mode without explicit break-glass: fetch failure means no broker truth and no env/config fallback. -- any broker balance failure: nonzero operator outcome and no broker-derived state/warning/slot write or false-zero classification. -- actual successful empty holdings remain semantically distinct and preserve existing behavior. -- `config.broker != "kis"` (Paper 포함)은 `credential_fetch_enabled` 값과 무관하게 existing broker를 유지하고 portfolio-hub credential fetch/KIS reinitialize를 하지 않는다. -- `config.broker == "kis" and credential_fetch_enabled=False`는 env-only로, portfolio-hub credential request와 CLI syntax regression 없이 유지된다. -- CLI `--config`는 daemon과 같은 `DaemonConfig.from_config()` path를 사용하므로 instance-level fetched-mode policy를 잃지 않는다. -- managed non-holding is read-first additive triage only; all persistence mutations remain on pre-existing validated flows. -- every repository managed-slot lookup is instance-scoped; no cross-instance ownership inference. -- external portfolio-hub wire contract, DB schema, AMQP payload, slot state enum, automatic remediation and live sync authorization do not change. - -### Non-goals - -- portfolio-hub credential storage/API redesign, token cache infrastructure replacement, global retry framework, bulk/global sync, automatic slot creation, auto trade/liquidation/clear, TVR strategy changes, TVR redeploy, production read/live sync execution. - -### Rollback - -- The code change must be independently revertible as a single hotfix commit/PR. Revert restores prior construction and decision behavior; it does not mutate existing slots or warnings because the new provider/failure and divergence branches are read-first. -- If fetched provider rollout fails, operator remediation is to stop broker-read/sync, retain evidence with safe codes, and use the separately approved break-glass policy only when an operator explicitly enables it. Do not silently toggle configuration or run `sync --live` as rollback. -- Baseline documentation update is rolled back with the implementation change if implementation evidence is rejected; REQ-122 must not describe unlanded source mode/code/command behavior. - -## Architecture Impact - -- **both**: `runtime-flows`는 daemon/CLI 공통 provider resolution, KIS balance fail-loud hold 및 두 divergence decision 흐름을 반영해야 한다. `project-structure`는 provider/factory가 stable module로 landed한 경우에만 새 ownership boundary를 반영한다. -- `interface-contract.md`와 `operations.md`도 REQ-122 범위에서 갱신 대상이나, portfolio-hub/AMQP 외부 wire 계약, DB schema 및 live-sync authorization은 변경하지 않는다. -- 실제 baseline 수정은 구현과 독립 QC evidence가 확정된 뒤에만 수행한다. - -## 9. REQ acceptance traceability - -| REQ / AC | Design element | Required verification evidence | -| --- | --- | --- | -| REQ-119 AC-1 | D1-D3 common provider; §6 same assembly | mocked fetched credentials; daemon-equivalent, `sync --dry-run`, `verify` same safe source label and holding set | -| REQ-119 AC-2 | D2 failure order; D5 barrier | fetch failure/no break-glass → safe error, nonzero, no env broker/qty inference | -| REQ-119 AC-3 | D1-D2 break-glass `mode` and safe observability | explicit break-glass fixture shows distinct safe mode/activation basis | -| REQ-119 AC-4 | D2의 env-only KIS/Paper branches 및 §6 CLI config loading | env-only KIS와 enabled+Paper fetch call count zero, existing Paper regression, `--config` CLI/daemon instance-policy parity | -| REQ-119 AC-5 | D1, §7 redaction seam | logs/JSON/fixtures deny-list assertions | -| REQ-120 AC-1 | D4-D5 strict `get_balance` and CLI boundary | token/auth/transport/malformed fixtures yield distinct sanitized failure and nonzero CLI | -| REQ-120 AC-2 | D5 no-write barrier | recording repo/warn store proves no zero skip, success, clear or slot write for every failure class | -| REQ-120 AC-3 | D4 successful-empty contract | valid empty `output1` gives `[]` and preserves normal empty path | -| REQ-120 AC-4 | D3 lifecycle ownership/reuse | repeated fetched reads counted for broker/token construction; throttle error remains fail-loud | -| REQ-120 AC-5 | D3 policy | implementation/design test records ownership, reuse/invalidation and 0-retry policy without requiring cache mechanism replacement | -| REQ-120 AC-6 | D1/D4/D5 and §7 redaction | failure logs/JSON/fixtures contain only safe code/mode/label | -| REQ-121 AC-1 | D7 unmatched miss | no managed slot → `unmanaged_position`, existing safe warning semantics | -| REQ-121 AC-2 | D6 richer repository lookup + D7 | inactive/error same-instance slot → `managed_non_holding_divergence` with required safe triage context | -| REQ-121 AC-3 | D7 4-case matrix | match and qty mismatch remain `ok`/`qty_warning`, no reclassification | -| REQ-121 AC-4 | D7 invariants and recording persistence seam | divergence creates no slot/warning state write, clear, trade, liquidation or auto-slot | -| REQ-121 AC-5 | D6 repository parity | PostgreSQL/InMemory same found/miss and selected representative result | -| REQ-121 AC-6 | D6 exact instance predicate | other-instance-only matching symbol/portfolio is a miss | -| REQ-122 AC-1 | §10 runtime baseline plan | post-implementation runtime flow matches provider, strict failure, two divergence codes/no-auto-remediation | -| REQ-122 AC-2 | §10 interface baseline plan | actual CLI exit/output safe fields and repository/reconciliation contract; wire-change statement | -| REQ-122 AC-3 | §10 operations baseline plan | dry-run → cross-check → explicit approval → one target live sync → re-reconcile sequence, with no target/command authorization claim | -| REQ-122 AC-4 | §7 redaction + §10 doc secret scan | baseline examples/log/JSON samples pass deny-list review | -| REQ-122 AC-5 | §10 evidence-before-doc rule | CLI help, implemented decision codes/tests, references cross-checked; unresolved facts not documented as policy | - -## Reference Doc Update Plan (identify only; do not update in this gate) - -REQ-119~121 implementation and independent QC evidence가 확정된 뒤에만 REQ-122 delivery에서 다음을 갱신한다. - -- `docs/runtime-flows.md`: fetched/env/break-glass provider resolution, daemon/CLI parity, balance failure hold, successful-empty distinction, `unmanaged_position`/`managed_non_holding_divergence` flow와 no-auto-remediation 경계. -- `docs/interface-contract.md`: actual `sync`/`verify` broker-read failure exit semantics, safe output fields, `BrokerBalanceError`/repository lookup의 internal contract, AMQP/portfolio-hub external wire contract unchanged 여부. -- `docs/operations.md`: masked source/account reference evidence, failure hold, dry-run → broker/ledger/slot cross-check → 사장님 explicit approval → single-target `sync --live --yes` → re-reconcile release gate. 이 문서는 live target을 선택하거나 실행을 승인하지 않는다. -- `docs/project-structure.md`: provider/factory가 신규 stable module로 landed한 경우에만 dependency/ownership 경계를 반영한다. -- `docs/srs/index.md` 및 REQ-119~122 status/Change Log: merge/main and verification evidence 이후 StagePilot 절차에 따라 갱신한다. 본 design gate에서 상태를 변경하지 않는다. -- REQ-112, REQ-113, REQ-115, REQ-103, REQ-105 references: implemented source priority, day-start lifecycle, reconcile decision terminology와 충돌 여부를 evidence 기반으로 cross-check한다. - -## 11. Design-gate exit conditions - -- [ ] provider source/failure/reuse contract과 break-glass boundary가 implementation owner에게 명확하다. -- [ ] strict balance exception mapping 및 CLI no-write barrier test seam이 확정되었다. -- [ ] repository lookup의 deterministic rich return, PostgreSQL/InMemory parity, instance isolation rule이 확정되었다. -- [ ] 4-case reconciliation matrix가 existing A/B/C behavior와 additive divergence effect를 구분한다. -- [ ] REQ-119~122 AC별 test/document evidence가 §9에 연결되었다. -- [ ] live service, credentials, production DB, `sync --live`를 사용하지 않았고 implementation/baseline update authorization과 혼동하지 않았다. - -## 12. Evidence boundary and unresolved gates - -### Source evidence inspected - -The following repository evidence is the basis for this design (paths are relative to -this trex-core root): - -- `trex_core/runtime/broker_provider.py`: `ConfigBrokerProvider` owns one broker per - process/command, distinguishes `env`/`fetched`/`break_glass`, and converts fetched - credential read failures to an allow-listed `BrokerCredentialUnavailable` code. -- `trex_core/adapters/broker/kis.py` and - `trex_core/application/broker_port.py`: balance reads validate `rt_cd` and - `output1`, return `[]` only for a validated empty response, and raise the - allow-listed `BrokerBalanceError` codes for failure classes. -- `trex_core/domain/slot/repository.py`, - `trex_core/domain/slot/in_memory_repository.py`, and - `trex_core/adapters/persistence/postgresql.py`: the instance-and-symbol query is - read-only, deterministic, and implemented with the same ordering rule in both - adapters. -- `trex_core/application/reconciliation.py`: the query is called only for an - unmatched positive broker holding; the result selects the additive decision code - without a persistence mutation. -- `tests/runtime/test_broker_provider.py`, - `tests/runtime/test_cli_broker_failure.py`, and - `tests/adapters/broker/test_kis.py`: fetched fallback prevention, CLI failure - boundary, sanitized balance failure, and validated-empty fixtures exist. The - repository parity evidence remains fake-driver based, not a live PostgreSQL - engine run. - -### Unresolved blockers and explicit non-decisions - -1. **Cross-service contract gate — unresolved for implementation/release:** this - repository contains only the consumer-side `PortfolioHubReadPort`. A live - portfolio-hub contract/integration fixture proving the credential-version and - `trading_enabled` semantics is not available here. Implementation must not - change the portfolio-hub API; any required wire change stops at a lead-owned - cross-service approval gate. -2. **Security gate — unresolved for release:** source-level sanitization and - deny-list tests are present, but production log sink, tracing, and portfolio-hub - observability configuration are outside this repository and have not been - independently reviewed. No credential or raw account evidence may be collected - until that review is complete. -3. **Migration gate — currently no schema migration is designed:** the lookup is - read-only over existing `daily_slot` columns and does not authorize an index or - schema change. If production query-plan evidence later requires an index, that - is a separate migration-approved batch, not an implementation assumption here. -4. **Scope/approval gate — explicit hold:** baseline document edits, production - configuration changes, deployment, and `sync --live` remain outside this design - gate. The next authorized step is `run-batch-implementation`; it must not be - started from this task without the lead's gate transition. diff --git a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/implementation.md b/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/implementation.md deleted file mode 100644 index 60050b1..0000000 --- a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/implementation.md +++ /dev/null @@ -1,20 +0,0 @@ -# BAT-057 Implementation - -- Status: draft -- Batch: BAT-057 - -## Plan Summary -- Implementation stage에서 REQ-119~121의 broker source parity, KIS fail-loud/no-write 및 managed non-holding divergence 분류를 feature worktree/PR flow로 구현하고, REQ-122는 검증 evidence 이후 문서화한다. - -## Changed Files -- To be determined at design gate; no implementation authorized in this planning handoff. - -## Execution Log -1. Not started. Batch planning only. - -## Validation -- Not started. Independent QC must re-run the implementation evidence before verification confirmation. - -## Remaining Risks -- Design gate 미완료 상태에서 provider/lifecycle/security 계약을 추정하지 않는다. -- `sync --live` 및 production command는 이 batch 단계에서 실행하지 않는다. diff --git a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/index.md b/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/index.md deleted file mode 100644 index fd21a76..0000000 --- a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/index.md +++ /dev/null @@ -1,34 +0,0 @@ -# BAT-057: operator broker truth 및 managed divergence emergency hotfix - -- Status: release-candidate (`confirm-req-implemented`, lead, 2026-07-15; PR #42 merged as `94e1489`) -- Profile: standard -- Source Discovery: dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix -- Included REQ: REQ-119, REQ-120, REQ-121, REQ-122 -- Excluded REQ: 없음 - -## Summary -- dcy-037에서 승인한 #39/#40/#41을 하나의 emergency delivery 단위로 묶어 fetched credential broker truth 정합, auth/token 실패 fail-loud, managed non-holding divergence 분류 및 baseline 문서 정합을 함께 계획한다. -- `sync --live` 실행과 production target 선택은 이 batch에서 승인하거나 수행하지 않고 별도 release approval gate로 유지한다. - -## Scope -- In Scope: - - REQ-119: daemon/operator broker-read 공통 fetched credential provider와 no-silent-fallback/secret-safe 관찰성 - - REQ-120: KIS balance auth/token/transport/malformed failure와 실제 empty balance의 분리, no-write 및 lifecycle 설계 제약 - - REQ-121: instance-scoped managed-slot existence query와 `unmanaged_position`/`managed_non_holding_divergence` additive 분류 - - REQ-122: 구현·검증 evidence 이후 runtime/interface/operations baseline 정합 및 remediation release gate 문서화 -- Out of Scope: - - portfolio-hub credential API·저장 모델 재설계 - - `sync --live` 자동 실행, 전역/bulk sync, unmanaged position 자동 slot 생성 또는 자동 remediation/trading - - TVR 전략 로직 변경 및 TVR 재배포 - -## Documents -- Planning: [planning.md](./planning.md) -- Design: [design.md](./design.md) -- Implementation: [implementation.md](./implementation.md) -- Verification: [verification.md](./verification.md) - -## Notes -- Cross-service contract, security, migration 또는 scope trade-off가 발견되면 구현을 진행하지 않고 lead gate로 중단한다. -- 모든 evidence는 credential, token, app key/secret, raw account number/payload를 포함하지 않는 masked label/fingerprint만 사용한다. -- `confirm-batch-verification` 완료: PR #42 independent QC가 PASS-WITH-NOTES (acceptance blocker 0)로 판정했다. fake-driver repository parity는 REQ-121 AC evidence로 수용되며, 실제 PostgreSQL engine integration은 환경 커버리지 gap으로 추적한다. -- PR #42는 `main`에 merge됐고 REQ-119~122의 `confirm-req-implemented`를 기록했다. `sync --live`, release 또는 production remediation은 이 batch의 별도 release approval gate 밖에서만 수행한다. diff --git a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/planning.md b/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/planning.md deleted file mode 100644 index fba8ce7..0000000 --- a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/planning.md +++ /dev/null @@ -1,45 +0,0 @@ -# BAT-057 Planning - -- Status: confirmed -- Batch: BAT-057 -- Profile: standard - -## Included REQ -- REQ-119 (Approved): fetched credential 공통 broker provider와 operator CLI truth 정합 -- REQ-120 (Approved): KIS balance 인증 실패 fail-loud 및 broker/token lifecycle 정책 -- REQ-121 (Approved): managed non-holding divergence 분류와 slot 존재 조회 계약 -- REQ-122 (Approved): operator broker truth HOTFIX baseline 및 remediation 문서 정합 - -## Delivery Plan -1. 공통 broker provider/factory와 daemon·`sync`·`verify`의 fetched credential source parity 경계를 설계한다. fetched mode fetch 실패 시 silent env/config fallback을 금지하고, env-only mode 회귀를 보존한다. -2. KIS balance failure class를 actual empty balance와 분리하고, operator broker-read의 non-zero/no-write semantics 및 token/broker lifecycle ownership·reuse·retry 정책을 evidence 기반으로 확정한다. -3. reconciliation의 unmatched broker symbol에 한해 instance-scoped managed-slot existence query를 추가하고, 기존 holding match/mismatch와 true unmanaged semantics를 보존하면서 additive divergence code를 도입한다. -4. REQ-119~121 구현 및 independent QC evidence가 준비된 후 REQ-122의 runtime/interface/operations baseline을 실제 계약과 대조해 갱신한다. -5. unit/integration/CLI regression과 secret-redaction 검증을 구현 lane 및 독립 QC lane에서 각각 수행한다. production command 또는 `sync --live`는 실행하지 않는다. - -## Dependencies -- Internal: - - 기존 REQ-112 credential read port, REQ-113 source priority/break-glass, REQ-115 day-start credential resilience - - `trex_core/runtime/daemon.py`, `trex_core/runtime/cli.py`, `trex_core/adapters/broker/kis.py`, `trex_core/application/reconciliation.py` - - SlotRepository protocol 및 PostgreSQL/InMemory 구현 - - 기존 sync/verify gate와 broker/ledger/slot read semantics -- External: - - portfolio-hub fetched credential authority는 기존 계약을 사용한다. 계약 변경이 필요하면 본 batch 범위를 중단하고 cross-service gate로 전환한다. - -## Design Gate -- Design Required Before Implementation: yes -- Reason: 공통 provider 경계, KIS token/broker lifecycle ownership·reuse·retry 정책, failure type/no-write semantics, repository query 계약 및 interface/runtime 영향이 구현 전에 확정되어야 한다. 특히 REQ-120 OQ-2는 process-local/shared cache/other provider 중 방식을 근거 없이 선결정할 수 없다. - -## Milestones -1. Batch 문서 및 planning 확정: 승인된 REQ-119~122와 dcy-037 scope traceability를 고정한다. -2. Design gate: provider/lifecycle/failure/reconciliation 계약과 cross-service·security 영향 확인. -3. Feature worktree implementation: REQ-119~121 코드·회귀 테스트 구현, REQ-122는 evidence 이후 문서 반영. -4. Independent QC: fetched source parity, fail-loud/no-write, true-empty 분리, 4-case divergence matrix, repository parity, instance isolation, secret scan을 재실행. -5. Release gate: 배포 전 dry-run 및 broker/ledger/slot cross-check 후 lead/사장님 명시 승인 없이는 `sync --live`를 실행하지 않는다. - -## Risks -- R1 High: fetched credential source/account identity mismatch가 다른 계좌 truth로 이어질 수 있음. masked source identity parity와 dry-run evidence로 완화한다. -- R2 High: auth/token/transport failure가 false zero로 변환되면 잘못된 state convergence가 발생함. typed/sanitized failure와 no-write assertion을 고정한다. -- R3 Medium: managed-slot existence query가 기존 holding mismatch/unmanaged semantics를 바꿀 수 있음. unmatched symbol에만 적용하고 4-case matrix를 유지한다. -- R4 Medium: 반복 fresh broker/token 생성이 throttle을 유발할 수 있음. lifecycle reuse 범위와 failure-class별 retry/no-retry를 design evidence로 결정한다. -- R5 High: hotfix 후 승인 없는 live sync가 외부 상태를 변경할 수 있음. `sync --live`를 별도 release approval gate로 유지하고 이번 batch에서 실행하지 않는다. diff --git a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/verification.md b/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/verification.md deleted file mode 100644 index 8e75e0d..0000000 --- a/docs/batches/bat-057_20260714_operator-broker-truth-hotfix/verification.md +++ /dev/null @@ -1,40 +0,0 @@ -# BAT-057 Verification - -- Status: confirmed (`confirm-batch-verification`, lead, 2026-07-15) -- Batch: BAT-057 -- Delivery ref: PR #42 — `feat/dcy-037-operator-broker-truth-hotfix` (committed PR head at evidence drafting) -- Scope of this artifact: implementation and independent-QC evidence. `confirm-batch-verification` is recorded above; it does not mark any REQ Implemented or make a Release Ready claim. - -## Evidence Boundary - -- No live broker, PostgreSQL database, deployment, production target, credential, or `sync --live` execution was performed. -- The focused suite used an in-process `pika` stub. Its latest recorded result, including the REQ-121 fake-driver parity regression, is **69 passed, 1 skipped, 1 deselected**. -- `compileall`, changed-file `ruff`, `git diff --check`, and the documentation secret scan passed. -- The subprocess case `test_config_required_cli_exits_nonzero` remains blocked solely because `pika` is absent in the isolated worktree subprocess. The subprocess cannot inherit the in-process stub. This is an environment/dependency-setup blocker, not a recorded behavioral failure. - -## Acceptance Mapping - -| REQ / acceptance coverage | Draft implementation evidence | Remaining verification boundary | -| --- | --- | --- | -| REQ-119 — fetched provider parity, no silent fallback, break-glass/env-only compatibility, safe source identity | Focused provider/CLI regression coverage passed under the in-process `pika` stub; changed-file lint/diff and secret scan passed. | Independent QC must rerun the PR #42 evidence and assess the blocked subprocess case in an environment with `pika` available. No live broker evidence exists. | -| REQ-120 — fail-loud balance failures, nonzero/no-write barrier, true-empty separation, lifecycle/redaction | Focused adapter and CLI failure-path regression coverage passed under the stub; secret scan passed. | Independent QC must rerun failure/no-write and true-empty evidence. The unavailable subprocess prevents a complete isolated CLI-process confirmation. | -| REQ-121 — four-case divergence classification, instance isolation, no auto-remediation, repository parity | Focused reconciliation/InMemory regression coverage plus a fake-driver parity test passed: it executes the PostgreSQL adapter's managed-slot SQL/params and row mapping against the same fixture. | **Environmental coverage gap:** no live/local PostgreSQL fixture was executed. The fake-driver unit contract establishes adapter-query parity, not real database integration behavior. Independent QC must retain this integration gap or obtain an approved non-production PostgreSQL fixture. No live broker or database was used. | -| REQ-122 — runtime/interface/operations alignment, safe examples, release-gated remediation | Baseline documentation changes at the PR #42 tip were included in changed-file diff checks; documentation secret scan passed. | Independent QC must cross-check the rendered baseline text, actual CLI/help and decision-code behavior against the committed PR diff. This artifact does not authorize a production command or deployment. | - -## Draft Validation Results - -| Check | Result | Notes | -| --- | --- | --- | -| Focused suite | PASS — 69 passed, 1 skipped, 1 deselected | Includes the REQ-121 fake-driver parity regression; in-process `pika` stub used. | -| `compileall` | PASS | Recorded implementation-lane validation. | -| Changed-file `ruff` | PASS | Recorded implementation-lane validation. | -| `git diff --check` | PASS | Recorded implementation-lane validation. | -| Documentation secret scan | PASS | No credentials, tokens, raw account numbers, or raw credential payloads in scanned documentation. | -| `test_config_required_cli_exits_nonzero` subprocess | BLOCKED (environment) | Isolated worktree lacks `pika`; subprocess cannot inherit the in-process stub. | -| PostgreSQL repository parity/live database coverage | FAKE-DRIVER PARITY PASS; live database NOT EXECUTED | The injected fake psycopg driver executed the actual adapter query/params and row mapping against the InMemory fixture. No local/live PostgreSQL integration was run, so database-engine behavior remains an environmental coverage gap. | - -## Confirmation and Next Gate - -- Independent QC reran the exact PR #42 head and returned **PASS-WITH-NOTES** (acceptance blockers: 0). The note records that the fake-driver repository parity test is accepted AC evidence, while real PostgreSQL engine integration remains an explicitly documented environmental coverage gap. -- PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`; `confirm-req-implemented` is recorded for **REQ-119~122**. -- `sync --live`, live broker/database access, deployment, and production remediation remain outside this batch verification and require their separate release approval gate. diff --git a/docs/batches/index.md b/docs/batches/index.md deleted file mode 100644 index b0508aa..0000000 --- a/docs/batches/index.md +++ /dev/null @@ -1,69 +0,0 @@ -# Batch Index - -## 목적 -- 이 문서는 batch delivery 단위의 전역 인덱스다. -- 각 batch는 `docs/batches//` 아래에서 profile(`standard` | `batch-lite`)에 맞는 산출물을 관리한다. - -## Batch Register - -| BAT ID | Profile | Status | Included REQ | Discovery | Folder | -| --- | --- | --- | --- | --- | --- | -| BAT-001 | standard | released | REQ-001, REQ-002, REQ-003, REQ-004 | dcy-001 | [bat-001](./bat-001_20260603_trex-core-slot-domain-seed/) | -| BAT-002 | standard | released | REQ-005, REQ-006, REQ-007, REQ-008, REQ-009 | dcy-002 | [bat-002](./bat-002_20260603_slot-core-daemon-http-api/) | -| BAT-003 | batch-lite | released | REQ-010 | dcy-002 | [bat-003](./bat-003_20260604_daemon-dotenv-config/) | -| BAT-004 | standard | released | REQ-011, REQ-012, REQ-013, REQ-014, REQ-015 | dcy-003 | [bat-004](./bat-004_20260604_broker-execution-and-outbox/) | -| BAT-005 | batch-lite | released | REQ-016 | dcy-003 | [bat-005](./bat-005_20260604_kis-token-redis-cache/) | -| BAT-006 | standard | released | REQ-017, REQ-018, REQ-019, REQ-020 | dcy-004 | [bat-006](./bat-006_20260604_portfolio-hub-ledger-integration/) | -| BAT-007 | standard | released | REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026 | dcy-006 | [bat-007](./bat-007_20260605_strategy-module-rabbitmq-transport/) | -| BAT-008 | standard | released | REQ-027, REQ-028, REQ-029, REQ-030, REQ-031, REQ-032, REQ-033 | dcy-005 | [bat-008](./bat-008_20260606_portfolio-hub-snapshot-read-layer/) | -| BAT-009 | batch-lite | released | REQ-010 (CHG-20260606-01 / AC-5) | dcy-002 | [bat-009](./bat-009_20260606_paper-broker-env-tuning/) | -| BAT-010 | standard | released | REQ-034, REQ-035, REQ-036, REQ-041 | dcy-007 | [bat-010](./bat-010_20260607_safety-sell-foundation/) | -| BAT-011 | standard | released | REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 | dcy-007 | [bat-011](./bat-011_20260607_safety-sell-triggers/) | -| BAT-012 | standard | released | REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 | dcy-008 | [bat-012](./bat-012_20260607_kill-switch-control/) | -| BAT-013 | standard | released | REQ-048, REQ-049, REQ-050, REQ-051 | dcy-009 | [bat-013](./bat-013_20260607_multi-instance-topology/) | -| BAT-014 | batch-lite | released | REQ-052, REQ-053, REQ-054 | dcy-010 | [bat-014](./bat-014_20260608_safety-sell-cooldown/) | -| BAT-015 | standard | released | REQ-007 (CHG-20260608-01), REQ-018 (CHG-20260608-01), REQ-021 (CHG-20260608-01), REQ-055 | dcy-011 | [bat-015](./bat-015_20260608_buy-intent-portfolio-id-contract/) | -| BAT-016 | standard | released | REQ-002 (CHG-20260608-01), REQ-003 (CHG-20260608-01), REQ-006 (CHG-20260608-01), REQ-007 (CHG-20260608-02), REQ-013 (CHG-20260608-01), REQ-015 (CHG-20260608-01), REQ-036 (CHG-20260608-01) | dcy-012 | [bat-016](./bat-016_20260608_sell-pricing-limit-price-rename/) | -| BAT-017 | standard | released | REQ-012 (CHG-20260608-01), REQ-013 (CHG-20260608-02), REQ-018 (CHG-20260608-02), REQ-031 (CHG-20260608-01), REQ-014 (CHG-20260608-01), REQ-015 (CHG-20260608-02) | dcy-013 | [bat-017](./bat-017_20260608_external-cancel-detection-stale-watchdog/) | -| BAT-018 | standard | released | REQ-005 (CHG-20260608-01), REQ-048 (CHG-20260608-01), REQ-009 (CHG-20260608-01) | dcy-014 | [bat-018](./bat-018_20260608_trading-date-timezone-daily-preallocation/) | -| BAT-019 | standard | released | REQ-017 (CHG-20260608-01), REQ-019 (CHG-20260608-01), REQ-018 (CHG-20260608-03), REQ-020 (CHG-20260608-01) | dcy-015 | [bat-019](./bat-019_20260608_ledger-outbox-permanent-error-backoff/) | -| BAT-020 | standard | released | REQ-032 (CHG-20260608-01), REQ-022 (CHG-20260608-01), REQ-021 (CHG-20260608-02), REQ-034 (CHG-20260608-01), REQ-026 (CHG-20260608-01) | dcy-016 | [bat-020](./bat-020_20260608_operator-cli-rpc-commands/) | -| BAT-021 | standard | released | REQ-034 (CHG-20260608-02), REQ-021 (CHG-20260608-03), REQ-037 (CHG-20260608-01), REQ-038 (CHG-20260608-01), REQ-039 (CHG-20260608-01), REQ-032 (CHG-20260608-02), REQ-042 (CHG-20260608-01) | dcy-017 | [bat-021](./bat-021_20260608_watchdog-ownership-visibility/) | -| BAT-022 | standard | released | REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) | dcy-016 | [bat-022](./bat-022_20260608_slot-state-snapshot-visibility/) | -| BAT-023 | standard | released | REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) | dcy-018 | [bat-023](./bat-023_20260608_ledger-cash-position-writeback-option-d/) | -| BAT-024 | batch-lite | released | REQ-032 (CHG-20260609-01) | dcy-016 (issue #17) | [bat-024](./bat-024_20260609_operator-enter-forced-close-at/) | -| BAT-025 | standard | released | REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01) | dcy-019 (issue #18) | [bat-025](./bat-025_20260609_slot-entry-count-and-closed-reallocation/) | -| BAT-026 | batch-lite | released | REQ-048 (CHG-20260609-01), REQ-010 (CHG-20260609-01) | 없음 (trex-orb-v1 정합) | [bat-026](./bat-026_20260609_config-dir-env-and-config-path/) | -| BAT-027 | standard | released | REQ-056, REQ-057 | dcy-020 (BAT-α) | [bat-027](./bat-027_20260610_asset-permanent-slot-foundation/) | -| BAT-028 | standard | released | REQ-058, REQ-059, REQ-060 | dcy-020 (BAT-β) | [bat-028](./bat-028_20260610_universe-reconcile-and-active-admission/) | -| BAT-029 | standard | released | REQ-061, REQ-062, REQ-063 | dcy-020 (BAT-γ) | [bat-029](./bat-029_20260610_asset-keyed-intent-and-divergence/) | -| BAT-030 | standard | released | REQ-064, REQ-062, REQ-056·057·065(잔여) | dcy-020 (BAT-δ) | [bat-030](./bat-030_20260610_asset-centric-cutover/) | -| BAT-031 | standard | released | REQ-066, REQ-067, REQ-057·061·062(CHG) | dcy-021 | [bat-031](./bat-031_20260610_position-sizing/) | -| BAT-032 | standard | released | REQ-068, REQ-069, REQ-070, REQ-071, REQ-072, REQ-061(CHG-05), REQ-013(CHG-03) | dcy-022 | [bat-032](./bat-032_20260610_execution-policy/) | -| BAT-033 | batch-lite | released | REQ-032 (CHG-20260610-06) | REQ-032 change-req (issue #23) | [bat-033](./bat-033_20260610_enter-qty-validation/) | -| BAT-034 | standard | released | REQ-002 (CHG-20260610-02), REQ-006 (CHG-20260610-02), REQ-007 (CHG-20260610-02), REQ-028 (CHG-20260610-01), REQ-032 (CHG-20260610-07) | dcy-023 (issue #22) | [bat-034](./bat-034_20260610_drop-allocated-capital/) | -| BAT-035 | standard | released | REQ-073, REQ-074, REQ-075, REQ-021 (CHG-20260611-01), REQ-034 (CHG-20260611-01), REQ-063 (CHG-20260611-01) | dcy-025 | [bat-035](./bat-035_20260611_strategy-read-surface-slots-req/) | -| BAT-036 | standard | released | REQ-048 (CHG-20260611-01), REQ-005 (CHG-20260611-01) | dcy-024 | [bat-036](./bat-036_20260611_safety-sell-disable-mode/) | -| BAT-037 | standard | released | REQ-073 (CHG-20260612-01) | dcy-025 (REQ-073 change-req) | [bat-037](./bat-037_20260612_slots-rpc-available-slots/) | -| BAT-038 | standard | released | REQ-018 (CHG-20260612-01) | dcy-026 (issue #24) | [bat-038](./bat-038_20260612_sell-transaction-tax-fee/) | -| BAT-039 | standard | released | REQ-011 (CHG-20260612-01) | 없음 (issue #4) | [bat-039](./bat-039_20260612_paper-seq-redis-persistence/) | -| BAT-040 | standard | released | REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) | 없음 (issue #25) | [bat-040](./bat-040_20260612_slots-rpc-db-filter/) | -| BAT-041 | standard | released | REQ-076, REQ-077, REQ-078 | dcy-027 (issue #20 이월) | [bat-041](./bat-041_20260612_long-term-instance-profile/) | -| BAT-042 | standard | released | REQ-079, REQ-080, REQ-081, REQ-082, REQ-073 (CHG-20260615-01), REQ-074 (CHG-20260615-01) | dcy-028 (issue #26) | [bat-042](./bat-042_20260615_slot-strategy-state-store/) | -| BAT-043 | batch-lite | released | REQ-007 (CHG-20260615-01) | 없음 (issue #27) | [bat-043](./bat-043_20260615_sell-intent-retryable-flag/) | -| BAT-044 | standard | released | REQ-083, REQ-070 (CHG-20260616-01) | dcy-030 (issue #28) | [bat-044](./bat-044_20260616_slippage-tick-size-snapping/) | -| BAT-045 | standard | released | REQ-084, REQ-085, REQ-087 | dcy-029 (issue #29, P0) | [bat-045](./bat-045_20260616_observability-logging-foundation/) | -| BAT-046 | standard | released | REQ-086, REQ-088, REQ-089, REQ-090 | dcy-029 (issue #29, P1) | [bat-046](./bat-046_20260616_observability-classification-pnl-runbook/) | -| BAT-047 | standard | released | REQ-091, REQ-092, REQ-093, REQ-094 | dcy-031 | [bat-047](./bat-047_20260619_event-publisher-connection-reliability/) | -| BAT-048 | standard | released | REQ-095, REQ-096, REQ-097 | dcy-032 | [bat-048](./bat-048_20260621_fill-price-backfill-guard-logging-alert-spike/) | -| BAT-049 | standard | released | REQ-098, REQ-099, REQ-100, REQ-101 | dcy-033 | [bat-049](./bat-049_20260621_alarm-system-dcy-033/) | -| BAT-050 | standard | release-candidate | REQ-102, REQ-103, REQ-104, REQ-107, REQ-108 | dcy-034 (core-A) | [bat-050](./bat-050_20260626_reconcile-warning-blocking-gate-core-a/) | -| BAT-051 | standard | release-candidate | REQ-105, REQ-106 | dcy-034 (core-B) | [bat-051](./bat-051_20260626_sync-verify-cli-cold-start-core-b/) | -| BAT-052 | standard | release-candidate | REQ-109, REQ-110, REQ-111 | dcy-035 | [bat-052](./bat-052_20260626_wac-realized-pnl-report/) | -| BAT-053 | standard | release-candidate | REQ-112, REQ-113, REQ-114, REQ-115 | dcy-036 | [bat-053](./bat-053_20260628_daystart-credential-fetch-failsafe/) | -| BAT-054 | standard | in-progress | REQ-113 (AC-2/AC-4 보강), REQ-115 (AC-1) | dcy-036 | [bat-054](./bat-054_20260628_daystart-credential-kis-inject/) | -| BAT-057 | standard | release-candidate | REQ-119, REQ-120, REQ-121, REQ-122 | dcy-037 | [bat-057](./bat-057_20260714_operator-broker-truth-hotfix/) | -| BAT-058 | standard | release-candidate | REQ-123 | dcy-038 | [bat-058](./bat-058_20260716_cost-basis-safe-sync-seed/) | -| BAT-059 | standard | release-candidate | REQ-124 | dcy-039 | [bat-059](./bat-059_20260727_intent-venue-fanin/) | -| BAT-062 | standard | release-candidate | REQ-127 | dcy-042 | [bat-062](./bat-062_20260729_shared-account-capital-allocation/) | -| BAT-063 | standard | release-candidate | REQ-118 (CHG-20260729-01) | REQ-118 change-req | [bat-063](./bat-063_20260729_kis-daily-fill-all/) | diff --git a/docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md b/docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md deleted file mode 100644 index 566914a..0000000 --- a/docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md +++ /dev/null @@ -1,245 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-001_20260603_slot-pure-domain-migration -- 이슈명: trex-core 베이스 수립 및 slot 순수 도메인 이식 -- 작성 시각(KST): 2026-06-03 -- 마지막 갱신 시각(KST): 2026-06-03 (draft-req) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-001, REQ-002, REQ-003, REQ-004 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 없음 (검토 초안 내용은 본 Discovery 본문과 부록 A 에 통합) -- 해석: - - bootstrap-baseline 초기화가 끝난 빈 베이스(trex-core)에서 시작하는 첫 real Discovery이며, 입력은 trex-orbit slot 도메인 코드 분석과 운영자 확정 결정(신규 베이스 + 순수 도메인만)이다. - - 이번 Discovery는 "코어 베이스 부트스트랩 + slot 순수 도메인 이식 + 단위테스트 green"의 범위와 성공 기준을 확정하고, 잔여 미결 결정(컬럼 계약 박제, OQ-2)을 드러내는 것이 핵심이다. 패키지 네임스페이스는 `trex_core.domain.slot` 으로 확정됨(OQ-1). - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - docs/discovery/ 아래 기존 Discovery 문서가 없고(첫 반복), 후보로 비교할 주기가 존재하지 않는다. - - 주제(코어 베이스 수립 + slot 순수 도메인 이식)가 기존 어떤 REQ/Batch/Release 와도 겹치지 않는 신규 문제 정의다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - trex-core 는 Python 코드 스캐폴딩이 전혀 없다 — `CLAUDE.md`, `docs/`(baseline 4종 + active index), `.stage-pilot/`(도구·템플릿·baseline.yaml) 만 존재하고 `app/`·`pyproject.toml`·`tests/` 가 없다. - - 이식 대상인 slot 도메인은 trex-orbit `app/domain/slot/`(총 ~1,150 LOC)에 있고, 그중 순수 6개 모듈(`state.py`/`models.py`/`errors.py`/`repository.py`/`service.py`/`in_memory_repository.py`)은 import 스윕 결과 **외부 의존이 0**(stdlib + 패키지 내부 참조만)으로 확인됐다. - - trex-core 의 baseline(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`)은 이미 slot 도메인을 planned 구조로 반영해 둔 상태이며, 실제 코드는 아직 없어 planned 와 실제 사이에 갭이 있다. -- 기대 상태 - - trex-core 가 자체 Python 프로젝트(pyproject + 패키지 레이아웃 + pytest/ruff)로 부트스트랩되어 빌드/테스트가 가능하다. - - slot 순수 도메인이 trex-core 안에 storage-agnostic 코어로 이식되고, 이식한 단위테스트가 trex-core 에서 통과한다. - - baseline 문서의 planned 구조가 실제 코드와 일치하도록 갱신된다. -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - trex-core 를 베이스로 사용할 후속 제품/도메인 개발자 -- 제약사항 - - 운영자 확정: 신규 베이스로 시작(trex-orbit 은 병행, 이번에 수정하지 않음) + 순수 도메인만 이식. - - 순수 도메인 경계 유지: 브로커/DB 드라이버/CLI 등 인프라에 직접 의존하지 않는다. - - 6 상태 / 8 전이 / advisory-lock 직렬화 / append-only 전이 로그 / closed→idle 재진입 +1 같은 도메인 불변식을 보존한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 개발자가 trex-core 를 clone 한 뒤 `pip install -e .[dev]` 와 `pytest` 로 slot 도메인 테스트가 통과하는 깨끗한 베이스를 확보한다. -- 후속 도메인 작성자가 slot 도메인을 trex-core 의 "표준 도메인 작성 패턴(storage-agnostic + Protocol 저장소 경계)" 참조로 삼는다. -- 다음 반복에서 영속화(PostgreSQL) 구현자가 이식된 `SlotRepository` Protocol 과 보존된 컬럼 계약(본 문서 부록 A)을 기준으로 저장소 구현을 붙인다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- trex-core Python 프로젝트 부트스트랩: `pyproject.toml`(배포명 `trex-core`) + 패키지 레이아웃(`trex_core/`, `trex_core/domain/`) + pytest/ruff 설정. -- slot 순수 도메인 이식: 6개 모듈 + `__init__.py` 복사, 공개 API 확정(`__init__` docstring 을 순수 도메인 기준으로 정리, 인프라 export 없음 확인). -- 도메인 단위테스트 이식 및 green: `test_state.py`, `test_service.py`(InMemorySlotRepository 기반)을 trex-core 에서 통과. -- 컬럼 계약 가드 신설(OQ-2 결정): 도메인에 표준 필드 집합 선언(`EXPECTED_SLOT_FIELDS`)을 두고, 순수 계약 테스트(`test_contract.py`)가 `dataclasses.fields(Slot)` 의 필드 집합과 일치함을 단언한다. DB-side 드리프트 검사는 영속화 반복으로 인계. -- baseline 문서 정합: 이식 후 `docs/project-structure.md`·`docs/runtime-flows.md` 의 planned 표기를 실제 구조와 일치하도록 갱신. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규 생성: `pyproject.toml`, `trex_core/__init__.py`, `trex_core/domain/__init__.py`, `trex_core/domain/slot/*`(6 모듈 + `__init__` + `EXPECTED_SLOT_FIELDS` 선언), `tests/domain/slot/*`(이식 2종 + 신규 `test_contract.py`). - - trex-orbit 코드: 무수정(병행). -- 문서 영향: - - `docs/project-structure.md`, `docs/runtime-flows.md` 의 planned→실제 갱신. - - `docs/srs/index.md`(REQ 등록), `docs/batches/index.md`(Batch 등록) 갱신은 후속 단계에서 발생. -- 운영 영향: - - 배포/운영 절차 변경 없음(라이브러리 시드, 런타임 데몬/CLI 없음). - - slot 도메인이 trex-orbit·trex-core 두 곳에 병존하는 임시 이중 진실 발생(동기화 부담은 리스크로 추적). - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: trex-core 를 설치/테스트 가능한 Python 패키지로 부트스트랩한다(`pyproject.toml` + 패키지 레이아웃 + pytest/ruff 설정). `pip install -e .[dev]` 후 `pytest` 가 실행 가능해야 한다. -- FR-2: slot 순수 도메인 6개 모듈(`state`/`models`/`errors`/`repository`/`service`/`in_memory_repository`)과 `__init__.py` 를 `trex_core.domain.slot` 네임스페이스(OQ-1 결정)로 이식하고, 모듈 간 import 경로를 `trex_core.domain.slot.*` 로 통일한다. 공개 API(`SlotState`/`Slot`/`SlotCapacity`/`SlotTransitionRecord`/`SlotRepository`/`SlotService`/예외/`is_allowed_transition`/`SLOT_ADVISORY_LOCK_ID`/`ALLOWED_TRANSITIONS`)를 인프라 심볼 없이 노출한다. -- FR-3: 이식된 도메인 단위테스트(`tests/domain/slot/test_state.py`, `test_service.py`)가 trex-core 에서 통과한다. -- FR-4: 이식 완료 후 `docs/project-structure.md` 와 `docs/runtime-flows.md` 의 planned 표기를 실제 코드 구조와 일치하도록 갱신한다. -- FR-5: 컬럼 계약 가드(OQ-2 결정) — 도메인 패키지에 표준 필드 집합 `EXPECTED_SLOT_FIELDS`(부록 A 를 가리키는 주석 포함)를 선언하고, 순수 계약 테스트 `tests/domain/slot/test_contract.py` 가 `{f.name for f in dataclasses.fields(Slot)} == EXPECTED_SLOT_FIELDS` 를 단언해 필드 추가/삭제 시 실패하도록 한다. 타입 단언은 하지 않고(이름/존재 중심) 타입 매핑은 부록 A 문서에 둔다. DB-side(information_schema) 드리프트 검사는 영속화 REQ 로 인계한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 순수 도메인 경계 유지 — 이식된 도메인 코드는 stdlib 와 패키지 내부 참조 외 외부 의존(psycopg/네트워크/CLI/config)을 import 하지 않는다(정적 import 스윕으로 검증 가능). -- NFR-2: 도메인 불변식 보존 — 6 상태/8 전이, advisory-lock 직렬화, append-only 전이 로그, closed→idle 재진입 +1, lock 보유 중 외부 I/O 금지가 이식 후에도 동일하게 성립한다. -- NFR-3: 컬럼 계약 보존 — `Slot` 데이터클래스 필드 계약(본 문서 부록 A)이 변형 없이 유지되어, 다음 반복의 영속화 구현이 동일 계약을 기준으로 삼을 수 있다. 보존은 선언(문서: 부록 A + `docs/data-model.md`)이 아니라 자동 가드(FR-5 계약 테스트)로 강제한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - trex-core 부트스트랩(pyproject/레이아웃/pytest/ruff). - - slot 순수 도메인 6개 모듈 + `__init__` 이식 및 공개 API 확정. - - 도메인 단위테스트(`test_state.py`, `test_service.py`) 이식 및 green. - - baseline 문서(project-structure, runtime-flows) planned→실제 갱신. -- Out of Scope - - PostgreSQL 구현체(`postgresql_repository.py`), SQL DDL/마이그레이션, `db init` 러너, schema drift 감지(`schema.py`). - - `trex slot` CLI(resolve/status/provision/archive), config 해석, coordinator 연동, daily_slot_archive/retention prune. - - trex-orbit 수정 및 trex-orbit↔trex-core 코드 수렴. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Medium): 영속화 부재로 trex-core 의 `SlotService` 는 in-memory repo 로만 구동되어, 단일 writer/advisory lock 의 실제 가치가 다음 반복(Postgres 재구성)까지 미실현된다. - - 완화 방안: 이번 반복 목표를 "도메인 코어 시드 + 테스트 green"으로 한정하고, 영속화는 별도 후속 Discovery 로 명시 인계한다. -- R-2 (Medium): DDL 을 함께 가져오지 않으므로 향후 영속화 구현 시 `Slot` 필드와 스키마가 어긋나는 컬럼 계약 드리프트 위험이 있다. - - 완화 방안: 이번 반복은 계약 테스트(FR-5)로 `Slot` 필드↔`EXPECTED_SLOT_FIELDS` 일치를 자동 강제하고, 부록 A·`docs/data-model.md` 를 문서 계약으로 둔다. DB-side(information_schema) 드리프트 검사는 영속화 REQ 로 인계한다. -- R-3 (Low): slot 도메인이 trex-orbit·trex-core 두 곳에 병존(이중 진실)해 로직 변경 시 동기화 부담이 생긴다. - - 완화 방안: 신규 베이스 결정의 의도된 비용으로 수용하고, 수렴 시점은 별도 Discovery(OQ 처리)로 추적한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: trex-orbit 의 순수 6개 모듈은 외부 의존이 0이라 코드 수정 없이 복사 가능하다(import 스윕으로 확인됨). -- A-2: 이식 시 유일한 코드 수정은 `__init__.py` docstring 정리 수준이며, 로직 변경은 없다. -- A-3: trex-core 의 툴체인은 trex-orbit 과 동등(Python >=3.12, setuptools, pytest, ruff)을 기본 채택한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 패키지 네임스페이스를 trex-orbit 과 동일한 `app.domain.slot` 으로 유지할지, trex-core 전용 `trex_core.domain.slot` 으로 출발할지. - - 상태: Answered (2026-06-03) - - 결정: `trex_core.domain.slot` 으로 출발(독립 베이스 네임스페이스). 근거: 재사용 코어 정체성·`app` top-level 패키지 충돌 회피·OQ-3(향후 수렴) 시 자연스러운 import 경로. 이식 시 import 라인 1회 기계적 치환 비용만 발생. - - 반영 위치: `# 4`(코드 영향 신규 생성 경로), `# 5` FR-1(레이아웃)·FR-2(import 경로 통일). baseline 구조 표기(`docs/project-structure.md`·`docs/runtime-flows.md` 등)의 `app/` planned 표기는 FR-4 갱신 시 `trex_core/` 로 정정한다. -- OQ-2: `Slot` 컬럼 계약(본 문서 부록 A)을 영속화 미구현 상태에서 어떻게 드리프트 없이 박제할지(문서 고정 vs 계약 테스트 vs 스키마 placeholder). - - 상태: Answered (2026-06-03) - - 결정: 계약 테스트를 주 수단으로 채택(2층 구조). ① 코드 가드 = 도메인의 `EXPECTED_SLOT_FIELDS` ↔ `dataclasses.fields(Slot)` 일치 단언(FR-5). ② 문서 거울 = 부록 A + `docs/data-model.md`. DB-side(information_schema) 드리프트 검사는 영속화 반복으로 인계. 근거: 문서 고정은 강제력이 없어 R-2 미완화, 스키마 placeholder 는 제외한 영속화 인프라의 조기 유입이라 범위 위반. 계약 테스트만 순수 도메인 범위 안에서 NFR-3 을 자동 강제. - - 반영 위치: `# 4`(계약 가드 신설 항목·신규 파일), `# 5` FR-5, NFR-3, `# 6` R-2 완화, `# 7` S-4. -- OQ-3: trex-orbit↔trex-core slot 도메인의 향후 수렴(역참조/단일화) 여부. - - 상태: Deferred - - 처리 방안: 이번 반복 비범위. 영속화 재구성 이후 별도 Discovery 에서 다룬다. OQ-1 결과(`trex_core.domain.slot`)를 인계 — 수렴 시 trex-orbit 이 `trex_core.domain.slot` 을 역참조하는 경로가 자연스럽다. - - 종료 조건: 영속화 반복 종료 후 수렴 Discovery 가 생성되면 그 문서로 이관. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: trex-core 가 부트스트랩되어 `pip install -e .[dev]` 후 `pytest` 가 정상 수집/실행된다. -- S-2: 이식한 slot 도메인 단위테스트 2개 파일이 모두 통과한다. -- S-3: 이식된 도메인 코드의 import 스윕에서 외부(인프라) 의존이 0으로 확인되고, baseline 문서(project-structure/runtime-flows)가 실제 구조와 일치한다. -- S-4: 계약 테스트(`test_contract.py`)가 `Slot` 필드 집합과 `EXPECTED_SLOT_FIELDS` 일치를 단언하고 통과한다(필드 변경 시 실패하는 것을 음성 케이스로 확인). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 클린 환경에서 설치 후 `pytest` 실행 로그 확인 / 데이터 출처: CI 또는 로컬 `pytest` 출력, `pyproject.toml`. -- S-2 측정 방식: `pytest tests/domain/slot` 통과 여부 / 데이터 출처: 테스트 실행 결과(green count). -- S-3 측정 방식: `grep`/정적 import 스윕으로 외부 모듈 import 0 확인 + baseline 문서와 실제 트리 비교 / 데이터 출처: 소스 import 목록, `docs/project-structure.md`·`docs/runtime-flows.md`. -- S-4 측정 방식: `pytest tests/domain/slot/test_contract.py` 통과 + 임시 필드 추가/삭제 시 실패 재현 / 데이터 출처: 계약 테스트 실행 결과, `EXPECTED_SLOT_FIELDS` 선언. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 운영자가 In/Out 경계(특히 영속화·CLI 제외)를 확인했다. → confirm-discovery 에서 문서대로 확정(2026-06-03). -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-3 각각 측정 방식과 데이터 출처가 명시됨(본 문서 # 7). -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: High 등급 리스크 없음(R-1/R-2 Medium, R-3 Low) — 별도 책임자 지정 불요, 완화 방안 명시됨. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 (OQ-1·OQ-2 Answered, OQ-3 Deferred → `Open` 0개) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. → Confirmed By: Justin. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md -- 참조 문서: - - docs/project-structure.md, docs/runtime-flows.md (구조/런타임 baseline — 본 Discovery 가 갱신 요구) - - docs/interface-contract.md, docs/data-model.md (인터페이스/데이터 baseline — 컬럼 계약 기준) - - stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md (baseline 갱신 시 논리 템플릿 참조) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~패키지 네임스페이스~~ → 결정됨: `trex_core.domain.slot` (OQ-1, 2026-06-03) -- ~~컬럼 계약 박제 수단~~ → 결정됨: 계약 테스트 주 수단 + 문서 거울, DB-side 드리프트는 영속화 반복 인계 (OQ-2, 2026-06-03) - -## CONFIRM -- ~~범위 경계~~ → 확정됨: 문서 §5 범위 경계대로(영속화·SQL·CLI·config·coordinator·trex-orbit 수정 = Out of Scope) (confirm-discovery, 2026-06-03) -- ~~툴체인~~ → 확정됨: trex-orbit 동등(Python >=3.12 + setuptools + pytest + ruff) 채택 (confirm-discovery, 2026-06-03) - -## DATA -- 없음 (별도 외부 데이터 요청 없음) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 모든 오픈 질문 해소(OQ-1 `trex_core.domain.slot`·OQ-2 계약 테스트 Answered, OQ-3 Deferred). 범위·툴체인 CONFIRM(§10)을 confirm-discovery 에서 확정하고 CONFIRMED_BY 지정 완료. REQ Drafting(FR-1~FR-5) 으로 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-03 - -# 부록 A. `Slot` 데이터클래스 ↔ `daily_slot` 컬럼 계약 (이식 보존 대상) -NFR-3 과 OQ-2 가 참조하는 컬럼 계약이다. 순수 도메인의 `Slot` 데이터클래스는 trex-orbit `daily_slot` 스키마 컬럼 계약을 암묵 전제하므로, trex-core 가 다음 반복에서 영속화 계층을 새로 구성할 때 이 계약을 반드시 만족시켜야 도메인이 정상 동작한다. (`docs/data-model.md` 와 동일 기준) - -| 필드 | 타입(도메인) | 원본 DDL 타입 | 비고 | -|---|---|---|---| -| `slot_id` | UUID | UUID PK | | -| `trading_date` | date | DATE NOT NULL | | -| `status` | SlotState | TEXT CHECK(6값) | idle/pending_buy/holding/pending_sell/closed/error | -| `symbol` | str? | TEXT | | -| `qty` | float? | NUMERIC(18,4) | | -| `avg_price` | float? | NUMERIC(18,4) | | -| `entry_price` | float? | NUMERIC(18,4) | 가격 잠금 | -| `allocated_capital` | float? | NUMERIC(18,2) | | -| `order_id` | str? | TEXT | 자유 주석 | -| `strategy_id` | str? | TEXT | 진입 전략 | -| `broker_order_id` | str? | TEXT | 브로커 식별자 | -| `portfolio_id` | str? | TEXT | | -| `sell_strategy_id` | str? | TEXT | 청산 전략 | -| `forced_close_at` | datetime? | TIMESTAMPTZ | 강제청산 마감선 | -| `created_at` / `updated_at` | datetime? | TIMESTAMPTZ | | - -부속 테이블 계약(영속화 재구성 시 함께 필요): `daily_symbol_entries`(재진입 카운터 — PK `(trading_date, symbol)`), `slot_transition_log`(append-only 감사 로그). 본 이관(순수 도메인)에서는 계약만 보존하고 DDL 은 가져오지 않는다. diff --git a/docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md b/docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md deleted file mode 100644 index fe43dec..0000000 --- a/docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md +++ /dev/null @@ -1,238 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed (transport 부분은 dcy-006/BAT-007 에서 RabbitMQ 로 대체됨; logical intent/admission 계약은 유효) -- 문서 ID: dcy-002_20260603_slot-core-daemon-http-api -- 이슈명: trex-core 슬롯 코어 데몬화 + HTTP intent API -- 작성 시각(KST): 2026-06-03 -- 마지막 갱신 시각(KST): 2026-06-05 (BAT-007 transport supersede) -- 대체됨: 본 Discovery 의 **HTTP `/v1` API transport** 가정은 dcy-006(`strategy-module-rabbitmq-transport`, BAT-007)에서 RabbitMQ messaging 으로 대체됨. logical 계약(intent admission, idempotency via `intent_log`, 단일 writer 불변식)은 그대로 유효하며 transport 만 교체. `adapters/api/` HTTP 어댑터, `GET /v1/healthz`, `POST /v1/intents/buy|sell`, `GET /v1/slots`, `GET /v1/capacity` 엔드포인트는 BAT-007 에서 제거됨. -- 후속 Discovery 참조: dcy-006_20260605_strategy-module-rabbitmq-transport (transport 대체) -- 생성된 REQ 참조: REQ-005, REQ-006, REQ-007, REQ-008, REQ-009 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: trex-orbit `orbit-v2_plan.md`(3-데몬 분리 설계안 — 슬롯 코어/매수·매도 결정 producer/ intent 핸드오프 사전 설계) -- 해석: - - dcy-001/BAT-001 로 trex-core 에 slot 순수 도메인(in-memory, 라이브러리)이 시드됐다. 이번 Discovery 는 그 코어를 **장기 실행 데몬**으로 승격하고, 매수·매도 전략이 **HTTP API 엔드포인트**로 intent 를 제출/조회하는 경계를 정의한다. - - 운영자 결정: 전략↔코어 경계는 **네트워크 HTTP API**다(DB-only outbox 가 아니라 API 엔드포인트). 순수 도메인은 안쪽 코어로 유지하고 데몬·API·영속화를 바깥 레이어로 추가한다(onion). - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-001 은 "순수 도메인 이식(라이브러리)"이 문제 정의였고, 이번은 "데몬화 + API 경계 + 영속화"로 범위·구조가 다르다. - - dcy-001 이 명시적으로 Out of Scope 로 둔 영속화/CLI/런타임을 정면으로 다루므로 기존 Discovery 갱신이 아니라 후속 독립 주기가 맞다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - trex-core 는 `trex_core/domain/slot/` 순수 도메인만 보유(상태탐색 결과: server/api/daemon/http/psycopg 참조 0, console_scripts 0). `SlotService` 는 in-memory repo 로만 구동되고 장기 실행 프로세스·외부 호출 경계가 없다. - - 영속화 구현체(PostgreSQL `SlotRepository`)·브로커·API·데몬 런타임이 모두 부재(dcy-001 에서 의도적으로 범위 밖이었음, dcy-001 R-1). - - 설계 참고자료(`orbit-v2_plan.md`)는 이미 슬롯 코어 단일 writer(불변식 A)·단일 브로커 세션(불변식 B)·buy/sell intent 핸드오프·safety floor 를 정리해 둔 상태다. -- 기대 상태 - - trex-core 가 데몬 프로세스로 떠서 slot 도메인을 **durable 단일 writer**로 소유한다. - - 매수·매도 전략 데몬이 HTTP API 로 intent 를 제출하고(매수/매도), holding 슬롯 스냅샷을 read-only 로 조회한다. - - 코어가 intent 를 심사(admission)해 슬롯 전이를 단독 수행한다(전략은 "제안", 코어가 "집행"). -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - 코어 API 소비자: 매수·매도 전략 데몬(별도 프로세스) -- 제약사항 - - 불변식 A — `daily_slot` 단일 writer 는 코어뿐. 전략은 슬롯을 직접 쓰지 않는다. - - 순수 도메인 경계 유지 — `trex_core.domain.slot` 은 웹 프레임워크/DB 드라이버에 의존하지 않는다(의존은 API·persistence 어댑터 레이어에만). - - dcy-001 부록 A 의 `Slot` 컬럼 계약을 영속화 구현이 만족해야 한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 매수 전략 데몬이 신호 발생 시 `POST /v1/intents/buy`(symbol/qty/...)를 호출하면, 코어가 admission 통과 시 idle→pending_buy 로 전이하고 결과를 응답한다. -- 매도 전략 데몬이 `GET /v1/slots?status=holding` 로 보유 슬롯 스냅샷을 읽어 전략을 평가한 뒤 `POST /v1/intents/sell` 로 청산 intent 를 제출한다. -- 운영자가 `GET /v1/capacity`·`GET /v1/healthz` 로 코어 데몬의 슬롯 가용성과 생존을 모니터링한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 데몬 런타임: trex-core 를 장기 실행 프로세스로 기동하는 엔트리포인트 + lifecycle(start/shutdown/healthz). -- HTTP API 경계: intent 제출(buy/sell) + holding 슬롯 read-only 스냅샷 + capacity/health 엔드포인트. -- 영속화: `SlotRepository` 의 PostgreSQL 구현(단일 writer, advisory lock 실제화) + 스키마/마이그레이션. in-memory 는 테스트용으로 잔존. -- 멱등 intent 처리(OQ-1 결정): `intent_id` 기반 dedup + 수락 시 **동기** 슬롯 전이 커밋(불변식 A 강제). 수락된 intent 는 같은 트랜잭션에서 `intent_log` 에 durable 기록(dedup 저장소 + audit + 향후 outbox 전구체). 완전 비동기 outbox 는 브로커 집행 Discovery 로 Deferred. -- baseline 갱신: `docs/project-structure.md`·`docs/runtime-flows.md`·`docs/interface-contract.md` 에 데몬/API/영속화 레이어 반영. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규 레이어: `trex_core/` 하위 API 어댑터(FastAPI+uvicorn, OQ-2 결정), persistence 어댑터(psycopg), 데몬 런타임/엔트리포인트. - - `pyproject.toml`: FastAPI+uvicorn + psycopg 의존성 및 console_scripts 추가. 도메인 패키지는 무의존 유지. -- 문서 영향: - - `docs/interface-contract.md` 에 HTTP API 계약 추가, `docs/runtime-flows.md` 에 데몬/요청 흐름 추가, `docs/data-model.md`/`docs/project-structure.md` 갱신. -- 운영 영향: - - 신규 운영물: 장기 실행 데몬 프로세스 + PostgreSQL 의존 + API 포트/헬스체크. 배포·모니터링 절차 신설. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: trex-core 를 데몬으로 기동하는 엔트리포인트와 lifecycle(기동/정상종료/`GET /v1/healthz`)을 제공한다. 거래일 슬롯 사전할당(preallocate)은 공개 엔드포인트가 아니라 데몬 lifecycle 내부(기동/스케줄) 책임으로 둔다(OQ-3 결정). -- FR-2: HTTP API 로 매수 intent(`POST /v1/intents/buy`)와 매도 intent(`POST /v1/intents/sell`)를 수락하고, 코어가 admission/검증 후 슬롯 전이(idle→pending_buy / holding→pending_sell)를 단독 수행한다. API 는 `/v1` 버전 프리픽스를 사용한다(OQ-3 결정). -- FR-3: HTTP API 로 holding 슬롯 스냅샷(`GET /v1/slots?status=holding`)과 capacity(`GET /v1/capacity`)를 read-only 로 제공한다. -- FR-4: intent 는 `intent_id` 멱등키로 dedup 하여 동일 intent 재전송이 슬롯을 중복 전이시키지 않는다. 수락 시 admission+슬롯 전이를 **동기**로 커밋해 호출자에게 admit/reject 를 응답하고(OQ-1 결정), 수락된 intent 를 `intent_log`(intent_id, payload, decision, ts) 에 같은 트랜잭션으로 durable 기록한다. `GET /v1/intents/{intent_id}` 로 결정을 재조회할 수 있어(응답 유실 시) 멱등 재시도를 지원한다. 완전 비동기 outbox 는 브로커 Discovery 로 Deferred. -- FR-5: `SlotRepository` 의 PostgreSQL 구현을 추가해 코어가 `daily_slot` 단일 writer 로 durable 하게 동작한다(advisory lock 실제화). 스키마는 **trex-core 소유의 신규 마이그레이션**으로 작성하되 dcy-001 부록 A 를 단일 기준으로 하고 orbit-v2 DDL 을 참조 구현으로만 쓴다(OQ-4 결정). 슬라이스 1 테이블: `daily_slot`(부록 A 16컬럼)·`daily_symbol_entries`·`slot_transition_log` + `intent_log`(OQ-1). `daily_slot_archive`/prune 는 범위 밖. `EXPECTED_SLOT_FIELDS` ↔ `daily_slot` 실제 컬럼(information_schema) 드리프트 테스트를 동반해 dcy-001 OQ-2 가 인계한 DB-side 검사를 실현한다. -- FR-6: `docs/interface-contract.md`·`docs/runtime-flows.md`·`docs/project-structure.md`·`docs/data-model.md` 를 데몬/API/영속화 레이어에 맞게 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 도메인 순수성 유지 — `trex_core.domain.slot` 은 웹 프레임워크/psycopg 에 의존하지 않는다(의존은 API·persistence 어댑터 레이어에 격리; import 스윕으로 검증). -- NFR-2: 단일 writer 불변식 — `daily_slot`/`SlotService.transit_slot` 쓰기는 코어 경로에서만 일어난다(API 핸들러가 advisory lock 으로 직렬화). -- NFR-3: 멱등/내구성 — 수락된 intent 의 슬롯 전이는 at-least-once 재전송에도 1회만 반영된다(`intent_id` dedup via `intent_log`). "수락 == 커밋"이라 수락-후-미처리 유실 창이 없다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 데몬 런타임 + HTTP API(intent 제출 + holding/capacity 조회 + health). - - PostgreSQL `SlotRepository` 영속화(단일 writer) + 멱등 intent 처리. - - baseline 문서 갱신. -- Out of Scope (후속 Discovery) - - 완전 비동기 durable outbox(intent 상태머신 + 소비 루프) — 브로커 집행 Discovery 에서 `intent_log` 를 승격(OQ-1 Deferred 부분). - - 브로커 단일 세션/실제 주문 집행(불변식 B, order execution). - - safety floor(forced_close/EOD/손절 backstop, liveness 게이팅) 및 sell_evaluation_log. - - 매수·매도 전략 데몬 자체 구현(코어의 소비자이며 별 repo/프로세스). - - 인증/인가 고도화, 멀티테넌시, 수평 확장. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 브로커 집행 없이 코어만 만들면 intent→슬롯 전이까지만 동작해 "거래"는 미완성. 부분 가치만 실현. - - 완화 방안: 이번 반복을 "durable 단일 writer 슬롯 코어 + API 경계"로 한정하고, 브로커/ safety floor 를 후속 Discovery 로 명시 인계(범위 경계에 박음). -- R-2 (Medium): HTTP 는 at-least-once 를 보장하지 않아 intent 중복/유실 가능. - - 완화 방안(OQ-1 결정): `intent_id` 멱등키(FR-4) + 수락 시 동기 커밋 + `intent_log` durable 기록. 브로커 집행이 들어오는 후속 Discovery 에서 `intent_log` 에 status+소비 루프를 얹어 outbox 로 점진 승격(API 계약 무변경). -- R-3 (Medium): 웹 프레임워크/psycopg 의존이 도메인 순수성을 오염시킬 위험. - - 완화 방안: 레이어 분리(domain/ application/ adapters) + import 스윕 가드(NFR-1, dcy-001 의 계약 테스트 패턴 확장). -- R-4 (Medium): orbit-v2 SQL 스키마와 trex-core `Slot` 컬럼 계약(부록 A) 불일치 위험. - - 완화 방안: 부록 A 를 기준으로 스키마 작성 + 드리프트 테스트(dcy-001 이 인계한 DB-side 검사 여기서 실현). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 매수·매도 전략은 별도 프로세스이며 코어 API 의 클라이언트다(코어는 전략 로직을 포함하지 않는다). -- A-2: 단일 코어 인스턴스가 `daily_slot` 단일 writer 다(슬라이스 1 은 수평 확장 비대상). -- A-3: 영속화는 PostgreSQL 을 사용하며 dcy-001 부록 A 컬럼 계약을 만족한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: intent 내구성 — 수락 시 동기 admission+커밋(슬라이스 1 기본) vs 내부 durable outbox 적재 후 비동기 소비. - - 상태: Answered (2026-06-03) - - 결정: **동기 커밋 + `intent_id` 멱등 + durable `intent_log` 이음매**. 수락 시 admission+슬롯 전이를 동기 커밋하고 admit/reject 를 즉시 응답, 수락 intent 를 같은 트랜잭션에서 `intent_log` 에 기록. 완전 비동기 outbox 는 가치(느린 브로커 집행 디커플링)가 실현되는 **브로커 집행 Discovery 로 Deferred** 하며, 그때 `intent_log` 에 status+소비 루프를 얹어 API 계약 변경 없이 승격한다. 근거: 이번 범위엔 intent 뒤 느린 다운스트림이 없어 outbox 이득 미실현 + 동기 응답이 매수 admit/reject UX 에 유리 + "수락==커밋"이 유실 창 제거. - - 반영 위치: `# 4`(멱등 처리 항목), `# 5` FR-4·NFR-3, `# 6` R-2 완화, `# 5` Out of Scope(완전 outbox Deferred). -- OQ-2: API 프레임워크/스택 — FastAPI+uvicorn(기본 권장) vs Flask vs stdlib http. Python >=3.12. - - 상태: Answered (2026-06-03) - - 결정: **FastAPI + uvicorn** 채택. 근거: 타입 기반 요청/응답 검증(pydantic), async, OpenAPI 자동 문서. Python >=3.12. - - 반영 위치: `# 4` 코드 영향(API 어댑터·pyproject 의존성). REQ 단계에서 부트스트랩(REQ-A 계열)에 의존성·버전 고정. -- OQ-3: 엔드포인트 1차 범위 — `/intents/buy`,`/intents/sell`,`/slots`,`/capacity`,`/healthz` 로 충분한지, admission 결과 조회/취소 엔드포인트가 필요한지. - - 상태: Answered (2026-06-03) - - 결정: 아래 6개로 1차 고정(`/v1` 버전 프리픽스). 모두 단일 코어 데몬이 노출. - - `GET /v1/healthz` — liveness/readiness. - - `POST /v1/intents/buy` — 매수 intent 수락, 동기 admit/reject 응답(+ slot_id). - - `POST /v1/intents/sell` — 매도 intent 수락, 동기 admit/reject 응답. - - `GET /v1/intents/{intent_id}` — 멱등 재조회/audit(POST 응답 유실 시 결정 재확인; FR-4 보강). - - `GET /v1/slots?status=holding` — holding 슬롯 스냅샷 read-only(status/trading_date 필터). - - `GET /v1/capacity` — idle/holding/pending + scarcity_ratio. - - 제외(슬라이스 1): intent 취소/admission 취소 엔드포인트(브로커·rollback 경로와 함께 후속), 슬롯 사전할당 엔드포인트(데몬 lifecycle 내부 처리, FR-1). - - 반영 위치: `# 5` FR-1/FR-2/FR-3/FR-4, `# 4` HTTP API 경계. interface-contract 초안은 REQ/Batch design 에서 고정. -- OQ-4: 영속화 스키마 출처 — orbit-v2 의 slot SQL 을 가져와 정합 vs 부록 A 기준으로 신규 작성. - - 상태: Answered (2026-06-03) - - 결정: **부록 A 를 단일 기준으로 trex-core 소유의 신규 마이그레이션을 작성**하고 orbit-v2 DDL 은 참조 구현으로만 사용. 슬라이스 1 테이블 = `daily_slot`·`daily_symbol_entries`·`slot_transition_log`·`intent_log`(archive/prune 제외). `EXPECTED_SLOT_FIELDS`↔information_schema 드리프트 테스트로 dcy-001 OQ-2 의 DB-side 검사 실현. 근거: 신규 베이스가 자기 스키마 lineage 를 소유(OQ-1 독립 결정과 일관) + 검증된 orbit DDL 재사용(재발명 회피). - - 반영 위치: `# 5` FR-5, `# 6` R-4 완화. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 코어 데몬을 기동하고 `GET /v1/healthz` 가 200 을 반환한다. -- S-2: `POST /v1/intents/buy` 호출 시 idle 슬롯이 pending_buy 로 전이되고, 동일 `intent_id` 재전송이 중복 전이를 만들지 않는다(멱등). -- S-3: `GET /v1/slots?status=holding` 가 holding 슬롯 스냅샷을 read-only 로 반환한다. -- S-4: PostgreSQL repo 로 코어를 구동했을 때 도메인 단위테스트/통합테스트가 통과하고, `daily_slot` 쓰기가 코어 경로로만 발생한다(단일 writer). -- S-5: 도메인 패키지 import 스윕에서 웹 프레임워크/psycopg 의존이 0 으로 유지된다(레이어 격리). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 데몬 기동 후 healthz 호출 / 데이터 출처: 통합테스트 또는 수동 curl 로그. -- S-2 측정 방식: API 통합테스트(정상 1회 + 중복 재전송) / 데이터 출처: 테스트 결과, 슬롯 상태·transition_log. -- S-3 측정 방식: holding 슬롯 조성 후 GET 응답 검증 / 데이터 출처: API 통합테스트. -- S-4 측정 방식: PostgreSQL 백엔드 통합테스트(가능 시) + 쓰기 경로 정적 검토 / 데이터 출처: 테스트 결과, 코드 리뷰. -- S-5 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain/slot` import 목록. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — 특히 브로커/safety floor 제외 - - 기준: 운영자가 슬라이스 1 경계를 확인했다. → confirm-discovery 에서 문서대로 확정(2026-06-03). -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7). -- [x] High 영향 리스크(R-1) 처리 방침 확정 - - 기준: R-1(브로커 미포함) 을 후속 Discovery 인계로 수용. → 범위 경계 확정과 함께 수용(2026-06-03). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (OQ-1~OQ-4 전부 Answered → `Open` 0개) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. → Confirmed By: Justin. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md -- 참조 문서: - - docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md (순수 도메인 코어 + 부록 A 컬럼 계약) - - docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, docs/data-model.md (baseline — 본 Discovery 가 갱신 요구) - - trex-orbit `orbit-v2_plan.md` (3-데몬 설계 참고자료) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~intent 내구성~~ → 결정됨: 동기 커밋 + `intent_id` 멱등 + durable `intent_log`, 완전 outbox 는 브로커 Discovery 로 Deferred (OQ-1, 2026-06-03) -- ~~영속화 스키마 출처~~ → 결정됨: 부록 A 기준 trex-core 소유 신규 마이그레이션(orbit-v2 DDL 참조), archive 제외, 드리프트 테스트 동반 (OQ-4, 2026-06-03) - -## CONFIRM -- ~~API 프레임워크~~ → 결정됨: FastAPI + uvicorn (OQ-2, 2026-06-03) -- ~~1차 엔드포인트 집합~~ → 결정됨: `/v1`(healthz, intents/buy, intents/sell, intents/{id}, slots, capacity); 취소·preallocate 엔드포인트 제외 (OQ-3, 2026-06-03) -- ~~범위 경계(브로커 deferral)~~ → 확정됨: 브로커 집행·safety floor·완전 outbox·전략 데몬을 후속 Discovery 로 미루고 슬라이스 1(데몬+API+영속화+멱등) 승인 (confirm-discovery, 2026-06-03) - -## DATA -- 없음 (별도 외부 데이터 요청 없음) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~OQ-4 전부 Answered(내구성=동기+intent_log / 프레임워크=FastAPI / 엔드포인트=/v1 6종 / 스키마=부록 A 기준 신규). 범위 경계(브로커·safety floor·outbox·전략 데몬 후속 Deferred)와 R-1 처리 방침을 confirm-discovery 에서 확정, CONFIRMED_BY 지정 완료. REQ Drafting 으로 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-03 diff --git a/docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md b/docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md deleted file mode 100644 index 143139f..0000000 --- a/docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md +++ /dev/null @@ -1,232 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-003_20260604_broker-execution-and-outbox -- 이슈명: 브로커 단일 세션 + 주문 집행 + durable outbox (intent→실제 주문, 불변식 B) -- 작성 시각(KST): 2026-06-04 -- 마지막 갱신 시각(KST): 2026-06-04 (draft-req) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-011, REQ-012, REQ-013, REQ-014, REQ-015 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: trex-orbit `orbit-v2_plan.md`(불변식 B·Order Execution·forced_close 소비자 사전 설계), dcy-002(슬롯 코어 데몬·intent API, 브로커/outbox 를 본 Discovery 로 Deferred) -- 해석: - - dcy-002/BAT-002 로 코어가 intent 를 받아 슬롯을 `pending_buy`/`pending_sell` 로 전이까지는 하지만, **실제 브로커 주문이 없어 pending 슬롯이 체결로 진행되지 않는다**(거래 미완성, dcy-002 R-1). - - 이번 Discovery 는 코어에 **단일 브로커 세션(불변식 B)** + **주문 집행/체결 처리** + **durable 주문 핸드오프(outbox 승격)**를 더해 intent 를 실제 주문으로 잇는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-002 는 명시적으로 브로커 집행·완전 outbox 를 Out of Scope 로 두고 본 후속 Discovery 로 Deferred 했다. - - 기존 REQ 본문 수정이 아니라 신규 능력(브로커 세션·주문 집행·체결·outbox)이라 `change-req` 가 아니라 새 Discovery 다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 코어 데몬은 intent 수락 → 슬롯 `idle→pending_buy`/`holding→pending_sell` 전이까지 수행(단일 writer, 불변식 A). 그러나 브로커/주문 집행/체결 처리 레이어가 없다(상태탐색: broker/order-exec/fill/dispatch 모듈 0). - - `pending_buy`/`pending_sell` 슬롯을 `holding`/`closed` 로 진행시키는 **시스템 경로가 없다**(전이는 허용 정의만 있고 구동 주체 부재 — 현재는 테스트만 수동 전이). - - `intent_log` 는 dedup 게이트 전용(비동기 outbox 아님, dcy-002 OQ-1). -- 기대 상태 - - 코어가 **단일 브로커 세션**을 소유(불변식 B)하고, pending 슬롯을 집행해 매수/매도 주문을 송신한다. - - 체결 시 `pending_buy→holding`(매수 체결) / `pending_sell→closed`(매도 체결), 거부/취소 시 `pending_buy→idle` 롤백을 코어가 단독 수행. - - 주문 핸드오프가 durable 하여 크래시 후 reconciliation 가능(중복 주문 없이 재개). -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - 코어 API 소비자(매수·매도 전략 데몬) — 집행 결과(체결/거부)를 슬롯 상태로 관측 -- 제약사항 - - 불변식 A — `daily_slot` 단일 writer 는 코어뿐(유지). - - 불변식 B — 브로커 주문/취소는 코어의 단일 세션만(전략은 브로커 직접 접근 금지). - - 도메인 순수성 — `trex_core.domain.slot` 은 브로커/네트워크 의존 0 유지(브로커는 adapters 레이어). - - 실거래 안전 — 중복 주문 금지(멱등), 크래시 reconciliation, 안전한 기본값. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 매수 intent 가 `pending_buy` 로 전이된 뒤, 코어 집행이 브로커에 매수 주문을 송신하고 체결되면 슬롯이 `holding` 으로 진행된다. -- 매도 intent 로 `pending_sell` 이 된 슬롯을 코어가 매도 주문 송신 → 체결 시 `closed` 로 진행, 재진입 카운트 +1(recovery). -- 데몬이 주문 송신 직후 크래시 → 재기동 시 reconciliation 으로 미체결/중복 주문을 정리(중복 주문 없음). - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 브로커 포트 + 단일 세션 어댑터(불변식 B): `BrokerPort`(submit_order/cancel/poll_fill 등) + 어댑터(슬라이스는 paper/stub 브로커 우선). -- 주문 집행 루프(OQ-4 결정=별도 백그라운드 루프): pending 슬롯 claim → 주문 송신 → `broker_order_id` 기록 → 체결 폴링/처리 → 슬롯 전이(holding/closed) + 거부 시 롤백(idle) + reconcile. intent API 는 pending 전이까지만, 집행은 루프가 비동기. -- durable 주문 핸드오프(OQ-3 결정=옵션 A): pending 슬롯 자체가 실행 큐. `broker_order_id` 멱등 가드 + 재기동 reconciliation. 별도 outbox 테이블 미신설(부분체결/재시도 본격화 시 옵션 B 승격 hook). -- baseline 갱신: runtime-flows(집행/체결 흐름)·interface-contract(브로커 경계)·project-structure(broker adapter/execution 레이어)·data-model(주문/체결 상태). - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규: `trex_core/` 하위 broker 포트 + 어댑터 2종(paper/stub + **KIS**), 주문 집행 컴포넌트(루프/워커), 체결 폴링/처리, reconciliation. `pyproject` 에 KIS 호출용 HTTP 의존(httpx 등) + KIS 자격증명 config(.env). - - 기존: 집행이 SlotService 로 `pending→holding/closed`/`pending_buy→idle` 전이를 구동(단일 writer 유지). -- 문서 영향: - - runtime-flows/interface-contract/project-structure/data-model 갱신. -- 운영 영향: - - 실주문 송신 → 실거래 리스크. 브로커 자격증명/세션 관리, 레이트리밋, 장애 시 reconciliation 운영 절차 신설. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `BrokerPort` 추상화와 단일 세션 어댑터를 제공하고(불변식 B), 코어만 주문/취소를 송신한다. 어댑터는 (a) 결정적 검증용 paper/stub 와 (b) **실제 KIS(한국투자증권) Open API 어댑터**를 모두 포함한다(OQ-1 결정). KIS 어댑터는 슬라이스에서 **KIS 모의투자 서버**로 검증하고, 실계좌 라이브 전환은 config 게이팅(운영 결정)으로 둔다. -- FR-2: 주문 집행 컴포넌트가 `pending_buy` 슬롯을 `FOR UPDATE SKIP LOCKED`(status=pending_buy AND broker_order_id IS NULL)로 claim 해 매수 주문을 송신하고 `broker_order_id` 를 기록한다(옵션 A 실행 큐, 멱등 가드로 중복 디스패치 방지). -- FR-3: 집행 루프가 `poll_fill` 로 체결을 **비동기 관측**(paper·KIS 동일 경로)해, 매수 체결 시 `pending_buy→holding`(avg_price/qty 반영), 거부/취소 시 `pending_buy→idle` 롤백을 코어가 단독 수행한다. -- FR-4: `pending_sell` 슬롯을 claim 해 매도 주문 송신, `poll_fill` 비동기 체결 관측으로 체결 시 `pending_sell→closed`(recovery 시 재진입 +1)로 진행한다. -- FR-5: 주문 핸드오프가 durable/멱등하다(옵션 A) — pending 슬롯 + `broker_order_id` 가 큐+멱등 가드이며, 재기동 시 `broker_order_id` 있는 pending 슬롯을 브로커 주문 상태와 대조하는 reconciliation 으로 미체결/중복을 정리한다(중복 주문 0). 별도 outbox 테이블은 두지 않는다. -- FR-6: baseline 문서(runtime-flows/interface-contract/project-structure/data-model)를 집행/브로커 레이어에 맞게 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 불변식 B — 브로커 세션/주문 송신은 코어 집행 경로에서만. 도메인/전략은 브로커 미접근. -- NFR-2: 도메인 순수성 유지 — `trex_core.domain.slot` 은 브로커/네트워크 의존 0(어댑터 격리, import 스윕). -- NFR-3: 멱등/내구성 — 동일 슬롯/주문에 대해 중복 주문이 발생하지 않으며, 크래시 재개 시 정확히 1회 의미를 보존한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 브로커 포트 + paper/stub 어댑터(불변식 B), 주문 집행 루프, 체결/거부 처리(슬롯 전이), durable 핸드오프 + reconciliation, baseline 갱신. -- In Scope (추가, OQ-1 결정) - - 실제 KIS Open API 어댑터(주문 송신/취소/체결 조회), KIS 모의투자 서버 검증, 세션/토큰 관리·레이트리밋. -- Out of Scope (후속 Discovery) - - safety floor(forced_close/EOD/손절 backstop, liveness gating, sell_evaluation_log) — 별도 Discovery(브로커 집행 위에 얹음). - - KIS **실계좌 라이브 go-live**(실자금 거래) — 어댑터는 구현하되 라이브 전환은 config 게이팅된 운영 결정으로 분리. - - 매수·매도 전략 데몬, API 인증/인가, 수평 확장. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 실주문 중복/유실 — 크래시 타이밍에 따라 중복 주문 또는 미체결 방치 위험. - - 완화 방안(OQ-3 결정=옵션 A): `broker_order_id` 멱등 가드(채워지면 재디스패치 안 함) + 재기동 reconciliation(broker_order_id 있는 pending 슬롯을 브로커 상태와 대조, FR-5). paper async 더블로 크래시 시나리오 테스트. -- R-2 (High): KIS 비동기 체결 — 주문 송신과 체결이 분리(체결 조회/폴링)되어 부분체결·지연·실패 처리 복잡. - - 완화 방안(OQ-2 결정): paper·KIS 통일 비동기 체결 계약(submit→poll). paper async 더블이 지연/부분/거부 체결을 **결정적으로 재현**해 운영과 동일 경로를 검증 + KIS 모의투자로 실 API 체결 확인 + 멱등 전이. -- R-5 (High): KIS 실 API — 자격증명/토큰 만료·레이트리밋·API 변경·실자금 손실 위험. - - 완화 방안: 슬라이스는 KIS **모의투자 서버**로만 검증, 실계좌 라이브는 config 게이팅 분리(Out of Scope). 자격증명은 .env/시크릿(REQ-010 패턴), 토큰 갱신·레이트리밋 어댑터 내 처리. -- R-3 (Medium): 도메인 순수성/단일 writer 침식 — 집행 컴포넌트가 broker+slot 을 함께 다뤄 경계가 흐려질 위험. - - 완화 방안: broker 는 adapters, 슬롯 쓰기는 SlotService 단일 경로(NFR-1/2), import 스윕 가드. 집행 루프(OQ-4)는 코어 내 단일 주체로 단일 writer/세션 유지(같은 프로세스 백그라운드). -- R-4 (Medium): 실거래 안전 — 잘못된 집행이 금전 손실. - - 완화 방안: 슬라이스 1 비실거래(paper), 안전 기본값, safety floor 는 후속이지만 본 설계가 그 hook 을 남긴다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 코어는 단일 인스턴스로 `daily_slot` 단일 writer + 단일 브로커 세션을 소유한다(수평 확장 비대상). -- A-2: 집행·체결의 결정적 검증은 paper/stub 로, 실 API 검증은 KIS **모의투자 서버**로 수행한다. 실계좌 라이브(실자금)는 본 슬라이스 비대상(config 게이팅 운영 결정). -- A-3: safety floor 는 본 Discovery 가 아니라 후속에서 다루되, 본 집행 설계가 그 hook(강제청산 소비 지점)을 남긴다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 브로커 어댑터 범위 — paper/stub 만(슬라이스 1) vs 실제 증권사(예: KIS) 어댑터까지. - - 상태: Answered (2026-06-04) - - 결정: **실제 KIS(한국투자증권) Open API 어댑터까지 구현.** paper/stub(결정적 테스트)와 KIS 어댑터를 모두 제공하고, KIS 는 **모의투자 서버**로 슬라이스 검증, 실계좌 라이브 go-live 는 config 게이팅으로 분리(Out of Scope). 근거: 실제 거래 능력 확보가 목표이고 KIS 모의투자로 안전하게 실 API 를 검증 가능. - - 반영 위치: `# 4`(코드 영향), `# 5` FR-1·In Scope·Out of Scope, `# 6` R-2/R-4·A-2. OQ-2(체결 모델)는 KIS 비동기 체결을 반드시 포함하도록 파급. -- OQ-2: 체결 처리 모델 — paper 도 KIS 와 동일한 **비동기 체결 구조**로 통일할지(검증 정확도). - - 상태: Answered (2026-06-04) - - 결정: **단일 비동기 체결 계약으로 통일.** `BrokerPort` 는 `submit_order`(→ accepted + broker_order_id) 와 `poll_fill`(체결 관측)로 분리하고, 집행 루프가 폴링으로 체결을 반영한다. paper/stub 은 즉시체결이 가능하더라도 **동기로 단축하지 않고** 동일한 submit→poll 구조를 따르되, 체결 시점·부분체결·거부를 **프로그래밍 가능한 결정적 더블**로 제공한다. KIS 는 체결 조회 API 폴링. 근거: 테스트(paper)와 운영(KIS)이 **같은 집행 코드 경로**를 타야 검증이 정확하고, paper 의 결정적 제어가 즉시체결보다 엣지 케이스(지연/부분/거부) 검증력이 높다. - - 반영 위치: `# 5` FR-1(paper 도 async 더블)·FR-3·FR-4, `# 6` R-2 완화, `# 7` S-1/S-2/S-6. 집행 루프 폴링 → OQ-4(별도 execution 루프) 방향 강화. -- OQ-3: 주문 핸드오프 durability 형태 — pending 슬롯 자체를 실행 큐로(orbit `claim ... FOR UPDATE SKIP LOCKED`) vs 별도 order outbox 테이블 vs intent_log 의 outbox 승격. - - 상태: Answered (2026-06-04) - - 결정: **옵션 A — pending 슬롯 자체가 실행 큐.** 별도 테이블 없이 `daily_slot.status IN (pending_buy,pending_sell) AND broker_order_id IS NULL` 을 `FOR UPDATE SKIP LOCKED` 로 claim, 주문 송신 후 `broker_order_id` 기록(멱등 가드 — 채워지면 재디스패치 안 함). reconciliation 은 `broker_order_id` 있는 pending 슬롯을 브로커 상태와 대조. dcy-002 `intent_log` 는 dedup 용도로 유지(outbox 승격 안 함, 옵션 C 지양). 부분체결/재시도가 본격화되면 별도 order 테이블(옵션 B)로 승격하는 hook 을 남긴다. 근거: 단순·orbit 검증·슬롯 단일 진실, 한 슬롯=한 주문 모델에 충분. - - 반영 위치: `# 4`(변경 항목), `# 5` FR-2·FR-5, `# 6` R-1 완화. 별도 outbox 테이블 미신설. -- OQ-4: 집행 트리거 — 별도 execution 워커 루프(주기적 claim) vs intent 수락 직후 인라인 디스패치. - - 상태: Answered (2026-06-04) - - 결정: **별도 execution 루프(데몬 프로세스 내 백그라운드).** 루프가 ① claim→주문 송신→broker_order_id 기록 ② poll_fill→체결 전이 ③ reconcile 를 주기적으로 수행한다. intent API 는 슬롯 pending 전이까지만(빠른 응답), 집행은 루프가 비동기. 인라인 nudge 는 선택적 후속 최적화로 보류. 근거: OQ-2(폴링 체결)·OQ-3(claim 큐+reconciliation)가 지속 집행 주체를 요구 → 루프가 정합. 단일 writer/세션은 같은 프로세스 내 단일 루프로 유지. - - 반영 위치: `# 4`(변경 항목), `# 5` FR-2~FR-5(집행 루프 주체), `# 6` R-3. 폴링 주기·배치 크기는 config(구현 세부). - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: paper 브로커로, 매수 intent → pending_buy → 집행 주문 송신(accepted) → **폴링 체결** → `holding` 까지 코어가 자동 진행한다. -- S-2: 매도 intent → pending_sell → 집행 → 폴링 체결 → `closed` + 재진입 +1 까지 자동 진행한다. -- S-3: 동일 슬롯에 대해 집행이 두 번 돌아도 중복 주문이 생기지 않는다(broker_order_id 멱등). -- S-4: 주문 송신 직후 크래시 시뮬레이션 후 재기동 → reconciliation 으로 중복 주문 없이 일관 상태로 수렴. -- S-5: 도메인 import 스윕에서 브로커/네트워크 의존이 0 으로 유지된다(불변식 B/순수성). -- S-6: paper async 더블이 비동기 체결(즉시/지연/부분/거부)을 결정적으로 재현하고, 동일 집행 경로가 KIS 모의투자에서도 동작한다(검증 정확도). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1/S-2 측정 방식: paper 브로커 통합테스트(전체 lifecycle) / 데이터 출처: 테스트 결과, 슬롯 상태·transition_log. -- S-3 측정 방식: 집행 중복 호출 테스트(같은 슬롯) / 데이터 출처: 브로커 주문 호출 수, broker_order_id. -- S-4 측정 방식: 크래시 주입 후 reconciliation 테스트 / 데이터 출처: 주문 수·슬롯 상태 일관성. -- S-5 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록. -- S-6 측정 방식: paper 더블의 fill 시나리오(즉시/지연/부분/거부) 파라미터화 테스트 + KIS 모의투자 lifecycle / 데이터 출처: 테스트 결과, 체결 로그. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — 특히 safety floor·실 브로커 제외 - - 기준: 운영자가 슬라이스 경계를 확인했다. → confirm-discovery 에서 문서대로 확정(2026-06-04). -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7). -- [x] High 영향 리스크(R-1 중복주문, R-2 KIS 체결, R-5 KIS 실 API) 처리 방침 확정 - - 기준: paper/stub + KIS 모의투자 검증 + reconciliation + 라이브 게이팅으로 수용. → 운영자 수용(2026-06-04). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (OQ-1~OQ-4 전부 Answered → `Open` 0개) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. → Confirmed By: Justin. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md -- 참조 문서: - - docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md (슬롯 코어 데몬·intent·intent_log — 본 Discovery 가 집행을 잇는다) - - docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, docs/data-model.md (baseline — 본 Discovery 가 갱신 요구) - - trex-orbit `orbit-v2_plan.md` (불변식 B·Order Execution·forced_close 소비자 설계 참고) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~브로커 어댑터 범위~~ → 결정됨: paper/stub + **실제 KIS 어댑터**(모의투자 검증, 실계좌 go-live 는 게이팅) (OQ-1, 2026-06-04) -- ~~주문 핸드오프 durability~~ → 결정됨: **옵션 A(pending 슬롯=실행큐 + broker_order_id 멱등)**, 별도 테이블 미신설, B 승격 hook (OQ-3, 2026-06-04) - -## CONFIRM -- ~~체결 모델~~ → 결정됨: paper·KIS 통일 **비동기 체결**(submit→poll), paper 는 결정적 async 더블 (OQ-2, 2026-06-04) -- ~~집행 트리거~~ → 결정됨: **별도 execution 루프**(데몬 내 백그라운드), 인라인 nudge 보류 (OQ-4, 2026-06-04) -- ~~범위 경계(deferral) + High 리스크 수용~~ → 확정됨: safety floor·KIS 실계좌 라이브·전략 데몬·인증을 후속으로 미루고 슬라이스(브로커 포트+paper/stub+KIS 모의투자+집행 루프+비동기 체결+reconciliation) 승인. R-1/R-2/R-5 완화책(멱등+reconciliation, paper async 더블+KIS 모의투자, 실계좌 게이팅) 수용 (confirm-discovery, 2026-06-04) - -## DATA -- 없음 (별도 외부 데이터 요청 없음) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~OQ-4 전부 Answered(KIS 어댑터까지 / 통일 비동기 체결 / 옵션 A 실행큐 / 별도 execution 루프). 범위 경계(safety floor·KIS 실계좌·전략 데몬·인증 후속 Deferred)와 High 리스크(R-1/R-2/R-5) 완화책을 confirm-discovery 에서 운영자 확정·수용, CONFIRMED_BY 지정 완료. REQ Drafting 으로 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-04 diff --git a/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md b/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md deleted file mode 100644 index 30c23de..0000000 --- a/docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md +++ /dev/null @@ -1,214 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-004_20260604_portfolio-hub-ledger-integration -- 이슈명: portfolio-hub ledger 연동 — 체결을 외부 원장에 기록 -- 작성 시각(KST): 2026-06-04 -- 마지막 갱신 시각(KST): 2026-06-04 22:46 -- 대체됨: 없음 -- 후속 Discovery 참조: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md (read 방향 후속) -- 생성된 REQ 참조: REQ-017, REQ-018, REQ-019, REQ-020 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: trex-orbit `app/clients/portfolio_client.py`(`post_ledger_trade` 등), orbit BAT-006/REQ-025(체결→portfolio-hub ledger write-back) -- 해석: - - BAT-004 로 체결까지 코어가 수행하지만, 체결을 **외부 회계/포지션 원장(portfolio-hub)에 기록하지 않는다**. 슬롯이 `portfolio_id` 를 운반하는데 소비처가 없다. - - 이번 Discovery 는 체결 시 portfolio-hub ledger 에 거래를 기록하는 통합을 더해 운영 회계 정확성을 확보한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-001~003 어디에도 portfolio-hub ledger 연동이 In Scope 가 아니었다(dcy-003 미명시 deferral). - - 기존 REQ 본문 수정이 아니라 신규 외부 통합(원장 기록)이라 `change-req` 가 아니라 새 Discovery 다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 코어 집행 루프(BAT-004)는 체결 시 슬롯을 `holding`/`closed` 로만 전이한다. portfolio-hub 등 **외부 원장에 거래를 기록하지 않는다**(상태탐색: portfolio/ledger 모듈 0). - - 슬롯 계약에 `portfolio_id` 가 있으나(dcy-001 부록 A, intent API 수신) **소비처가 없다**(배선만 됨). - - orbit 에는 `PortfolioHubClient.post_ledger_trade`(`POST /v1/assets/{asset_id}/ledger/trades`, `execution_id` 멱등, 409 `LedgerConflictError`)가 있어 체결을 원장에 기록한다. -- 기대 상태 - - 체결(매수/매도) 시 코어가 portfolio-hub ledger 에 거래(asset/side/qty/price/fee/tax/executed_at)를 **멱등하게 기록**한다. - - portfolio-hub 일시 장애에도 원장 기록이 유실되지 않게 처리한다(재시도/보류). -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - portfolio-hub(외부 회계/포지션 서비스) 및 그 데이터 소비자 -- 제약사항 - - 도메인 순수성 — `trex_core.domain.slot` 은 portfolio-hub/네트워크 의존 0(어댑터 격리). - - 멱등 — 동일 체결(execution_id)이 원장에 중복 기록되지 않는다. - - 단일 기록 주체 — 코어 집행 경로만 원장에 기록(전략/도메인 미접근). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 매수 체결(`pending_buy→holding`) 시 코어가 portfolio-hub ledger 에 buy 거래를 기록 → 외부 포지션/잔고 반영. -- 매도 체결(`pending_sell→closed`) 시 sell 거래 기록 → 실현 손익/잔고 반영. -- portfolio-hub 가 잠시 불가 → 코어가 재시도/보류로 원장 기록을 결국 반영(유실 없음), 슬롯 전이는 별도 보장. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- LedgerPort + portfolio-hub 어댑터: `LedgerPort.record_trade(...)` + `PortfolioHubLedger`(orbit `post_ledger_trade` 패턴) + 결정적 테스트용 stub/in-memory 원장. -- 체결→원장 기록: 집행 루프의 fill 전이 시 거래를 원장에 기록(buy/sell), `execution_id` 멱등. -- 내구성: portfolio-hub 장애 시 재시도/보류(원장 기록 유실 방지) — 방식은 OQ. -- baseline 갱신: runtime-flows(원장 기록 흐름)·interface-contract(LedgerPort)·project-structure(ledger 어댑터)·data-model(원장 기록/필드). - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규: `trex_core/` 하위 LedgerPort + portfolio-hub 어댑터 + stub, 집행 루프 fill 경로에 ledger 기록 hook. config(portfolio-hub URL/토큰). - - 기존: ExecutionService fill 전이에 원장 기록 단계 추가. -- 문서 영향: - - runtime-flows/interface-contract/project-structure/data-model 갱신. -- 운영 영향: - - 신규 외부 의존(portfolio-hub) + 자격증명(.env). 장애 시 재시도/보류 운영 절차. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `LedgerPort.record_trade(asset, order_id, execution_id, side, qty, price, fee, tax, executed_at)` 추상화 + portfolio-hub 어댑터(`POST /v1/assets/{asset_id}/ledger/trades`) + 결정적 검증용 stub/in-memory 원장을 제공한다. -- FR-2: 집행 루프가 매수 체결(`pending_buy→holding`)·매도 체결(`pending_sell→closed`) 시 해당 거래를 원장에 기록한다. ledger path 의 `asset_id` 는 슬롯 `portfolio_id`(=asset_id, OQ-1 결정)를 사용하고 side/qty/price/체결데이터를 함께 보낸다. -- FR-3: 원장 기록은 correlation_id(`{asset_id}:{order_id}:{execution_id}`)로 멱등하다 — 동일 체결 재기록 시 로컬 dedup + portfolio-hub 중복/409(LedgerConflict)를 안전 처리하고 중복 원장 0. execution_id 는 결정적 합성(OQ-3). -- FR-4: 체결 시 원장 기록을 ledger outbox(work item)에 적재하고 재시도 루프가 portfolio-hub write 를 수행한다(orbit LedgerWriter 패턴). outcome = completed/duplicate_completed/retryable_failure/conflict_blocked. 슬롯 전이와 분리되어 일시 장애에도 유실 없이 결국 기록(conflict 는 운영자 해소). -- FR-5: baseline 문서(runtime-flows/interface-contract/project-structure/data-model)를 ledger 레이어에 맞게 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 도메인 순수성 유지 — `trex_core.domain.slot` 은 portfolio-hub/네트워크 의존 0(어댑터 격리, import 스윕). -- NFR-2: 멱등/무유실 — 동일 execution_id 중복 기록 없음 + 장애 시 결국 기록(at-least-once + dedup). -- NFR-3: 단일 기록 주체 — 코어 집행 경로만 원장에 기록. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - LedgerPort + portfolio-hub 어댑터(post_ledger_trade) + stub, 체결→원장 기록(buy/sell), execution_id 멱등, 장애 재시도/보류, baseline 갱신. -- Out of Scope (후속 Discovery) - - `post_portfolio_transaction`(입출금 등 거래 외 트랜잭션), asset master 동기화·get_assets/get_portfolios 연동. - - portfolio-hub ↔ 코어 양방향 정합(reconciliation), 포지션 재구성. - - safety floor, 매수·매도 전략 데몬, 실계좌 go-live(타 Discovery). - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 원장 기록 유실/중복 — 체결은 됐는데 원장 미기록(장애) 또는 중복 기록 시 회계 부정확. - - 완화 방안(OQ-2/OQ-3): correlation_id 멱등(409/duplicate 안전) + ledger outbox 재시도 루프(무유실) + conflict_blocked 운영자 해소. stub 으로 결정적 테스트. -- R-2 (해소, OQ-1): asset_id 해석 — orbit 근거로 `asset_id == portfolio_id` 확정. 슬롯 `portfolio_id` 를 path 로 직접 사용(추가 매핑/조회 없음). -- R-3 (Medium): 외부 의존 결합 — portfolio-hub 장애가 집행 경로를 막을 위험. - - 완화 방안: 원장 기록을 슬롯 전이와 분리(전이는 우선, 기록은 재시도/보류). 어댑터 격리(NFR-1). -- R-4 (해소, OQ-3): fee/tax/execution_id 출처 — execution_id 결정적 합성(KIS 규칙/paper synth), fee 는 BrokerPort 계산(orbit calculate_fee), tax 기본 0(정밀 모델 후속). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: portfolio-hub 는 orbit 과 동일 계약(`POST /v1/assets/{asset_id}/ledger/trades`, execution_id 멱등, 409 conflict)을 제공한다. -- A-2: 코어는 단일 인스턴스로 원장 기록의 단일 주체다. -- A-3: 자격증명(URL/토큰)은 .env(REQ-010 패턴)로 주입한다(orbit `TREX_PORTFOLIO_HUB_*`). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: asset_id 해석 — portfolio-hub `{asset_id}` 를 슬롯의 무엇으로 채울지(`portfolio_id` == asset_id? symbol→asset_id 매핑? portfolio-hub get_assets 조회?). - - 상태: Answered (2026-06-04) - - 결정: **`asset_id == portfolio_id`** — 슬롯의 `portfolio_id` 를 ledger path param 으로 직접 사용. symbol 매핑/get_assets 조회 불필요. 근거: trex-orbit `app/domain/ledger.py`("asset_id == portfolio_id for this system"), migration 003(Order Execution 이 `{portfolio_id}` path 로 write-back, 매수 커밋 시 슬롯에 캐시), `coordinator.py`(`asset_id = asset_id or portfolio_id`). trex-core 는 이미 buy intent 가 `portfolio_id` 를 공급해 슬롯에 캐시하므로 추가 조회 없이 그대로 사용. - - 반영 위치: `# 5` FR-1·FR-2, `# 6` R-2(해소). buy intent 의 `portfolio_id` 필수 여부는 구현 시 결정(없으면 기록 skip 또는 거부). -- OQ-2: 기록 내구성 방식 — fill 전이와 동일 경로 동기 기록 vs 별도 ledger outbox 비동기. - - 상태: Answered (2026-06-04) - - 결정: **별도 ledger outbox(work item) + 재시도 루프** (orbit `LedgerWriter`/recovery_work_item 패턴 차용). 체결 시 ledger 기록을 outbox 에 적재(슬롯 전이와 분리 → 전이는 우선, 기록은 eventually-consistent), 루프가 portfolio-hub write 시도. correlation_id = `{asset_id}:{order_id}:{execution_id}` 로 dedup, outcome 어휘 = completed / duplicate_completed / retryable_failure(재시도) / conflict_blocked(409, 운영자 해소). 근거: orbit `app/domain/ledger.py` 가 동일 패턴으로 무유실+멱등을 보장. - - 반영 위치: `# 4`, `# 5` FR-3·FR-4, `# 6` R-1. (BAT-004 의 옵션 B outbox 와 유사 구조 — ledger 전용.) -- OQ-3: 체결 데이터(execution_id/fee/tax) 출처 — KIS 체결조회/주문응답에서 추출 가능 범위, paper 더블의 합성 값. - - 상태: Answered (2026-06-04) - - 결정: **execution_id 는 결정적 합성** — KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`(orbit kis.py 규칙 차용), paper: 결정적 synth(예: `paper:{broker_order_id}:{filled_qty}`). **fee 는 BrokerPort fee 계산**(orbit `calculate_fee(side, amount)` 차용), **tax 는 기본 0**(정밀 세금 모델은 후속). FillStatus/브로커가 execution_id·fee 를 운반하도록 contract 확장. - - 반영 위치: `# 5` FR-1·FR-3, `# 6` R-4. (BrokerPort 에 fee 계산 + FillStatus.execution_id 추가는 구현 세부.) -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: stub 원장으로, 매수 체결 시 buy 거래가 원장에 1건 기록되고 슬롯이 holding 으로 전이된다. -- S-2: 매도 체결 시 sell 거래가 기록되고 슬롯이 closed 로 전이된다. -- S-3: 동일 execution_id 재기록 시 원장에 중복이 생기지 않는다(멱등). -- S-4: 원장 기록이 일시 실패해도 재시도/보류로 결국 기록되고, 슬롯 전이 일관성이 유지된다. -- S-5: 도메인 import 스윕에서 portfolio-hub/네트워크 의존이 0 으로 유지된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1/S-2 측정 방식: stub 원장 통합테스트(체결 lifecycle) / 데이터 출처: 테스트 결과, 원장 기록 목록·슬롯 상태. -- S-3 측정 방식: 동일 execution_id 재기록 테스트 / 데이터 출처: 원장 엔트리 수. -- S-4 측정 방식: 원장 실패 주입 후 재시도 테스트 / 데이터 출처: 기록 결과·슬롯 상태. -- S-5 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — 특히 거래 외 트랜잭션/asset 동기화 제외 - - 기준: 운영자가 슬라이스 경계를 확인했다. (운영자 확정 2026-06-04 — 문서대로 수용) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 측정 방식·출처 명시(본 문서 # 7). -- [x] High 영향 리스크(R-1 원장 유실/중복) 처리 방침 확정 - - 기준: execution_id 멱등 + 재시도/보류로 수용 가능한지 운영자 확인. (운영자 확정 2026-06-04 — 멱등+outbox 재시도 수용) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (OQ-1~OQ-3 전부 Answered → `Open` 0개) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. (Confirmed By = Justin Ko) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md -- 참조 문서: - - docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md (집행/체결 — 본 Discovery 가 체결에 원장 기록을 더한다) - - docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, docs/data-model.md (baseline — 본 Discovery 가 갱신 요구) - - trex-orbit `app/clients/portfolio_client.py` (post_ledger_trade 계약 참고) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -- ~~asset_id 소스~~ → 결정됨: `asset_id == portfolio_id`(슬롯 portfolio_id 직접 사용, orbit 근거) (OQ-1, 2026-06-04) -- ~~기록 내구성~~ → 결정됨: 별도 ledger outbox + 재시도 루프(orbit LedgerWriter 패턴), correlation_id dedup, outcome 어휘 (OQ-2, 2026-06-04) - -## CONFIRM -- ~~체결 데이터 출처~~ → 결정됨: execution_id 결정적 합성(KIS 규칙/paper) + fee BrokerPort 계산 + tax 0 (OQ-3, 2026-06-04) -- ~~범위 경계 확정~~ → 결정됨: 거래 외 트랜잭션(`post_portfolio_transaction`)·asset 동기화·reconciliation 을 후속 Discovery 로 미루는 In/Out Scope 경계를 문서대로 확정 (운영자 Justin Ko, 2026-06-04) - -## DATA -- ~~portfolio-hub ledger API 계약/자격증명~~ → REQ/delivery 단계 계측·확정으로 위임(비차단): 설계는 orbit `post_ledger_trade` 검증 패턴 + stub/in-memory 결정론 테스트(S-1~S-5)로 REQ 초안 작성에 충분하며, 실제 계약/자격증명(orbit 동일 가정 A-1) 검증은 REQ acceptance/verification evidence 로 수집. (운영자 Justin Ko, 2026-06-04) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~OQ-3(asset_id 소스·기록 내구성·체결 데이터 출처) 전부 Answered, 범위 경계·R-1 처리 방침 운영자 확정(문서대로). portfolio-hub ledger API 계약/자격증명(DATA)은 orbit 동일 가정 + stub 결정론 검증으로 REQ 초안 작성을 막지 않아 REQ/delivery 계측 evidence 로 위임(비차단). 저장소 사실 관계는 현재 상태와 일치(ledger 모듈 0, portfolio_id 배선만 됨, BAT-004 fill 전이 확인). REQ Drafting 으로 인계한다. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-04 21:47 diff --git a/docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md b/docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md deleted file mode 100644 index 1bb8270..0000000 --- a/docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md +++ /dev/null @@ -1,333 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-005_20260604_portfolio-hub-asset-snapshot-api -- 이슈명: portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공 -- 작성 시각(KST): 2026-06-04 22:46 -- 마지막 갱신 시각(KST): 2026-06-06 00:20 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-027, REQ-028, REQ-029, REQ-030, REQ-031, REQ-032, REQ-033 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: dcy-004(portfolio-hub ledger write 통합, BAT-006 진행 중)의 Out of Scope `asset master 동기화·get_assets/get_portfolios 연동`. orbit `app/clients/portfolio_client.py`(read 계약 get_assets/get_portfolios 패턴). **dcy-006(confirmed) — trex-core ↔ 전략 모듈 통신 transport 를 RabbitMQ 로 확정(HTTP 표면 0). 본 Discovery 의 스냅샷 read 는 그 transport(RabbitMQ request-reply RPC) 위에서 전달된다.** -- 해석: - - dcy-004 는 체결을 portfolio-hub 원장에 기록하는 **write 방향**이었고, 본 Discovery 는 그 반대인 **read 방향** — portfolio-hub 종목 스냅샷을 trex-core 가 전략 모듈에 제공한다. dcy-004 가 명시적으로 후속으로 미룬 영역을 이어받는다. - - **transport/계약 분리**: 본 Discovery 는 스냅샷의 **logical 계약**(응답 단위·필드·마스킹·view 모드·strategy_key 필터·신선도)을 정의하며 **transport-무관**이다. 전달 방식(HTTP 아님 — dcy-006 의 RabbitMQ RPC)은 dcy-006 소관이고, 본 문서는 그 transport 위에 logical 계약을 얹는다. - - active-cycle 판정(BAT-006 draft 진행 중)이지만 명시적 신규 입력이고, BAT-006(write)과 read 방향이 분리되어 범위 충돌이 없으므로 병렬 독립 주기로 진행한다. core 경유의 핵심 가치는 단순 패스스루가 아니라 **core 의 권위 슬롯 상태를 겹쳐(slot overlay) 전략 실행 대상 자산을 선별(pending·warning·pause 마스킹)** 해 제공하는 것이다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-004 는 write 방향(원장 기록)만 In Scope 였고 read 방향(get_assets/get_portfolios)은 명시적으로 Out of Scope(후속)였다. 본 Discovery 는 그 후속 read 통합이다. - - 기존 REQ 본문 수정이 아니라 신규 외부 read 통합 + 신규 read 계약(스냅샷 응답)이라 `change-req` 가 아니라 새 Discovery 다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - trex-core 의 read 경로는 슬롯/capacity 조회와 intent 조회뿐이고 portfolio-hub 종목을 노출하는 read 경로가 없다. (dcy-006 구현으로 통신 transport 가 RabbitMQ 로 전환됨 — `adapters/api` HTTP 어댑터 제거, `adapters/messaging`(rabbitmq/topology) 추가, daemon 이 `trex.snapshot.req`/`trex.intent.req` RPC consumer 운영. 따라서 본 스냅샷 read 는 그 snapshot RPC 위에 portfolio-hub 종목 logical 계약을 얹는다 — portfolio-hub read 포트/어댑터는 아직 없다.) - - portfolio-hub read 어댑터(get_assets/get_portfolios)가 trex-core 에 없다. dcy-004/BAT-006 은 write 방향 `LedgerPort` + portfolio-hub 어댑터만 추가하며, read 포트는 다루지 않는다. - - 결과적으로 전략 모듈이 "무엇을 매매할지" 정할 portfolio-hub 종목 정보를 trex-core 를 통해 받을 경로가 없다. -- 기대 상태 - - trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env)에 일일 스케줄로 portfolio-hub 에서 자신의 `daemon_id` universe(= `managed_by_daemon_id` 가 해당 daemon 인 portfolio 집합)를 가져와 스냅샷으로 보유한다. universe 항목은 portfolio 단위로 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 **가격(price)은 제외**한다. - - 전략 모듈 요청 시 trex-core 가 이 스냅샷을 기반으로 **portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 담되 pending·warning·pause 상태 자산은 마스킹한 전략 실행 대상 자산 리스트**를 반환한다(OQ-4). - - 응답은 요청 `view` 파라미터(`all` 기본 | `buyable` | `held`)로 base 집합을 고르고 envelope(`assets[]` + `available_slots`·`degraded` 등 meta)로 반환한다(OQ-11/OQ-12/OQ-6). 가용 슬롯이 0 이어도 보유 자산은 계속 반환되며 신규 매수 가용성은 `available_slots` meta 로 신호한다. -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - 전략 모듈(매수/매도/position, trex-core 외부 소비자) 및 portfolio-hub(외부 자산/포지션 서비스) -- 제약사항 - - 도메인 순수성 — `trex_core.domain` 은 portfolio-hub/네트워크 의존 0(read 어댑터 격리). - - 단일 통합 경계 — portfolio-hub 자격증명/계약 접점은 trex-core 어댑터(dcy-004 재사용)로 한정하며, 매수 전략 등 모든 소비자는 core 스냅샷 계약을 경유한다(portfolio-hub 직접 호출 금지, OQ-3). - - core 는 슬롯/capacity 의 권위 단일 writer(불변식 A) — slot overlay 는 이 권위 상태를 사용한다. - - transport 종속성 — 스냅샷 read 의 전달은 dcy-006(confirmed)의 RabbitMQ request-reply RPC 를 사용한다(HTTP 아님). 본 Discovery 는 logical 계약만 정의하고 transport 세부(토폴로지·delivery)는 dcy-006 소관이다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략 모듈이 자기 portfolio(전략 실행 대상 자산)를 trex-core 스냅샷 API 로 요청 → portfolio-hub 보유 자산(holding 슬롯 자산 포함)을 받되, pending·warning·pause 상태 자산은 마스킹되어 빠진 리스트를 받는다. -- 모든 슬롯이 차 있어 더 살 수 없는 상황 → 보유 자산은 계속 응답에 포함되고(`view=all`/`held`), 신규 매수 가능 여부는 `available_slots` meta 로 신호한다. `view=buyable` 은 `available_slots=0` 이면 `[]` 를 반환한다(OQ-11/OQ-12). -- 막 매수 주문되어 슬롯이 pending(미체결)인 종목 → 마스킹되어 전략 모듈 응답에서 빠져 중복 매수를 막고, 체결되어 holding 이 되면 보유 자산으로 응답에 포함된다. -- 스냅샷 페치 시 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 어긋난 자산 → `warning` 으로 표시되어 그 자산은 응답 리스트에서 마스킹되고 매매 전략이 수행되지 않는다. -- 전략 모듈이 자기 `strategy_key` 를 파라미터로 넣어 포트폴리오를 요청 → 그 `strategy_key` 가 등록된 자산만 받는다. 파라미터를 생략하면 전체 자산을 받는다(여러 전략 모듈이 하나의 스냅샷 API 를 공유하되 자기 책임 자산만 조회). -- 운영자가 특정 자산을 런타임에 pause → 전략 모듈이 포트폴리오를 요청해도 그 자산은 응답에서 마스킹되어 매매가 멈추고, resume 하면 다시 응답에 포함된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- portfolio-hub read 포트 + 어댑터: `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 로 daemon universe 조회, dcy-004 어댑터/자격증명 재사용) + 결정론 stub. 전략파라미터는 trex-core 가 해석하지 않는 opaque JSON 으로 운반한다. -- 일일 스냅샷 갱신: 설정 시각(기본 07:30 KST, .env)에 스케줄러(신규 daemon 컴포넌트)가 universe 를 페치해 스냅샷을 갱신한다. 현금/포지션은 baseline 에 더해, 체결 시 portfolio-hub ledger write 응답이 반환하는 변경 후 권위 cash/position 으로 갱신한다(옵션 D — 당초 옵션 C 의 core 로컬 체결 replay 보정을 대체). -- 포지션 정합 검증 + warning: 페치 시 portfolio-hub 포지션 vs 실제 브로커 계좌 포지션을 대조하고, 불일치 자산은 `warning` 상태로 응답 리스트에서 마스킹한다. -- 종목 스냅샷 read 계약: trex-core 가 보유 스냅샷을 read 계약(요청-응답, transport 는 dcy-006 RabbitMQ RPC)으로 제공하고, **holding 포함·pending·warning·pause 마스킹**한 전략 실행 대상 자산 리스트를 반환한다(slot overlay, OQ-4). -- strategy_key 필터: 스냅샷 read 요청이 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2)를 받아, 주어지면 해당 `strategy_key` 가 등록된(= `active_strategy_id` 일치) 자산만 반환하고, 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. `strategy_key` 는 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 단일 `active_strategy_id` 정확 일치(OQ-9). -- 런타임 pause/resume: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있고, pause 된 항목은 전략 모듈의 포트폴리오 요청 결과에서 제외한다. pause 단위·제어 방식·영속성은 OQ-10. -- 응답 view 모드 + envelope: 응답 base 집합을 요청 `view`(`all` 기본|`buyable`|`held`)로 선택하고 envelope(`assets[]` + `view`·`available_slots`·`degraded`·`reason` meta)로 반환한다(OQ-11/OQ-12/OQ-6). capacity 0 이어도 보유 자산은 반환, `buyable` 만 `available_slots=0` 시 `[]`. -- baseline 갱신: interface-contract(read 포트/메시지 계약)·runtime-flows(스냅샷 조회 흐름, RabbitMQ RPC)·project-structure(read 어댑터) 갱신. (dcy-006 의 transport baseline 갱신과 정합.) - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규: `trex_core/application` read 포트, 신규 `adapters/portfolio` read 어댑터 + stub, 스냅샷 read 를 dcy-006 messaging transport(RabbitMQ RPC, `adapters/messaging`)로 노출 + 응답 스키마(transport-무관), slot overlay(점유 종목 제외) + 현금/포지션 write-응답 갱신 로직(`cash_balance_after`/`asset_quantity_after`, 옵션 D), 일일 스냅샷 스케줄러(런타임 신규), 포지션 정합 검증 + `warning` 상태, `config` 에 스냅샷 페치 시각 주입(`daemon_id` 는 portfolio-hub 토큰에서 도출 — 별도 config 불필요, OQ-7), `strategy_key` 필터(요청 payload → `active_strategy_id` 매칭), 스냅샷 항목 pause/resume 상태 저장(Postgres, portfolio_id 키, refresh 무관) + 제어 표면(CLI + dcy-006 RPC 제어 메시지) + 응답 산출 시 pause 마스킹(전역). - - (dcy-006 구현 완료: `adapters/api` 제거·`adapters/messaging` 추가·`trex.snapshot.req` RPC consumer 존재. 스냅샷 read 는 이 RPC consumer 위에 portfolio-hub 종목 logical 계약을 채워 구현한다.) - - 기존: capacity/slot 조회 재사용(점유 종목·가용 슬롯 판정), 브로커 계좌 포지션 조회(BrokerPort 잔고조회 = KIS 주식잔고조회 `TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`, symbol 집계 — 정합 검증용, OQ-8) 재사용/확장. -- 문서 영향: - - interface-contract/runtime-flows/project-structure 갱신. -- 운영 영향: - - portfolio-hub read 의존 추가(dcy-004 자격증명 재사용). 캐시 도입 시 신선도/무효화 운영 고려. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: trex-core 가 종목 스냅샷을 read 계약(요청-응답)으로 제공한다. transport 는 dcy-006 의 RabbitMQ request-reply RPC 이며(HTTP 아님), 전략 모듈이 메시지로 소비한다. 스냅샷 단위는 daemon 이 관리하는 portfolio universe 이며, 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. (OQ-1 Answered) -- FR-2: 스냅샷 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 에 보유된 자산을 기본 포함하며, holding 슬롯의 자산도 포함**한다(전략 모듈이 보유 포지션까지 받아 관리). 단 다음 상태의 자산은 **마스킹**하여 응답에서 제외한다: (a) 슬롯 상태 pending(미체결 in-flight), (b) 자산 상태 warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 포함/마스킹 판정은 core 권위 슬롯 상태 + 정합 검증 + pause 상태를 사용하며 portfolio-hub 보유 여부보다 우선한다(slot overlay). 본 조항은 기본(default) view(`all`)의 구성 규칙이며, 응답 베이스 집합은 요청 `view` 파라미터(`all`/`buyable`/`held`, OQ-11/OQ-12)로 선택될 수 있고 마스킹은 선택된 베이스 위에 균일 적용된다. (변경 2026-06-05, OQ-4 결정: 종전 'pending·holding 점유 종목 제외, 매수 후보 리스트' → 'holding 포함, pending·warning·pause 만 마스킹'.) -- FR-3: 응답은 **envelope** 로 제공한다(자산은 항상 `assets: []` 배열, null 금지; meta: `view`·`available_slots`·`degraded`·`reason`) (OQ-6). 자산 base 집합은 요청 `view` 파라미터로 선택한다(OQ-11/OQ-12) — **`all`(기본, universe 전체) | `buyable`(universe − 슬롯 점유) | `held`(holding 슬롯만)**. 마스킹(pending·warning·pause)은 전 모드 균일 적용(OQ-4; pending 은 모든 모드 마스킹이라 `held`=holding 만). capacity 는 `available_slots` meta 로 신호하며 `buyable` 은 `available_slots=0` 이면 `[]`. (종전 'capacity 0 → 매수 후보 빈 리스트'는 OQ-4 holding 포함 + 본 결정으로 대체.) -- FR-4: portfolio-hub read 접근을 `PortfolioHubReadPort.get_portfolios(daemon_id)` 추상화 + portfolio-hub 어댑터(`managed_by_daemon_id` 기준) + 결정론 stub 로 제공하고, dcy-004 의 portfolio-hub 어댑터/자격증명을 재사용한다. `daemon_id` 는 portfolio-hub 자격증명/토큰에서 도출하며(OQ-7, 단일 진실원) 별도 config 값이 아니다(필요 시 whoami 1회 도출·캐시). 전략파라미터는 opaque JSON 으로 통과시킨다(trex-core 가 스키마를 해석하지 않음). -- FR-5: baseline 문서(interface-contract/runtime-flows/project-structure)를 read 포트·스냅샷 read 계약(RabbitMQ RPC)·read 어댑터·일일 스케줄러에 맞게 갱신한다(dcy-006 transport baseline 과 정합). -- FR-6: trex-core daemon 이 설정 가능한 시각(기본 07:30 KST, .env 로 지정)에 일일 스케줄로 `daemon_id` universe 를 페치해 스냅샷을 보유한다. universe 멤버십·전략파라미터는 일일 스냅샷으로 고정하고, 현금/포지션은 스냅샷 baseline 에 더해 **체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값으로 갱신한다(옵션 D)**. (포지션/현금의 ground truth 는 portfolio-hub 이며 응답값으로 덮어써 로컬 누적 보정 drift 를 제거. portfolio-hub 가 ledger write 를 회계 단일 권위로 단일 트랜잭션 처리하고 `*_after` 를 반환하기로 확정 — A-7, portfolio-hub#8. portfolio-hub 배포(REL-003 등) 시점까지 구현 대기.) -- FR-7: 스냅샷 페치 시 각 portfolio 의 portfolio-hub 포지션과 실제 브로커 계좌 포지션이 **수량 정확 일치**하는지 검증한다(symbol 단위 집계, 허용 오차 없음 — OQ-8). 불일치 자산은 `warning` 상태로 표시·응답 리스트에서 마스킹하고 해당 자산에 매매 전략을 수행하지 않으며, **다음 정합 성공 시 자동 해제**한다(영속 hold 는 pause/FR-9). 브로커 포지션은 KIS 주식잔고조회(`TTTC8434R`/`VTTC8434R`, `pdno`+`hldg_qty`)를 BrokerPort 로 조회한다. -- FR-8: 스냅샷 read 요청은 선택적 `strategy_key` 파라미터(요청 payload, dcy-006 OQ-2 매핑)를 받는다. `strategy_key` 가 주어지면 해당 `strategy_key` 가 등록된(= 자산의 `active_strategy_id` 와 일치) 자산만 응답 리스트에 포함하고, `strategy_key` 가 없으면 (pending·warning·pause 마스킹 후) 모든 자산을 반환한다. 여러 전략 모듈이 같은 스냅샷 API 를 공유하되 각자 자기 책임 자산만 받는다. `strategy_key` 는 사람이 읽을 수 있는 안정 유일 slug(예: `target-value-v1`)이며 자산의 단일 `active_strategy_id` 와 대소문자 구분 정확 일치로 매칭한다(OQ-9; 다중 등록은 범위 밖). `strategy_key` 필터는 `view` 파라미터와 **직교 합성**된다(OQ-12). -- FR-9: trex-core 가 보유한 스냅샷 universe 의 개별 항목을 사용자가 런타임에 일시 중단(pause)/재개(resume)할 수 있다. pause 된 항목은 전략 모듈의 포트폴리오 요청 결과(FR-2)에서 제외한다. 응답 리스트는 holding 포함을 기본으로 (pending 마스킹) ∧ (warning 마스킹) ∧ (pause 마스킹) ∧ (strategy_key 필터, 주어진 경우)의 합성으로 산출된다. pause 단위는 `portfolio_id`(=asset_id), 제어는 **CLI + dcy-006 RPC 제어 메시지**, **Postgres 영속**(재시작·일일 refresh 후 유지), **전역 적용**, resume 시 다음 요청부터 즉시 복귀(OQ-10). pause(수동·영속) vs warning(자동·일시)은 독립 overlay·OR 합성. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 도메인 순수성 유지 — read 포트는 application 계약, 어댑터는 adapters 레이어 격리. `trex_core/domain` 은 portfolio-hub/네트워크 의존 0(import 스윕). -- NFR-2: 일관성 — 점유 종목 제외/가용 슬롯 판정은 core 의 권위 슬롯 상태(강한 일관성)를 사용한다. 현금/포지션은 일일 baseline + 체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값 갱신으로 유지하고(옵션 D), 스냅샷 페치 시 브로커 계좌와 정합 검증한다(불일치 자산 `warning` 제외). -- NFR-3: 가용성 degrade — portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 **명시적 degraded 신호(reason code + sanitize 된 message 포함) + 신규 전략 매매 보수적 중단**(OQ-5; 빈 리스트/silent stale 금지). core 자율 보호 매도는 유지(ledger 는 outbox 로 결국 기록). dcy-006 OQ-4 와 공통 '보수적 안전' 철학. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - portfolio-hub read 포트 + 어댑터 + stub, 종목 스냅샷 read API, holding 포함·pending·warning·pause 마스킹(slot overlay, OQ-4), capacity 0 응답 재정의(OQ-11), 설정 시각 일일 스냅샷 + 현금/포지션 write-응답 갱신(옵션 D), 포지션 정합 검증 + `warning` 제외, baseline 갱신. -- Out of Scope (후속 Discovery) - - 매수 전략 의사결정 로직 자체(점수화·랭킹·진입 타이밍) — 전략 모듈 책임. - - portfolio-hub ↔ core 양방향 reconciliation, 포지션 재구성, asset master 영속 동기화. - - 매수/매도 전략 데몬, safety floor, 실계좌 go-live. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Medium): stale 스냅샷 — 일일 캐시한 현금/포지션이 장중에 어긋날 수 있다. - - 완화 방안(OQ-2 Answered, 옵션 D): 현금/포지션을 baseline + 체결 시 portfolio-hub ledger write 응답의 권위 cash/position 으로 갱신해 유지. daemon 이 유일 매매 주체이고 장중 입출금이 없다는 전제(A-6)로 정확. 추가로 페치 시 브로커 계좌 정합 검증(FR-7). 잔여 갭: 체결→write flush 사이에는 baseline 값이라 현금이 잠시 stale 할 수 있으나, 중복 매수 배제는 slot overlay(FR-2)가 즉시 담당. -- R-2 (Medium): in-flight race — dcy-004 ledger outbox 가 eventually-consistent 라, 막 체결한 종목이 portfolio-hub 포지션에 아직 반영되기 전 중복 노출될 수 있다. - - 완화 방안(OQ-4 Answered): pending 슬롯 마스킹으로 미체결 in-flight 종목을 응답에서 제외해 중복 노출을 차단한다. 체결 완료(holding)된 자산은 보유 자산으로 포함되며, 전략 모듈이 보유 상태를 보고 재매수하지 않는다(holding 자산의 보유 수량 표기 정합은 core 권위 슬롯 상태 기준 — 설계 시 확정). (종전 'pending·holding 동시 제외'에서 'pending 만 마스킹, holding 포함'으로 변경.) -- R-3 (Medium): 가용성 결합 — portfolio-hub 장애가 전략 read 를 막는다. - - 완화 방안(OQ-5 Answered): 권위 cash/position 유지 불가 시 명시적 degraded 신호로 신규 매매 보수적 중단(stale 매매·누적 drift 차단). core 보호 매도는 유지(outbox 로 ledger 결국 기록). 어댑터 격리(NFR-1). -- R-4 (Low): 책임 경계 확장 — core 가 portfolio-hub read gateway 로 비대해질 위험. - - 완화 방안: 모든 소비자가 core 를 경유하되(OQ-3 Answered), core 의 부가가치(slot overlay·현금/포지션 보정·정합 검증)로 범위를 한정하고 단순 패스스루 프록시는 지양한다. -- R-5 (Medium): 포지션 drift — portfolio-hub 가 실제 브로커 계좌와 어긋나면(과거 누락·정합 오류) 스냅샷 baseline 자체가 틀린다. (옵션 D 로 전환해 core 로컬 replay 누적 drift 는 제거되었으나, portfolio-hub ↔ 브로커 계좌 간 drift 는 여전히 남는다.) - - 완화 방안(FR-7, OQ-8 Answered): 스냅샷 페치 시 portfolio-hub 포지션 vs 브로커 계좌 포지션 **수량 정확 일치**(symbol 집계) 검증, 불일치 자산은 `warning` 으로 매매 제외(보수적 안전), 다음 정합 성공 시 자동 해제. 브로커 출처 = KIS 주식잔고조회. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: portfolio-hub 는 daemon 인증(`auth_tokens.daemon_id`)으로 자신이 관리하는 portfolio 집합을 조회하는 read 계약을 제공한다. (근거: portfolio-hub `app/api/v1_portfolios.py`·`v1_user_info.py` 의 `auth.daemon_id` 스코프) -- A-2: trex-core 는 슬롯/capacity 의 권위 단일 소스이며, 점유 종목·가용 슬롯 판정에 자기 상태를 사용할 수 있다. -- A-3: 전략 모듈은 trex-core 외부 소비자이며 dcy-006 transport(RabbitMQ request-reply RPC)로 스냅샷을 받는다(HTTP 아님). logical 계약(필드/마스킹/view 모드/strategy_key)은 transport-무관. -- A-4: dcy-004 의 portfolio-hub 어댑터/자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 read 방향에서도 재사용한다. -- A-5: portfolio-hub 의 `daemon` 은 1급 엔티티이며 portfolio 가 `managed_by_daemon_id` 로 daemon 에 귀속된다(1 daemon : N portfolio, 각 portfolio ≈ 1 asset, asset_id == portfolio_id). 전략파라미터는 portfolio-hub 가 `strategy_params` JSONB 로 보관한다. trex-core daemon 의 universe = 자신이 관리하는 portfolio 집합이다. (근거: portfolio-hub `app/domain/models.py` Daemon/Portfolio, `strategy_params` JSONB migration) -- A-6: 이 trex-core daemon 은 자신이 관리하는 portfolio 의 유일한 매매 주체이며, 장중 거래 외 현금 변동(입출금)은 없다. 따라서 일일 baseline + 체결 시 portfolio-hub write 응답 갱신으로 현금/포지션을 정확히 유지할 수 있다(옵션 D 전제). (사용자 확인 2026-06-04) -- A-7 (확정 2026-06-05, portfolio-hub#8): portfolio-hub 가 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 응답 root 에 flat 필드 **`cash_balance_after`, `asset_quantity_after`** 2개를 비파괴 추가하기로 회신. 더 나아가 이 endpoint 를 회계 단일 권위로 만들어 **ledger row INSERT + portfolio cash/quantity UPDATE 를 단일 DB 트랜잭션(row lock)** 으로 처리한다 → trex-core 는 write 1회로 회계 완결, drift 원천 차단, read round-trip 불요. status 별: `new`(201) = 본 거래 반영 후 권위 값, `duplicate`(200) = 현재 권위 값(idempotent 정합), `409`/잔고부족 `422` = `*_after` 미정의(본문 미정). trex-core 는 이 응답값으로 스냅샷 현금/포지션을 갱신한다(옵션 D). 의존: portfolio-hub 측은 별도 Discovery(dcy-006)·release(REL-003 등)로 배포 예정 — **배포 시점까지 trex-core 구현은 대기**. 추적: https://github.com/fourblocks2020/portfolio-hub/issues/8 . (사용자 결정 2026-06-05 / portfolio-hub 회신 반영) - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 스냅샷의 정의 — 응답 단위와 필드. - - 상태: Answered (2026-06-04) - - 결정: 스냅샷 = daemon 이 관리하는 portfolio universe. 항목당 symbol, asset_id(=portfolio_id), 현금(cash_balance), 포지션(asset_quantity), active_strategy_id, 전략파라미터(opaque JSON). 가격(price)은 사용하지 않으므로 제외. 전략파라미터는 portfolio-hub 가 `strategy_params` JSONB 로 보관하므로 trex-core 는 해석 없이 opaque JSON 으로 통과시킨다. - - 반영 위치: `# 3` 기대 상태, `# 5` FR-1·FR-4, `# 6` A-5. -- OQ-2: 신선도/캐시 정책 — universe 페치 주기와 현금/포지션 신선도. - - 상태: Answered (2026-06-04) - - 결정(개정 2026-06-05, 옵션 C → 옵션 D): universe 멤버십·전략파라미터는 설정 시각(기본 07:30 KST, .env) 일일 스냅샷으로 고정. 현금/포지션은 스냅샷 baseline 에 더해 **체결 시 portfolio-hub ledger write 요청의 응답이 반환하는 변경 후 권위 cash/position 으로 갱신**한다(옵션 D). 근거: portfolio-hub 가 cash/position 의 ground truth 이므로 write 응답값으로 덮어쓰면 옵션 C 의 core 로컬 체결 replay 누적 보정에서 생길 수 있던 drift(R-5)가 발생하지 않는다. 갱신이 체결(write) 경로에서만 일어나므로 read 경로의 portfolio-hub 가용성 결합(옵션 B 의 매 요청 live 호출)도 피한다. A-6(유일 매매 주체·장중 외부 변동 없음) 전제는 유지. 의존(확정 2026-06-05, portfolio-hub#8): portfolio-hub 가 ledger write 응답에 `cash_balance_after` / `asset_quantity_after` 를 반환하고 ledger INSERT + 잔고 UPDATE 를 단일 트랜잭션으로 처리하기로 회신 — trex-core 는 write 응답값으로 스냅샷을 갱신한다. portfolio-hub 배포(dcy-006/REL-003) 시점까지 구현 대기. 체결→write flush 사이 즉시 갭의 중복 매수 배제는 slot overlay(FR-2)가 계속 담당. - - 반영 위치: `# 4`, `# 5` FR-6·NFR-2·In Scope, `# 6` R-1·R-5·A-6·A-7. -- OQ-3: core 경유 정책 — 모든 소비자가 trex-core 를 경유하는가, 일부는 portfolio-hub 직접 호출을 허용하는가? - - 상태: Answered (2026-06-04) - - 결정: 매수 전략 모듈 등 **모든 소비자는 trex-core 스냅샷 API 를 경유**한다. portfolio-hub 직접 호출은 허용하지 않는다. core 가 단일 통합 경계(자격증명·계약·slot overlay·현금/포지션 보정·정합 검증)를 일관 제공하고, 소비자는 portfolio-hub 계약/자격증명을 알 필요가 없다. - - 반영 위치: `# 3` 제약사항, `# 6` R-4 완화. -- OQ-4: in-flight 합성 규칙 — portfolio-hub 포지션과 core 슬롯 상태를 합쳐 응답을 만들 때의 포함/마스킹 규칙. - - 상태: Answered (2026-06-05) - - 결정: 응답(전략 실행 대상 portfolio 자산 리스트)은 **portfolio-hub 보유 자산을 기본 포함하고 holding 슬롯 자산도 포함**한다. 다음 상태만 **마스킹**(제외): (a) 슬롯 pending(미체결 in-flight), (b) warning(브로커 실제 position 불일치, FR-7), (c) 사용자 pause(FR-9). 우선순위: core 권위 슬롯 상태 + 정합 검증 + pause 가 portfolio-hub 보유 여부보다 우선(마스킹 조건 충족 시 보유 여부와 무관하게 제외). in-flight race(R-2)는 pending 마스킹으로 차단; 체결 완료분은 holding 으로 포함. (종전 'pending·holding 동시 제외' → 'holding 포함, pending·warning·pause 마스킹'으로 변경.) - - 반영 위치: `# 3` 시나리오, `# 5` FR-2·FR-9, `# 6` R-2, `# 7` S-2. - - 잔여: holding 자산의 보유 수량 표기를 core 권위 슬롯 vs 스냅샷 중 무엇 기준으로 할지(holding→ledger flush 갭의 수량 정합)는 설계 시 확정. 종전 FR-3(가용 슬롯 0 → 빈 리스트)은 'holding 포함' 변경과 충돌 → OQ-11 로 재정의. -- OQ-5: 가용성 degrade 정책 — portfolio-hub 불가 시 스냅샷 read 동작. - - 상태: Answered (2026-06-05) - - 결정: portfolio-hub 가 불가해 **권위 cash/position 을 유지할 수 없는 상태**(옵션 D 의 ledger write `*_after` 갱신 불가, 또는 일일 universe fetch 실패)면 **신규 전략 매매를 보수적으로 중단**한다. 차단 사유는 'ledger 기록 불가'가 아니라(dcy-004 outbox 가 유실 없이 결국 기록) **stale cash/position 으로 매매 시 잘못된 사이징·누적 drift 위험**이다. - - read 응답: 빈 리스트(`[]` — '후보 없음'과 모호)나 silent stale 캐시 서빙 대신 **명시적 degraded 신호**를 실어 전략이 신규 매매를 보류하게 한다. 신호는 `degraded=true` + **degraded 사유**를 포함한다 — machine-readable reason code(예: `portfolio_hub_unavailable` / `snapshot_fetch_failed` / `cash_position_stale`)로 전략이 원인별 분기 가능하게 하고, 사람용 message 로 운영 진단을 돕는다. 단 raw 예외/자격증명 등 민감정보는 노출하지 않고 sanitize 한다. - - core 자율 보호 매도(손절·시간 만기, dcy-006 A-5)는 **계속 동작** — 그 체결의 ledger 도 outbox 가 복구 후 기록하므로 outage 중에도 열린 포지션은 보호된다. 즉 '중단'은 신규 전략 매매에 한정한다. - - dcy-006 OQ-4(RabbitMQ degrade)와 **공통 '보수적 안전' 철학**: 임의의 핵심 의존성(portfolio-hub OR RabbitMQ) degrade 시 신규 매매 중단 + core 보호 매도 유지. - - 반영 위치: `# 5` NFR-3, `# 6` R-3. - - 종료 조건(충족): degrade 동작이 NFR-3/R-3 에 반영됨. -- OQ-6: 빈 리스트 표현 시맨틱 — 응답이 비었을 때의 표현. - - 상태: Answered (2026-06-05) - - 결정: 응답은 **envelope** 로 감싸고 자산 목록은 항상 배열(`assets: []`, **null 금지**)로 준다. 빈 배열은 '항목 없음'을 그대로 의미하고, '왜 비었는지'는 envelope 의 meta 로 구분한다 — `view`(에코된 view 모드, OQ-11/12), `available_slots`(capacity 신호), `degraded`+`reason`(OQ-5). 근거: 리스트 필드에 null 을 섞으면 소비자 분기·버그 유발 → 항상 배열. 빈 리스트만으로는 capacity 0 / 전부 마스킹 / 필터 0매칭 / degrade 를 구분 못 하므로 envelope 필요(OQ-5 가 이미 `degraded`/`reason` 을 요구해 envelope 는 사실상 강제). transport 가 메시지 payload(dcy-006 RPC)라 REST status-code 가 아니라 구조화 envelope 가 자연스럽다. - - 반영 위치: `# 5` FR-3 (자산 항목(FR-1)·마스킹(FR-2)은 envelope 의 `assets[]` 원소로 위치). - - 종료 조건(충족): 표현 방식(envelope + `[]`)이 FR-3 에 반영됨. 정확한 envelope 필드 스키마는 OQ-12 view 파라미터와 함께 REQ/설계 단계 확정. -- OQ-7: trex-core daemon 의 `daemon_id` 출처. - - 상태: Answered (2026-06-05) - - 결정: `daemon_id` 는 **portfolio-hub 자격증명/인증 토큰(`auth_tokens.daemon_id`)에서 도출**한다(단일 진실원). 별도 `TREX_DAEMON_ID` .env 설정값을 1차 출처로 두지 않는다 — 자격증명과 분리 설정 시 dual-source drift(잘못된 universe·auth 오류) 위험. portfolio-hub read 는 토큰으로 daemon 을 스코프하므로(A-1) trex-core 는 daemon_id 를 명시적으로 들 필요가 가급적 없고, 값이 필요하면 startup 때 whoami(portfolio-hub `v1_user_info` 류) 1회로 도출·캐시한다. .env override 는 테스트/stub 한정이며 startup 에서 토큰 daemon_id 와 일치 검증(불일치 시 fail-closed)한다. dcy-004 의 `TREX_PORTFOLIO_HUB_*` 자격증명을 재사용하므로 신규 `TREX_DAEMON_ID` 주입은 불필요. - - 반영 위치: `# 4` 영향 범위(config), `# 5` FR-4. - - 종료 조건(충족): daemon_id 출처가 FR-4/영향 범위에 반영됨. -- OQ-8: 포지션 정합 검증 세부. - - 상태: Answered (2026-06-05) - - 결정: - 1. **일치 기준 = 수량 정확 일치**(정수 shares, 허용 오차 없음). A-6(유일 매매 주체·장중 외부 변동 없음)상 portfolio-hub 포지션과 브로커 잔고는 동일 체결에서 파생되어 정확히 일치해야 하며, tolerance 는 실제 이상(누락 체결·외부 거래)을 가린다. 현금은 FR-7 범위 밖(수수료·세금·T+2 결제로 단순 비교 불가) — 수량만 대조. - 2. **warning 회복 = 다음 정합 성공 시 자동 해제**(매 정합 사이클 재계산되는 파생 플래그, sticky 아님). raise 는 매번 로깅/alert. 영속 hold 가 필요하면 운영자 pause(FR-9)로 분리한다(자동 warning=일시 / 수동 pause=영속). - 3. **브로커 포지션 출처 = KIS 주식잔고조회**(실전 `TTTC8434R` / 모의 `VTTC8434R`, `GET /uapi/domestic-stock/v1/trading/inquire-balance`)를 BrokerPort 잔고조회로 재사용. 항목당 `pdno`(종목코드) + `hldg_qty`(보유수량). paper broker 는 결정론 stub. - - **symbol 단위 집계 대조**: 브로커는 단일 계좌(dcy-003 불변식 B)라 잔고가 symbol 단위 집계 → portfolio-hub per-portfolio 포지션을 symbol 로 합산해 broker per-symbol `hldg_qty` 와 대조(보통 1 portfolio:1 symbol 이나 안전하게 집계 기준). - - 주의: 당일 전량매도분이 `hldg_qty=0` 으로 D-2 까지 잔고에 남을 수 있음(015 문서) → 0 수량 잔고 처리 규칙은 설계 시 확정. - - 반영 위치: `# 4` 영향 범위, `# 5` FR-7, `# 6` R-5. - - 참고: docs/broker/kis/rest/국내주식/주문/계좌/015_v1_국내주식-006_주식잔고조회.md - - 종료 조건(충족): 정합 기준·회복 규칙·포지션 출처가 FR-7 에 반영됨. -- OQ-9: 전략 ID 필터의 식별자/등록 시맨틱. - - 상태: Answered (2026-06-05) - - 결정: - - **식별자 형태**: 전략 모듈이 넘기는 `strategy_key` 는 **사람이 읽을 수 있으면서 ID 역할을 하는 유일·안정 값**(human-readable stable slug, 예: `target-value-v1`)이다. 'display name vs opaque ID' 이원화 없이 이 slug 자체가 유일 키다. 매칭은 대소문자 구분 정확 일치(exact). - - **명명(2026-06-05)**: 파라미터명을 `strategy_id` → **`strategy_key`** 로 변경한다. portfolio-hub 의 `strategy_id`(그쪽 내부 전략 엔티티 ID)와 이름이 겹쳐 혼동되므로 분리한 것이다. `strategy_key` 는 trex-core 요청 파라미터(전략 모듈 자기 식별자)이며, 그 **값은 자산의 portfolio-hub `active_strategy_id` 와 매칭**된다. - - **등록 판정**: 자산의 **단일 `active_strategy_id` 와 동등 비교**(`strategy_key == active_strategy_id`). 한 자산은 한 시점에 하나의 active 전략에 귀속한다(portfolio-hub `active_strategy_id` 단일, A-5). 자산당 다중 전략 등록(예: buy/sell 분리, trex-core#1)은 **현재 범위 밖**이며 필요해지면 '등록 목록' 필드로 확장(후속). - - 반영 위치: `# 5` FR-8, `# 3` 시나리오. - - 종료 조건(충족): 식별자 형태·등록 판정이 FR-8 에 반영됨. -- OQ-10: 런타임 pause/resume 메커니즘. - - 상태: Answered (2026-06-05) - - 결정: - - **단위**: `portfolio_id`(= asset_id) — universe 항목 키. (symbol 은 다중 portfolio 공유 시 모호하므로 portfolio_id 기준.) - - **제어 방식**: 운영자 admin 액션으로 **CLI + dcy-006 RPC 제어 메시지** 둘 다 제공(로컬 CLI + 원격 ops). config 리로드는 비채택. - - **영속성**: **Postgres 영속**(기존 단일 store), portfolio_id 키. daemon 재시작 후 유지, 일일 스냅샷 refresh 와 무관(refresh 는 universe/포지션만 갱신, pause overlay 불변). - - **적용 범위**: **전역** — 모든 전략 요청에서 마스킹(운영자 안전 액션, 어느 전략이 묻든 거래 금지). - - **resume**: pause 는 request-time mask 라 resume 시 **다음 요청부터 즉시 응답 복귀**(일일 갱신 대기 불요). - - pause(수동·영속) vs warning(자동·일시, OQ-8)은 독립 overlay 이며 OR 합성(둘 중 하나라도면 마스킹). - - 반영 위치: `# 4` 영향 범위, `# 5` FR-9. - - 종료 조건(충족): pause 단위·제어 방식·영속성·적용 범위가 FR-9 에 반영됨. -- OQ-11: capacity 소진 시 응답 시맨틱. - - 상태: Answered (2026-06-05) — 방향 확정, 파라미터 설계는 OQ-12 로 분리. - - 결정: capacity(가용 슬롯)가 모두 소진됐을 때의 응답은 **고정값이 아니라 전략 모듈이 요청 시 지정하는 view 모드(파라미터)에 따라 달라진다.** 최소 3개 모드: (a) **빈 리스트 `[]`** (신규 매수 후보가 목적인 전략 — 살 게 없음), (b) **슬롯 점유 자산만** (보유 포지션 관리·매도/조절 전략 — 자기가 들고 있는 것만), (c) **portfolio 스냅샷 − 슬롯 점유 자산** (점유 안 된 잔여 universe). 같은 view 모드 개념은 capacity 0 이 아닐 때도 응답 구성을 좌우한다(이 파라미터는 strategy_key 필터(FR-8)와 별개의 두 번째 요청 파라미터). (→ OQ-12 에서 enum `all`/`buyable`/`held` 으로 확정.) - - 반영 위치: `# 5` FR-2·FR-3, `# 6` OQ-12. - - 잔여(→ OQ-12): 파라미터 이름/enum 값, 기본 모드(생략 시), 각 모드와 마스킹(pending·warning·pause, OQ-4)·strategy_key 필터의 합성 규칙, capacity 신호 메타 필드(OQ-6) 동반 여부. -- OQ-12: 응답 view/scope 파라미터 설계. - - 상태: Answered (2026-06-05) - - 결정: - - **파라미터**: 요청 payload `view`(dcy-006 OQ-2 매핑), enum = **`all` | `buyable` | `held`**. base 집합 — `all` = universe 전체(portfolio-hub 보유 + holding 슬롯), `buyable` = universe − 슬롯 점유(미점유 신규 진입 후보), `held` = holding 슬롯 자산만. (1차안의 `remainder` 는 `buyable` 과 동일이라 drop.) - - **기본 모드**: 생략 시 **`all`**(OQ-4/FR-2 holding 포함·FR-8 전체와 정합). - - **마스킹 직교·균일**: 모드는 base 집합만 고르고, 마스킹(pending·warning·pause)은 전 모드 균일 적용. **pending 은 모든 모드 마스킹** → `held` = holding 만(미체결 제외). warning/pause 도 전 모드 마스킹. - - **strategy_key 직교**: `view` 와 `strategy_key`(FR-8)는 독립 합성 — `assets = base(view) − mask(pending·warning·pause) − (strategy_key 필터, 주어지면)`. - - **capacity**: envelope 에 `available_slots` 항상 포함(OQ-6). `buyable` 은 capacity-gated — `available_slots=0` 이면 `[]`(OQ-11 정합). `held` 엔 정보성. - - **전략 타입 매핑(trex-core#1)**: `buyable`=매수, `held`=position/매도, `all`=통합. - - 반영 위치: `# 5` FR-2·FR-3·FR-8. - - 종료 조건(충족): 파라미터 스펙·기본 모드·합성 규칙이 FR-2/FR-3/FR-8 에 반영됨. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: stub portfolio-hub read 로, 스냅샷 API 가 portfolio-hub 종목을 포함한 전략 실행 대상 자산 리스트를 반환한다. -- S-2: holding 슬롯 자산은 응답에 포함되고, pending 슬롯 자산은 마스킹되어 제외된다(slot overlay). -- S-3: 가용 슬롯 0 일 때 `view=all`/`held` 는 보유 자산을 계속 반환하고 `view=buyable` 은 `[]` 를 반환하며, `available_slots=0` meta 가 신호된다(OQ-11/OQ-12). -- S-4: 도메인 import 스윕에서 portfolio-hub/네트워크 의존이 0 으로 유지된다. -- S-5: interface-contract/runtime-flows/project-structure 가 read 레이어와 정합한다. -- S-6: 스냅샷 페치 시 portfolio-hub 포지션과 브로커 계좌 포지션이 불일치하는 자산이 `warning` 으로 표시되고 응답 리스트에서 마스킹된다. -- S-7: `strategy_key` 를 넣어 호출하면 해당 `strategy_key` 가 등록된 자산만, 생략하면 전체 자산이 반환된다. -- S-8: 자산을 런타임에 pause 하면 응답 리스트에서 마스킹되고, resume 하면 다시 포함된다. -- S-9: portfolio-hub 가 불가해 권위 cash/position 유지가 불가하면 응답에 `degraded=true` + reason code 가 실려 신규 매매 보류를 신호한다(OQ-5). -- S-10: 요청 `view`(`all`/`buyable`/`held`)별로 base 집합이 다르고 마스킹(pending·warning·pause)은 전 모드 균일 적용된다(OQ-12). -- S-11: 응답이 `assets`(항상 배열, 비면 `[]`·null 아님) + meta(`view`·`available_slots`·`degraded`·`reason`) envelope 형태다(OQ-6). -- S-12: 별도 `TREX_DAEMON_ID` 없이 portfolio-hub 토큰에서 도출한 daemon_id 로 universe 를 조회한다(OQ-7). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: stub read 통합테스트(스냅샷 응답) / 데이터 출처: 테스트 결과, 응답 본문. -- S-2 측정 방식: 슬롯 holding/pending 진입 후 스냅샷 호출 테스트 / 데이터 출처: 응답 리스트(holding 포함·pending 마스킹) vs 슬롯 상태. -- S-3 측정 방식: 슬롯 소진(capacity 0) 후 스냅샷 호출 테스트 / 데이터 출처: 응답 리스트·capacity 신호 메타(OQ-11 확정안 기준). -- S-4 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록. -- S-5 측정 방식: 문서-코드 정합 점검(리뷰) / 데이터 출처: baseline 문서 vs 실제 메시지 계약(RPC)/read 포트. -- S-6 측정 방식: 포지션 불일치 주입 후 스냅샷 페치 테스트 / 데이터 출처: 자산 `warning` 상태·응답 리스트. -- S-7 측정 방식: `strategy_key` 유/무 호출 비교 테스트 / 데이터 출처: 응답 리스트 vs 자산 `active_strategy_id`. -- S-8 측정 방식: pause→호출→resume→호출 시퀀스 테스트 / 데이터 출처: 응답 리스트 포함/제외 여부. -- S-9 측정 방식: portfolio-hub 불가(권위 유지 불가) 주입 후 호출 테스트 / 데이터 출처: 응답 `degraded`·`reason` 필드. -- S-10 측정 방식: `view` 값별 호출 비교 테스트 / 데이터 출처: 응답 base 집합 vs 슬롯 상태·마스킹. -- S-11 측정 방식: 응답 스키마 검증(빈 응답 포함) / 데이터 출처: 응답 envelope 구조(`assets` 타입·meta 필드). -- S-12 측정 방식: `TREX_DAEMON_ID` 미설정 기동 후 universe 조회 테스트 / 데이터 출처: 토큰 도출 daemon_id vs 조회된 portfolio 집합. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — 특히 전략 의사결정 로직·reconciliation 제외 - - 기준: 운영자가 read 슬라이스 경계를 확인했다. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-12 측정 방식·출처 명시(본 문서 # 7). -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 본 Discovery 의 리스크는 현재 전부 Medium/Low. 신선도(R-1)·race(R-2)·가용성(R-3) 처리 방침을 운영자가 확인. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-12 이 전부 Answered/Deferred 처리(현재 OQ-1~OQ-12 전부 Answered). -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- 참조 문서: - - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md (write 방향 — 본 Discovery 가 read 방향으로 이어받음) - - docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md (confirmed — 스냅샷 read 의 전달 transport(RabbitMQ RPC, HTTP 0). 본 Discovery 는 logical 계약, dcy-006 은 transport) - - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md (baseline — 본 Discovery 가 갱신 요구) - - trex-orbit `app/clients/portfolio_client.py` (get_assets/get_portfolios read 계약 참고) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~스냅샷 응답 단위/필드~~ → 결정됨: daemon 관리 portfolio universe(symbol/asset_id/현금/포지션/active_strategy_id/전략파라미터 opaque JSON), 가격 제외 (OQ-1, 2026-06-04) -- ~~신선도 정책~~ → 결정됨: universe·전략파라미터 설정 시각 일일 캐시 + 현금/포지션 baseline + 체결 시 portfolio-hub write 응답의 권위 cash/position 갱신(옵션 D) (OQ-2, 2026-06-04 / 옵션 C→D 개정 2026-06-05) -- ~~daemon_id 주입~~ → 결정됨: portfolio-hub 자격증명/토큰에서 도출(단일 진실원), 별도 `TREX_DAEMON_ID` 불필요; .env override 는 테스트 한정 + 토큰 일치 fail-closed (OQ-7, 2026-06-05) -- ~~빈 표현 시맨틱~~ → 결정됨: envelope + `assets: []`(null 금지), meta(`view`·`available_slots`·`degraded`·`reason`)로 의미 분리 (OQ-6, 2026-06-05) -- ~~capacity 0 시 응답 재정의~~ → 결정됨: 응답 형태를 전략 모듈 요청 view 모드 파라미터로 선택(빈 `[]` / 슬롯 점유 자산만 / portfolio−점유) (OQ-11, 2026-06-05) -- ~~응답 view/scope 파라미터~~ → 결정됨: `view`=`all`(기본)|`buyable`|`held`, strategy_key 직교 합성, 마스킹 전 모드 균일(`held`=holding만), `available_slots` 메타(buyable은 0이면 `[]`) (OQ-12, 2026-06-05) -- ~~전략 ID 시맨틱~~ → 결정됨: 사람이 읽는 안정 유일 slug(예: `target-value-v1`), 자산의 단일 `active_strategy_id` 와 정확 일치; 다중 등록은 범위 밖 (OQ-9, 2026-06-05) -- ~~pause/resume 메커니즘~~ → 결정됨: portfolio_id 단위, CLI + dcy-006 RPC 제어, Postgres 영속(refresh 무관), 전역, 즉시 복귀 (OQ-10, 2026-06-05) - -## CONFIRM -- ~~read 슬라이스 범위 경계~~ → 확정됨(2026-06-06, Justin): In Scope(portfolio-hub read 포트/어댑터·스냅샷 read 계약·마스킹·view 모드·옵션 D·정합 검증·baseline) / Out of Scope(전략 의사결정 로직·reconciliation·safety floor) 확인 -- ~~core 경유 정책~~ → 결정됨: 모든 소비자 trex-core 경유, portfolio-hub 직접 호출 금지 (OQ-3, 2026-06-04) -- ~~in-flight 합성/제외 규칙~~ → 결정됨: holding 포함, pending·warning·pause 마스킹, core 권위 우선 (OQ-4, 2026-06-05) -- ~~가용성 degrade 정책~~ → 결정됨: 권위 cash/position 유지 불가 시 명시적 degraded 신호 + 신규 매매 보수적 중단, core 보호 매도 유지 (OQ-5, 2026-06-05). dcy-006 OQ-4 와 공통 보수적 안전. - -## DATA -- ~~portfolio-hub read 계약 확인~~ → 검증됨(2026-06-06, confirm-discovery): portfolio-hub `app/api/v1_portfolios.py` `list_portfolios_v1`(`GET`)이 `managed_by_daemon_id=auth.daemon_id` 로 daemon universe 스코프 조회를 제공하고, `PortfolioItem` 에 `cash_balance`·`asset_quantity`·`managed_by_daemon_id` 포함. 전략파라미터는 `strategy_params` JSONB(A-5). -- ~~portfolio-hub write 응답 계약 확장~~ → 확정됨(2026-06-05, portfolio-hub#8): `POST /v1/portfolios/{portfolio_id}/ledger/trades` 응답 root 에 flat 필드 `cash_balance_after`, `asset_quantity_after` 추가 + ledger INSERT/잔고 UPDATE 단일 트랜잭션(회계 단일 권위). status 별 `*_after` 의미 정의(new=반영 후, duplicate=현재 권위, 409/422=미정). portfolio-hub 측은 dcy-006/REL-003 으로 배포 예정 — 배포 시점까지 trex-core 구현 대기. 추적: https://github.com/fourblocks2020/portfolio-hub/issues/8 (옵션 D 의존, A-7) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1(스냅샷 정의)·OQ-2(신선도, 옵션 D — portfolio-hub#8 write 응답 `*_after` 확정)·OQ-3(core 경유 정책)·OQ-4(in-flight 합성 — holding 포함, pending·warning·pause 마스킹)·OQ-5(가용성 degrade — 권위 cash/position 유지 불가 시 degraded 신호+보수적 중단)·OQ-6(빈 표현 — envelope + `assets: []`/meta)·OQ-7(daemon_id — 토큰 도출 단일 진실원)·OQ-8(정합 — 수량 정확 일치·symbol 집계·자동 해제·KIS 잔고조회)·OQ-9(전략 ID — 사람이 읽는 안정 slug·단일 active 일치)·OQ-10(pause — portfolio_id·CLI+MQ·Postgres 영속·전역·즉시 복귀)·OQ-11(capacity 0 응답 — view 모드 파라미터)·OQ-12(view 파라미터 — `all`/`buyable`/`held` + envelope) **전부 Answered**. 남은 인계 전 항목은 범위 경계 확인(운영자)과 CONFIRMED_BY 지정이며, confirm-discovery 에서 확정 후 REQ Drafting 으로 인계한다. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-06 00:08 diff --git a/docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md b/docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md deleted file mode 100644 index a34f791..0000000 --- a/docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md +++ /dev/null @@ -1,279 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-006_20260605_strategy-module-rabbitmq-transport -- 이슈명: trex-core ↔ 전략 모듈 통신 transport — RabbitMQ 도입 (스냅샷 read + intent 제출 + 이벤트) -- 작성 시각(KST): 2026-06-05 21:23 -- 마지막 갱신 시각(KST): 2026-06-05 22:45 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: dcy-005(portfolio-hub 종목 스냅샷 read API — FR-1/A-3 가 HTTP pull 가정), dcy-002(slot-core 데몬 + HTTP intent API), dcy-003(주문 집행 + durable outbox/idempotency), trex-core#1(매수/매도/position 전략 taxonomy) -- 해석: - - dcy-002(intent API)와 dcy-005(스냅샷 read)는 전략 모듈과의 통신을 **HTTP API**로 가정했는데, 사용자가 전략 모듈 통신을 API endpoint 대신 **RabbitMQ(AMQP)** 로 하고자 한다. transport 선택은 스냅샷 read·intent 제출·이벤트 전반에 걸친 cross-cutting 통합 아키텍처 결정이라, read 슬라이스(dcy-005) 안에 두지 않고 **독립 Discovery로 분리**한다. - - 핵심 분리 원칙: logical 계약("무엇을 주고받나" — 스냅샷 필드/마스킹/view 모드/strategy_id 필터, intent 스키마)은 dcy-005/002/003 소관으로 **transport-무관**하게 유지하고, 본 Discovery 는 "어떻게 전달하나"(전달 방식·메시지 매핑·delivery 보증·운영)만 다룬다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - transport/messaging/RabbitMQ 를 다루는 기존 Discovery 가 없다(dcy-001~005 탐색 결과, repo 내 amqp/rabbitmq/pika 구현 0건). - - 가장 가까운 dcy-002 는 HTTP API "수립"이 주제이지 transport "선택"이 아니며, MQ 전환은 dcy-002/005 의 미세 조정이 아니라 통신 모델 자체를 바꾸는 새 통합 아키텍처 문제다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - trex-core daemon 은 FastAPI/uvicorn 기반 **HTTP `/v1` API**(`trex_core/adapters/api/app.py`)로 전략/소비자와 통신한다(`/v1/intents/buy|sell`, `/v1/slots`, `/v1/capacity`; dcy-005 스냅샷 read 는 draft). messaging/AMQP 인프라는 없다(repo grep: rabbitmq/amqp/pika 0건). - - baseline 문서가 HTTP 를 명시적 transport 로 기술한다 — `interface-contract.md`: "HTTP `/v1` API :: 슬롯 코어 데몬이 노출, 매수·매도 전략 데몬이 호출". `runtime-flows.md` 도 HTTP request/response 흐름 기준. - - 전략 모듈은 외부 소비자로서 HTTP pull 로 스냅샷을 받고 HTTP POST 로 intent 를 제출한다고 가정한다(dcy-005 A-3, dcy-002). - - adapters 레이어는 `api / broker / ledger / persistence` 로 구성되며 messaging 어댑터는 없다. -- 기대 상태 - - trex-core ↔ 전략 모듈 통신을 RabbitMQ(AMQP) 메시지 기반으로 제공한다. 스냅샷 요청과 intent admission 은 request-reply(reply-to/correlation-id), 체결/슬롯 상태 변화는 이벤트(pub/sub)로 전달한다(intent 체결 결과는 이벤트로, OQ-2). - - view 모드(dcy-005 OQ-11)·strategy_id(dcy-005 FR-8) 같은 요청 속성이 메시지 속성/routing key 로 매핑된다. - - logical 계약은 불변이고 transport 만 교체된다. HTTP `/v1` API 는 RabbitMQ 로 완전 대체되어 **HTTP 표면이 0 이 된다**(병행 없음, OQ-1; healthz 포함 제거, OQ-7). liveness 는 프로세스 감독이 담당. -- 이해관계자 - - 문서 작성자/구현자(AI agent) - - 리뷰어/승인자(운영자, genichin@gmail.com) - - 전략 모듈(trex-core 외부 소비자) 및 trex-core daemon - - 운영자(RabbitMQ 인프라 도입·운영) -- 제약사항 - - 도메인 순수성 — `trex_core.domain` 은 transport/AMQP 의존 0(어댑터 격리). - - logical 계약 보존 — 스냅샷/intent 의 의미·필드·마스킹·view 모드·스키마는 transport 와 독립이며 본 Discovery 가 바꾸지 않는다. - - core 권위 불변식 유지 — 슬롯/capacity 단일 writer(불변식 A) 는 transport 교체와 무관하게 보존된다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략 모듈이 portfolio 스냅샷을 요청 → trex-core 가 RabbitMQ reply 큐로 전략 실행 대상 자산 리스트를 응답한다(HTTP GET 대체). -- 전략 모듈이 매수/매도(향후 target) intent 를 메시지로 발행 → trex-core 가 소비해 슬롯 전이를 수행한다. -- 체결/슬롯 상태가 바뀌면 → trex-core 가 이벤트를 발행하고 전략 모듈이 구독해 즉시 반영한다(HTTP polling 불요). -- RabbitMQ 가 일시 불가 → degrade 정책(버퍼링 금지·신규 매매 보수적 중단, OQ-4)에 따라 신규 전략 매매는 멈추고, 열린 포지션은 core 자율 안전 매도가 보호한다(미구현, A-5). - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- transport 추상화 + RabbitMQ 어댑터(`pika` 동기): trex-core ↔ 전략 통신을 메시지 기반으로 제공. 스냅샷·intent admission = request-reply, 이벤트 = pub/sub 패턴 정의. 어댑터는 `adapters/messaging`(신규) 레이어로 격리하고 결정론 stub 제공. 런타임은 uvicorn 제거 후 sync+thread daemon 재구성(OQ-5). -- 메시지 계약 매핑: 기존 logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id, intent 스키마)을 메시지 payload/속성/routing key 로 1:1 의미 보존 매핑. -- delivery 신뢰성: ack/재처리, idempotency(dcy-003 의 intent correlation_id 재사용), dead-letter, 내구성 큐. 순서는 intent 단일 consumer FIFO 로 보존하고 이벤트는 순서 보증 불요(상태 운반·last-wins, OQ-3). -- HTTP 완전 제거: HTTP `/v1` API 를 RabbitMQ 로 대체(병행 없음, OQ-1)하고 **`healthz` 포함 모든 HTTP 라우트와 `adapters/api` HTTP 어댑터를 제거**(HTTP 표면 0, OQ-7). dcy-005 FR-1/A-3·dcy-002 의 HTTP 가정을 갱신. liveness 는 프로세스 감독, 앱 헬스/관측성은 로그·메트릭. -- baseline 갱신: `interface-contract.md`(메시지 계약)·`runtime-flows.md`(MQ 흐름)·`project-structure.md`(messaging 어댑터) 갱신. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - 신규: `trex_core/adapters/messaging` RabbitMQ(`pika`) 어댑터 + stub, 스레드별 connection/channel 관리, 직렬화, request-reply correlation(reply-to/correlation-id), intent RPC consumer 스레드, snapshot RPC consumer 스레드, 이벤트 publisher, 재연결/백오프, `config` 에 `TREX_RABBITMQ_URL`(.env) 주입(기존 `TREX_*` 패턴, OQ-6). uvicorn 제거로 `TREX_API_HOST`/`TREX_API_PORT` 폐기. - - 기존: **`adapters/api` HTTP 어댑터·uvicorn 제거**, daemon entrypoint 를 sync+thread 런타임으로 교체(OQ-5), application 레이어의 intent/스냅샷 진입점을 transport-무관 포트로 재배선(직접 변경 최소화). -- 문서 영향: - - interface-contract/runtime-flows/project-structure 갱신, dcy-005 FR-1/A-3·dcy-002 HTTP 가정 완화 cross-reference. -- 운영 영향: - - RabbitMQ 브로커 인프라 추가(현 스택의 redis/timescale 처럼 docker 추가 가능). exchange/queue 토폴로지, 모니터링, 자격증명/접속 운영. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: trex-core ↔ 전략 모듈 통신을 RabbitMQ(AMQP) 메시지 기반으로 제공한다(토폴로지 OQ-2). 스냅샷 요청과 **intent admission 은 request-reply**(`trex.snapshot.req`/`trex.intent.req` + RabbitMQ direct reply-to + `correlation_id`), 체결/슬롯 상태 변화는 **이벤트(topic exchange `trex.events`, pub/sub)** 로 전달한다. intent 의 체결 결과는 admission 응답이 아니라 이벤트로 전달해 현 2단계 시맨틱(admission 동기 + fill 비동기)을 보존한다. -- FR-2: 기존 logical 계약(스냅샷 필드/마스킹/view 모드(dcy-005 OQ-11)/strategy_id 필터(dcy-005 FR-8), intent 스키마(dcy-002/003))을 transport-무관으로 보존하고, 메시지 payload/속성/routing key 로 의미 손실 없이 매핑한다. 매핑 규칙(OQ-2): **요청(스냅샷·intent)의 view 모드·strategy_id 는 payload, 이벤트의 strategy_id·symbol 은 routing key**(`{event_type}.{strategy_id}.{symbol}`)로 싣는다. -- FR-3: messaging 접근을 transport 포트 추상화 + RabbitMQ 어댑터(`pika` 동기 클라이언트) + 결정론 stub 로 제공한다. 런타임은 uvicorn 제거 후 순수 sync+thread daemon(consumer/execution/ledger 스레드)으로 재구성한다(OQ-5). `trex_core/domain` 은 transport/AMQP 의존 0 을 유지한다. -- FR-4: delivery 신뢰성 — at-least-once + idempotency 로 effectively-once 를 제공한다(OQ-3). intent 는 슬롯 durable commit 후 ack + `correlation_id`(dcy-003 멱등키) dedup, durable/persistent + 단일 consumer FIFO. 이벤트는 durable exchange + per-strategy durable queue(catch-up) + 현재 상태·단조 시퀀스 운반(last-wins, 순서 보증 불요) + TTL/max-length. 발행은 publisher confirms, 실패는 dead-letter. 중복 전달에도 슬롯 단일 writer 불변식을 유지하고, 미수신/중복은 스냅샷 RPC 로 reconcile 한다. -- FR-5: HTTP `/v1` API 를 RabbitMQ 로 **완전 대체**한다(병행 없음, OQ-1; HTTP 표면 0, OQ-7). 전략-facing 통신(`intents/buy|sell`·`intents/{id}`·`slots`·`capacity`·dcy-005 스냅샷 read)을 메시지 기반으로 이전하고, **`healthz` 포함 모든 HTTP 라우트와 HTTP 어댑터를 제거**한다. liveness 는 프로세스 감독이 담당(앱 헬스/관측성은 로그·메트릭). dcy-005 FR-1/A-3·dcy-002 의 HTTP 가정을 갱신한다. -- FR-6: baseline 문서(`interface-contract.md`/`runtime-flows.md`/`project-structure.md`)를 메시지 계약·MQ 흐름·messaging 어댑터에 맞게 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 도메인 순수성 — transport/AMQP 의존은 adapters 레이어에 격리하고 `trex_core/domain` import 0(import 스윕). -- NFR-2: 신뢰성/일관성 — at-least-once 전달 + idempotency 로 effectively-once 효과를 보장한다(OQ-3, 진짜 exactly-once 는 미추구). 슬롯/capacity 권위 불변식을 보존하고, 이벤트는 advisory(push 최적화)이며 스냅샷 RPC 가 권위 reconcile 경로다. -- NFR-3: 가용성 degrade — RabbitMQ 불가 시 **버퍼링 금지 + 신규 매매 보수적 중단**(OQ-4). 단 **core 자율 안전 매도(손절·시간 만기)는 MQ·전략과 독립으로 계속**해 열린 포지션을 보호한다(이 capability 는 별도 core 기능, 현재 미구현 — A-5). dcy-005 OQ-5 와 공통 '보수적 안전' 철학을 따른다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - transport 포트 + RabbitMQ 어댑터 + stub, 스냅샷·intent admission request-reply, 이벤트 pub/sub, 메시지 계약 매핑, delivery 신뢰성, HTTP 완전 제거, baseline 갱신. -- Out of Scope (다른 Discovery 소관) - - 스냅샷/intent 의 logical 계약 자체 변경(dcy-005/002/003), 전략 의사결정 로직(전략 모듈 책임, trex-core#1), RabbitMQ 클러스터/HA 운영 세부, portfolio-hub 등 다른 외부 연동의 transport. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Low, 완화됨): 이중 transport 복잡도 — OQ-1 에서 HTTP 병행 없이 **대체**로 결정되어 계약 표류·중복 유지 비용은 대부분 해소. 단 전환기 동안 HTTP 어댑터 제거 시점과 전략 모듈 MQ 전환의 동기화가 필요(big-bang 마이그레이션 리스크는 R-5 로 분리). - - 완화 방안: 단일 logical 계약을 transport-무관으로 정의(FR-2/FR-3), HTTP 어댑터 제거를 전략 모듈 전환과 함께 단계적 계획. -- R-2 (Medium): delivery 의미 불일치 — at-least-once 재전달이 intent 를 중복 실행할 수 있다. - - 완화 방안(OQ-3 Answered): dcy-003 correlation_id idempotency dedup + 슬롯 durable commit 후 ack + dead-letter(FR-4), 슬롯 단일 writer 불변식(NFR-2). 미수신/중복 이벤트는 스냅샷 RPC reconcile. -- R-3 (Medium→High, 대체 결정으로 상향): 가용성 결합 — RabbitMQ 장애가 전략 통신을 전면 차단한다. HTTP 병행을 두지 않기로(OQ-1) 했으므로 **HTTP fallback 이 없어 단일 장애점 성격이 강해진다.** - - 완화 방안(OQ-4 Answered): 버퍼링 금지 + 신규 매매 보수적 중단. **열린 포지션은 core 자율 안전 매도(손절·시간 만기)가 MQ·전략과 독립으로 보호** → SPOF 영향이 '신규 매매 중단'으로 한정되고 포지션 방치 위험은 제거된다. 단 이 보호는 아직 미구현 safety 기능에 의존(A-5, 추후 구현). RabbitMQ durable/HA 운영도 고려. -- R-4 (Low): request-reply over MQ 복잡도 — 동기 HTTP pull 보다 reply 상관·타임아웃 처리가 필요하다. - - 완화 방안: correlation-id + reply-to 규약(OQ-2). RPC per-request 타임아웃·재요청 규약은 설계 시 표준화(전략 클라이언트 측). -- R-5 (Medium): big-bang transport 마이그레이션 — HTTP 제거와 전략 모듈 MQ 전환이 어긋나면 통신 단절. - - 완화 방안: 전환 순서/롤백 계획 수립, stub 기반 사전 검증(FR-3), 전환 중 관측성은 로그·메트릭으로 확보(OQ-7 에서 HTTP healthz 제거 결정 — probe 대체 안 함). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 전략 모듈은 RabbitMQ 클라이언트로 메시지를 발행/구독할 수 있는 외부 소비자다. -- A-2: trex-core 는 슬롯/capacity 권위 단일 소스이며 transport 교체와 무관하게 그 불변식을 유지한다. -- A-3: 스냅샷/intent 의 logical 계약(필드/마스킹/view 모드/strategy_id/스키마)은 dcy-005/002/003 에서 정의되고, 본 Discovery 는 전달 방식만 다룬다. -- A-4: RabbitMQ 브로커는 운영 환경에 도입 가능하다(현 스택에 redis/timescale 처럼 docker 로 추가 가능). -- A-5: trex-core 는 transport·전략과 독립으로 동작하는 **기본 안전 매도(손절 stop-loss, 시간 만기 forced_close) capability 를 갖는다(또는 추후 구현)**. degrade 시(RabbitMQ 불가·전략 슬롯 방치) 열린 포지션 보호의 전제다(OQ-4). 현재 미구현(`forced_close_at` 필드만 존재, stop-loss 없음)이며 구현은 별도 작업으로 추적(transport 범위 밖 core 매매 안전 기능). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: HTTP 대체 vs 병행 — RabbitMQ 가 HTTP `/v1` API 를 완전 대체하는가, 둘을 병행(이중 transport)하는가? - - 상태: Answered (2026-06-05) - - 결정: **RabbitMQ 가 HTTP `/v1` API 를 대체한다.** 이중 transport 병행은 채택하지 않는다. 전략-facing 통신(`intents/buy|sell`, `intents/{id}`, `slots`, `capacity`, dcy-005 스냅샷 read)은 전부 메시지 기반(command / request-reply / event)으로 옮긴다. - - 반영 위치: `# 3` 기대 상태, `# 4` 변경 항목, `# 5` FR-5, `# 6` R-1·R-3. - - 잔여(해소): 운영 probe/admin 표면의 '대체' 범위는 OQ-7 에서 Answered(앱 레벨 헬스 표면 제거, HTTP 표면 0). -- OQ-2: 통신 패턴별 토폴로지 — exchange/queue/routing key + reply 상관 + view 모드/strategy_id 매핑. - - 상태: Answered (2026-06-05) - - 결정: 3 패턴 토폴로지 확정. - - ① 스냅샷 RPC(request-reply): 요청 queue `trex.snapshot.req`(direct/default exchange) → trex-core 소비, 응답은 RabbitMQ direct reply-to(`amq.rabbitmq.reply-to`) + `correlation_id`. 요청 payload 에 `strategy_id`·`view` 모드. - - ② intent admission RPC(request-reply): 요청 queue `trex.intent.req` → trex-core 소비, 응답 = admission(`status`, `slot_id`, `reason`). `correlation_id` = intent 멱등키(dcy-003 `{asset_id}:{order_id}:{execution_id}`)로 RPC 상관 + dedup 일원화. **체결/종료(fill/close) 결과는 ② 응답이 아니라 ③ 이벤트로** 전달 — 현 2단계 시맨틱(admission 동기 + fill 비동기) 무손실 보존. - - ③ 이벤트 pub/sub: trex-core 가 체결·슬롯 상태 변화를 topic exchange `trex.events` 에 발행, routing key = `{event_type}.{strategy_id}.{symbol}`. 전략은 자기 queue 를 `*.{strategy_id}.#` 패턴으로 바인딩해 자기 자산 이벤트만 수신(broker단 필터). - - 매핑 원칙: view 모드·strategy_id 는 **요청(①②)에서는 payload, 이벤트(③)에서는 routing key**(요청은 라우팅 대상이 아니고 이벤트는 대상). - - 반영 위치: `# 5` FR-1·FR-2·FR-4. - - 잔여(→ 타 OQ): 이벤트 queue durability(전략 down 중 유실)·at-least-once/exactly-once → OQ-3; RPC reply 대기 클라이언트(pika/aio-pika) → OQ-5; RPC 타임아웃/degrade → OQ-4. -- OQ-3: delivery 보증 수준 — at-least-once + idempotency 로 충분한가, 추가 ordering/exactly-once 가 필요한가? - - 상태: Answered (2026-06-05, 위임 결정) - - 결정: **at-least-once + idempotency = effectively-once** 를 기준으로 한다. 분산 메시징에서 진짜 exactly-once 는 불가하므로 추구하지 않고 멱등 소비로 '사실상 한 번' 효과를 낸다. 패턴별: - - ① 스냅샷 RPC: read-only → 멱등. at-least-once, 별도 dedup 불요. 요청/응답은 transient(클라이언트 timeout 후 재요청). manual ack. - - ② intent admission RPC: at-least-once + `correlation_id`(= dcy-003 멱등키) dedup. 슬롯 전이를 Postgres 에 durable commit 한 뒤 ack → crash 로 재전달돼도 idempotency 가 중복 실행을 차단. intent 요청 queue·메시지는 durable/persistent, **단일 consumer FIFO** 로 순서 보존(trex-core 단일 writer 불변식 A 와 정합). 전역 total order 는 불요. - - ③ 이벤트 pub/sub: at-least-once + 멱등 소비. durable exchange + **per-strategy durable queue 로 전략 down 중 이벤트 catch-up**. 이벤트는 delta 가 아니라 **현재 상태 + 단조 증가 시퀀스(슬롯 version/timestamp)** 를 실어 last-wins 적용 → **순서 보증 불요**. 미수신/중복은 **스냅샷 RPC(권위 reconcile 경로)** 로 항상 복구(이벤트는 push 최적화이지 단일 진실원이 아님). - - 공통: trex-core 발행은 publisher confirms 로 broker 수락 확인. per-strategy 이벤트 queue 는 무한 적체 방지를 위해 TTL/max-length(초과분 drop-oldest, 스냅샷 reconcile 로 회복) 적용. - - 반영 위치: `# 5` FR-4·NFR-2, `# 6` R-2. - - 근거: 멱등키(dcy-003)와 스냅샷 권위 reconcile 이 이미 있어 exactly-once 인프라 비용 없이 안전. 순서는 단일 consumer FIFO + 상태 운반 이벤트로 충분. -- OQ-4: 가용성 degrade 정책 — RabbitMQ 불가 시 행동 규약. - - 상태: Answered (2026-06-05) - - 결정: - 1. **버퍼링 금지** — broker 불가 시 intent 를 로컬에 쌓아 복구 후 재생하지 않는다(지나간 결정의 지연 실행 위험 차단). HTTP fallback 은 OQ-1(HTTP 0)으로 선택지에서 제외. - 2. **보수적 중단** — broker 불가 시 신규 전략 매매 유입은 중단하고, 전략은 복구 후 신선 데이터로 재판단한다. 스냅샷 RPC 타임아웃 시 전략은 매매를 보류한다. - 3. **core 자율 안전 매도는 transport·전략과 독립으로 계속** — trex-core 의 기본 안전 매도(손절 stop-loss, 시간 만기 forced_close)는 RabbitMQ 가용성과 무관하게, 그리고 **전략이 특정 슬롯을 더 이상 패치/관리하지 않아도(dead-man's-switch)** 동작해야 한다. broker 장애나 전략 방치 시에도 열린 포지션은 core 가 보호한다. - - 참고(현행 코드, 정직 기록): 이 안전 매도 기능은 **아직 미구현**이다 — `forced_close_at` 은 필드만 존재(BAT-014/REQ-066 lineage)하고 자동 트리거 로직이 execution 루프에 없으며, stop-loss(손절)는 코드에 전혀 없다. **나중에 구현 예정**이며 본 문서에는 degrade 정책의 전제로 참조만 기입한다(별도 구현 Discovery 는 추후, A-5). - - 반영 위치: `# 5` NFR-3, `# 6` R-3·A-5(신규). - - 종료 조건(충족): degrade 동작·core 안전 매도 독립성 전제가 NFR-3/R-3/A-5 에 반영됨. -- OQ-5: 클라이언트/런타임 통합. - - 상태: Answered (2026-06-05) - - 결정: **`pika`(동기) + 스레드 기반 런타임**. HTTP 제거(OQ-1)로 uvicorn/FastAPI 를 들어내고, 현 sync stack(psycopg 동기·동기 서비스·`threading.Thread` execution/ledger 루프)에 정합하는 순수 sync+thread daemon 으로 재구성한다. - - consumer: intent RPC consumer 스레드(단일, prefetch=1, Postgres commit 후 ack → OQ-3 FIFO/멱등 충족) + snapshot RPC consumer 스레드. pika connection 은 thread-safe 가 아니므로 스레드별 connection/channel 또는 `add_callback_threadsafe` 규약을 따른다. - - 이벤트 publisher: execution 루프 스레드가 체결/종료 시 자기 channel 로 `trex.events` 에 발행(OQ-2/OQ-3). - - entrypoint: `uvicorn.run(app)` → connection 셋업 + consumer/execution/ledger 스레드 기동 + graceful shutdown(기존 `stop` 이벤트 패턴 확장 + connection close). - - 재연결/백오프: pika 는 자동 재연결이 없으므로 직접 구현하며 OQ-4 degrade(보수적 중단) 정책과 묶는다. - - 반영 위치: `# 4` 변경 항목·영향 범위, `# 5` FR-3. - - 종료 조건(충족): 클라이언트·런타임 통합이 FR-3/영향 범위에 반영됨. -- OQ-6: 자격증명/설정 주입 — `.env` AMQP URL 등 접속 정보 주입 패턴. - - 상태: Answered (2026-06-05) - - 결정: 기존 `TREX_*` env 주입 패턴을 재사용한다. AMQP 접속은 단일 URL env **`TREX_RABBITMQ_URL`**(예: `amqp://user:pass@host:5672/vhost`)로 받아 `DaemonConfig.from_env()` 에서 로드한다 — 기존 `TREX_KIS_REDIS_URL`(URL+자격증명을 한 env 로) 패턴과 동형. 자격증명은 URL 에 포함(redis 와 동일 방식). - - 반영 위치: `# 4` 영향 범위(config), `# 5` FR-3, `# 10` DATA. - - 참고: uvicorn 제거(OQ-5)로 기존 `TREX_API_HOST`/`TREX_API_PORT` env 는 폐기 대상이 된다. - - 종료 조건(충족): 주입 방식이 config/영향 범위에 반영됨. -- OQ-7: 운영 probe/admin 표면의 '대체' 범위 — OQ-1 의 HTTP 대체 결정이 전략-facing 통신에 한정인가, `GET /v1/healthz` 같은 앱 레벨 헬스 표면까지 제거하는가? (현 배포 환경: 오케스트레이터 없음 — daemon 은 맨 프로세스(uvicorn)로 실행, repo 에 k8s/compose/Dockerfile 없음. 따라서 '프로세스 살아있음' liveness 는 프로세스 감독(systemd/supervisor/docker restart)이 담당하며 transport 와 무관. HTTP 대체로 잃는 것은 앱 레벨 HTTP 헬스 표면뿐.) - - 상태: Answered (2026-06-05) - - 결정: **(a) 앱 레벨 헬스 표면 제거 — HTTP 표면 0.** `GET /v1/healthz` 포함 모든 HTTP 엔드포인트를 제거한다. liveness("프로세스 살아있음")는 프로세스 감독(systemd/supervisor/docker restart)이 담당하며 transport·broker 와 무관하다(broker 장애가 재시작 루프를 유발하지 않음). 앱 레벨 헬스/관측성은 로그·메트릭으로 대체한다. - - 반영 위치: `# 3` 기대 상태, `# 4` 변경 항목, `# 5` FR-5, `# 6` R-5. - - 영향: 현재 `trex_core/adapters/api/app.py` 의 `healthz` 라우트도 제거 대상에 포함된다. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: stub messaging 어댑터로 스냅샷 request-reply 가 HTTP 와 동일한 logical 응답(필드/마스킹/view 모드)을 반환한다. -- S-2: intent admission RPC 메시지가 슬롯 전이를 일으키고, 중복 전달에도 correlation_id idempotency 로 단일 실행된다. -- S-3: 체결/슬롯 상태 이벤트가 발행되고 구독자가 수신한다. -- S-4: 도메인 import 스윕에서 transport/AMQP 의존이 0 으로 유지된다. -- S-5: interface-contract/runtime-flows/project-structure 가 메시지 계약·MQ 흐름과 정합한다. -- S-6: daemon 기동 후 HTTP 표면이 0 이다(`healthz` 포함 HTTP 포트/엔드포인트 없음, OQ-1/OQ-7). -- S-7: RabbitMQ 불가 시 신규 intent 가 버퍼링되지 않고 보수적으로 중단되며, broker 복구 후에도 옛 intent 가 자동 재생되지 않는다(OQ-4). -- S-8: strategy_id 를 routing key 로 한 이벤트가 해당 전략 queue 에만 전달되고 다른 전략 queue 에는 전달되지 않는다(FR-2/OQ-2). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: stub request-reply 통합테스트 / 데이터 출처: 응답 메시지 본문 vs HTTP 응답. -- S-2 측정 방식: 중복 intent 메시지 주입 테스트 / 데이터 출처: 슬롯 상태·실행 횟수. -- S-3 측정 방식: 이벤트 발행-구독 테스트 / 데이터 출처: 구독자 수신 메시지. -- S-4 측정 방식: 정적 import 스윕 / 데이터 출처: `trex_core/domain` import 목록. -- S-5 측정 방식: 문서-코드 정합 리뷰 / 데이터 출처: baseline 문서 vs 실제 토폴로지/어댑터. -- S-6 측정 방식: 기동 후 포트/라우트 스캔 / 데이터 출처: daemon 런타임(HTTP 리스너 부재). -- S-7 측정 방식: broker 중단 주입 후 intent 발행 테스트 / 데이터 출처: 신규 슬롯 전이 없음·복구 후 재생 없음. -- S-8 측정 방식: 2개 전략 queue 바인딩 후 이벤트 발행 테스트 / 데이터 출처: 각 queue 수신 메시지. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — 특히 logical 계약 불변·전략 로직 제외 - - 기준: 운영자가 transport 범위 경계(logical 계약은 dcy-005/002/003 소관)를 확인했다. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-8 측정 방식·출처 명시(본 문서 # 7). -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: HTTP 대체 결정(OQ-1)으로 가용성 결합(R-3)이 High 로 상향(HTTP fallback 부재). delivery(R-2)·가용성(R-3)·big-bang 마이그레이션(R-5) 처리 방침을 운영자가 확인. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-7 이 전부 Answered/Deferred 처리(현재 OQ-1~OQ-7 전부 Answered). -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md -- 참조 문서: - - docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md (스냅샷 read — HTTP 가정 FR-1/A-3, view 모드/strategy_id logical 계약) - - docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md (HTTP intent API — transport 가정 출처) - - docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md (correlation_id idempotency/outbox — delivery 신뢰성 재사용) - - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md (baseline — 본 Discovery 가 갱신 요구) - - stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md (baseline 갱신 논리 템플릿) - - https://github.com/fourblocks2020/trex-core/issues/1 (전략 taxonomy — intent 종류와 연계) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~HTTP 대체 vs 병행~~ → 결정됨: RabbitMQ 가 HTTP `/v1` API 를 **대체**(병행 없음) (OQ-1, 2026-06-05). 운영 probe 잔존 범위는 OQ-7. -- ~~운영 probe/admin 범위~~ → 결정됨: 앱 레벨 헬스 표면 제거, HTTP 표면 0(`healthz` 포함). liveness=프로세스 감독 (OQ-7, 2026-06-05) -- ~~delivery 보증 수준~~ → 결정됨: at-least-once + idempotency = effectively-once. intent=durable commit 후 ack+dedup·단일 consumer FIFO, 이벤트=durable per-strategy queue+상태/시퀀스 운반(순서 불요)+스냅샷 reconcile, exactly-once 미추구 (OQ-3, 2026-06-05) -- ~~클라이언트/런타임 통합~~ → 결정됨: `pika`(동기) + 스레드 런타임, uvicorn 제거, consumer 스레드(intent prefetch=1 FIFO·snapshot), 재연결 직접 구현 (OQ-5, 2026-06-05) - -## CONFIRM -- ~~transport 범위 경계~~ → 확정됨(2026-06-05, Justin): In Scope(RabbitMQ 전환·HTTP 완전 제거·delivery 신뢰성·baseline 갱신) / Out of Scope(logical 계약 변경·전략 로직·core 안전 매도 구현·RabbitMQ HA 운영) 확인 -- ~~degrade 정책~~ → 결정됨: 버퍼링 금지 + 신규 매매 보수적 중단 + core 자율 안전 매도(손절·시간 만기)는 MQ·전략 독립 동작(미구현, 추후, A-5). dcy-005 OQ-5 와 공통 보수적 안전 (OQ-4, 2026-06-05) -- ~~통신 패턴별 토폴로지~~ → 결정됨: 스냅샷·intent admission = request-reply(direct reply-to), 이벤트 = topic exchange `trex.events`(rk `{event_type}.{strategy_id}.{symbol}`), 요청은 payload·이벤트는 routing key 매핑, intent fill 은 이벤트로 (OQ-2, 2026-06-05) - -## DATA -- DATA(비차단, REQ/delivery 단계 계측·provision): RabbitMQ 브로커 인프라 provision + 접속 자격증명 — 주입 방식은 `TREX_RABBITMQ_URL` env 로 확정(OQ-6). 실제 broker 인스턴스/계정 제공은 delivery 단계 운영 작업이며 FR/NFR·범위·성공 기준·검증 시나리오가 충분해 REQ 초안 작성을 막지 않는다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: **OQ-1~OQ-7 전부 Answered** — HTTP 대체(표면 0)·통신 토폴로지(RPC 스냅샷/intent + topic 이벤트)·delivery(effectively-once)·degrade(버퍼링 금지·보수적 중단·core 자율 안전 매도 독립)·클라이언트(pika 동기 + 스레드, uvicorn 제거)·자격증명(`TREX_RABBITMQ_URL`)까지 transport 골격 확정. 남은 인계 전 항목은 transport 범위 경계 확인(운영자)과 CONFIRMED_BY 지정뿐이다. DATA(RabbitMQ 인프라 provision)는 delivery 단계 운영 작업으로 비차단. confirm-discovery 에서 확정 후 REQ Drafting 으로 인계한다. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-05 22:30 diff --git a/docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md b/docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md deleted file mode 100644 index c921a4b..0000000 --- a/docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md +++ /dev/null @@ -1,259 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-007_20260606_core-autonomous-safety-sell -- 이슈명: core 자율 안전 매도 — stop-loss·take-profit·시간 만기 자동 트리거 -- 작성 시각(KST): 2026-06-06 -- 마지막 갱신 시각(KST): 2026-06-07 (draft-req: REQ-034~042 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-034, REQ-035, REQ-036, REQ-037, REQ-038, REQ-039, REQ-040, REQ-041, REQ-042 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 참조 파일: 없음 - - 관련 TODO/메모: 없음 -- 해석: - - REL-001 은 `forced_close_at` 필드만 존재하고 만기 트리거 로직이 없는 상태로 baseline 을 확정했다. stop-loss 는 필드·코드 모두 없다. dcy-006 A-5 와 BAT-007 R-1(High) 에서 "미구현 상태로 운영 release 전 별도 트래킹 필요"로 명시됐다. - - 이번 Discovery 는 (1) snapshot RPC 기반 per-slot 워치독으로 전략 모듈 활성 여부를 감지하고, (2) 워치독 만료 슬롯에 한해 forced_close_at 만기·stop-loss(손절)·take-profit(익절) 가격 조건을 자율 매도로 처리하며, (3) 장 마감 시각(`TREX_MARKET_CLOSE_LIQUIDATION_TIME`)에 전체 `holding` 슬롯을 청산하는 세 가지 기능의 범위·제약·오픈 질문을 정의한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-001~006 어디에도 stop-loss, forced_close_at 자동 트리거, 워치독, 장 마감 강제 청산을 다루는 Discovery 가 없다. - - dcy-006 A-5 는 "추후 구현" 가정으로 명시되었고, REL-001 excluded 로 확정됐다. - - 기존 코드에 `forced_close_at` 필드는 존재하나 감지·실행 루프가 없어 기능 구현이 신규임이 명확하다(`trex_core/domain/slot/models.py:34`). - -# 3. 문제점의 요약 - -- 현재 상태 - - 슬롯 도메인에 `forced_close_at: Optional[datetime]` 필드가 존재하고 PostgreSQL 에 영속되지만, 만기 여부를 주기적으로 검사하거나 자동 매도를 제출하는 로직이 없다(`trex_core/domain/slot/models.py:34`). - - stop-loss(손절 가격 임계값) 를 위한 필드·코드가 전혀 없다. - - 전략 모듈이 활성인지 비활성인지를 데몬이 감지하는 메커니즘이 없다. - - RabbitMQ 장애나 전략 모듈 방치 상황에서 열린 포지션이 자동으로 청산되지 않고 무기한 유지될 수 있다(BAT-007 R-1 High). - - 장 마감 후에도 `holding` 슬롯이 자동 청산되지 않는다. -- 기대 상태 - - snapshot RPC 응답 시 해당 슬롯들의 워치독 타임스탬프가 갱신되며, `TREX_WATCHDOG_TIMEOUT` 이상 갱신되지 않은 슬롯은 "전략 모듈 미감시" 상태로 판정된다. - - 워치독 만료 슬롯에 한해 `forced_close_at` 만기·stop-loss(손절)·take-profit(익절) 가격 조건 체크를 수행하고, 조건 충족 시 내부 매도 intent 를 제출한다. - - `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST) 이 설정된 경우 해당 시각에 전략 모듈 활성 여부와 무관하게 모든 `holding` 슬롯을 청산한다. - - 자동 매도 intent 는 기존 execution worker 를 통해 실행돼 outbox 내구성·슬롯 상태 전이·ledger 기록을 모두 따른다. -- 이해관계자 - - 문서 작성자(AI agent) - - 리뷰어/승인자: Justin Ko - - 운영 담당자: trex-core 데몬 운영자 - - 전략 모듈 개발자: snapshot RPC 호출 주기가 워치독 만료 임계값보다 짧아야 한다는 연동 계약 합의 필요 -- 제약사항 - - trex-core 는 sync+thread 런타임이며, 신규 trigger 루프는 기존 6 스레드 아키텍처를 과도하게 변경하지 않아야 한다. - - `forced_close_at` 필드가 이미 DB 스키마·도메인·persistence 에 존재하므로 하위호환을 유지하면서 트리거만 추가해야 한다. - - trex-core#3(강제 청산/kill-switch) 는 별도 Discovery 로 분리됨(OQ-2 Answered). 이번 Discovery 는 자동 조건 트리거(시간·가격·장 마감)만 다룬다. - - 워치독은 in-memory 이며 데몬 재시작 시 초기화된다. 재시작 후 `TREX_WATCHDOG_GRACE_PERIOD`(기본 60초) 동안 만료 판정 유예(OQ-4 Answered). - -## 주요 사용자 시나리오 - -- 전략 모듈이 N시간 만기 매수 슬롯을 등록(`forced_close_at = 오후 3시`)한 뒤 프로세스를 종료한 경우 — 워치독이 만료되고, 데몬이 3시에 해당 슬롯을 감지해 자동 매도를 제출한다. -- RabbitMQ 장애로 전략 모듈이 snapshot RPC 를 보내지 못하는 상황 — 워치독이 만료되고, stop-loss 조건 충족 시 데몬이 자동 매도를 제출한다. -- 전략 모듈이 정상 동작 중인 슬롯 — 워치독이 주기적으로 갱신되어 만료되지 않으므로 forced_close_at / stop-loss 체크를 건너뛴다(전략 모듈 책임). -- `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10` 설정 시 — 오후 3시 10분에 전략 모듈 활성 여부와 무관하게 전체 `holding` 슬롯에 매도 intent 제출. -- 운영자가 CLI 또는 RPC 를 통해 특정 슬롯에 즉시 청산(kill-switch)을 요청하는 경우 — 별도 Discovery(trex-core#3 기반)에서 다룬다. 이번 Discovery 범위 밖. - -# 4. 이번에 정의할 변경 - -- **per-slot 워치독**: snapshot RPC 응답 시 해당 슬롯들의 `last_seen_at` 타임스탬프를 in-memory 로 갱신. `TREX_WATCHDOG_TIMEOUT`(초, env) 경과 시 "미감시" 판정. -- **forced_close_at 만기 감지 루프**: 워치독 만료 슬롯 중 `forced_close_at <= now(KST)` 인 슬롯에 내부 매도 intent 제출. `TREX_FORCED_CLOSE_CHECK_INTERVAL`(초, env) 주기. -- **stop-loss·take-profit 임계값 필드 추가 및 가격 체크**: 슬롯 도메인에 `stop_loss_price: Optional[Decimal]`·`take_profit_price: Optional[Decimal]` 추가. 워치독 만료 슬롯에 한해 KIS `TTTC8397R` polling(`stck_prpr`), 조건 `현재가 <= stop_loss_price OR 현재가 >= take_profit_price` 시 매도 trigger. `TREX_STOP_LOSS_POLL_INTERVAL`(초, env) 주기(두 조건 공통 사용). -- **임계값 갱신 RPC**: `trex.intent.req` 채널에 `action: "update_thresholds"` 추가. `holding` 슬롯의 `stop_loss_price`·`take_profit_price`·`forced_close_at` 를 매수 이후에도 갱신 가능(각 필드는 독립적으로 null 가능). 수신 시 해당 슬롯의 워치독 `last_seen_at` 도 리셋(전략 모듈 활성 증명). -- **장 마감 강제 청산**: `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional) 설정 시 해당 시각에 모든 `holding` 슬롯 매도. 미설정 시 동작 없음. 워치독 만료 여부 무관. -- **자동 매도 내부 경로**: `SlotService.sell()` 직접 호출 — RabbitMQ intent RPC 우회, transport 장애와 독립. - -## 영향 범위 - -- 코드 영향: - - `trex_core/domain/slot/models.py`: `stop_loss_price: Optional[Decimal]`, `take_profit_price: Optional[Decimal]` 필드 추가 - - `trex_core/domain/slot/service.py`: forced_close / stop-loss / take-profit 조건 평가 메서드; `update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)` 메서드 - - `trex_core/application/portfolio.py` 또는 신규 모듈: per-slot 워치독 store (`dict[slot_id, datetime]`, in-memory) - - `trex_core/application/messaging_handlers.py`: (1) snapshot RPC handler — 응답 슬롯 `last_seen_at` 갱신; (2) intent RPC — `stop_loss_price`·`take_profit_price` 파싱; (3) `update_thresholds` action handler — `stop_loss_price`·`take_profit_price`·`forced_close_at` DB 갱신 + `last_seen_at` 리셋 - - `trex_core/runtime/daemon.py`: safety-sell worker 스레드 추가 — forced-close/stop-loss·take-profit(워치독 만료 슬롯) + 장 마감 청산(전체) 처리 - - `trex_core/runtime/config.py`: `TREX_WATCHDOG_TIMEOUT`(기본 120), `TREX_WATCHDOG_GRACE_PERIOD`(기본 60), `TREX_FORCED_CLOSE_CHECK_INTERVAL`(기본 60), `TREX_STOP_LOSS_POLL_INTERVAL`(기본 30), `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(optional) env 파싱 - - `trex_core/adapters/persistence/`: `stop_loss_price`, `take_profit_price` 컬럼 추가 DB 마이그레이션 - - KIS 현재가 polling 어댑터 신규: `TTTC8397R`(실전) / `VTTC8397R`(모의) 분기, `stck_prpr` 파싱 - - `trex_core/application/intents.py`: `IntentIn.stop_loss_price`, `IntentIn.take_profit_price` 필드 추가 -- 문서 영향: - - `docs/interface-contract.md`: intent RPC payload 에 `stop_loss_price`·`take_profit_price`·`forced_close_at` 필드, `update_thresholds` action 계약, snapshot RPC 가 워치독 갱신 신호임을 명시 - - `docs/runtime-flows.md`: 워치독 갱신 흐름, forced-close checker, stop-loss 가격 체크, 장 마감 청산 흐름 추가 - - `docs/project-structure.md`: 신규 모듈/어댑터 반영 -- 운영 영향: - - DB 마이그레이션: `stop_loss_price`, `take_profit_price` 컬럼 추가(additive, 기존 슬롯 NULL 허용) - - 신규 데몬 스레드: safety-sell worker (forced-close + stop-loss·take-profit + 장 마감 통합) - - 전략 모듈 연동 계약: snapshot RPC 호출 주기 < `TREX_WATCHDOG_TIMEOUT` 이어야 오탐 없음 - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: 데몬은 `TREX_FORCED_CLOSE_CHECK_INTERVAL`(초, 기본 60) 주기로 `holding` 상태이며 **워치독이 만료된** 슬롯 중 `forced_close_at <= now(KST)` 인 슬롯을 감지해 내부 매도 intent 를 제출한다. -- FR-2: `stop_loss_price` 또는 `take_profit_price` 가 설정되고 **워치독이 만료된** 슬롯에 한해, 데몬은 `TREX_STOP_LOSS_POLL_INTERVAL`(초, 기본 30) 주기로 KIS `TTTC8397R`(`stck_prpr`) 를 polling 하고 `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 내부 매도 intent 를 제출한다. 두 조건은 동일 polling 루프에서 평가한다. -- FR-3: 자동 매도 intent 는 `SlotService.sell()` 내부 함수 직접 호출로 제출한다(RabbitMQ intent RPC 우회). 기존 outbox 내구성·슬롯 상태 전이·ledger 기록 경로를 따른다. -- FR-4: 이미 `pending_sell` 이거나 자동 매도가 진행 중인 슬롯에 대해 중복 trigger 가 발생하지 않도록 idempotency 를 보장한다. -- FR-5: snapshot RPC handler 는 응답에 포함된 슬롯들의 `last_seen_at` 타임스탬프를 in-memory 워치독 store 에 갱신한다. `now() - last_seen_at > TREX_WATCHDOG_TIMEOUT` 인 슬롯을 "워치독 만료"로 판정한다. -- FR-6: `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST, optional) 이 설정된 경우 해당 시각에 워치독 만료 여부와 무관하게 모든 `holding` 슬롯에 내부 매도 intent 를 제출한다. 미설정 시 장 마감 청산 동작 없음. -- FR-7: 전략 모듈은 `trex.intent.req` 채널에 `action: "update_thresholds"` 메시지를 발행해 `holding` 상태 슬롯의 `stop_loss_price`·`take_profit_price`·`forced_close_at` 를 갱신할 수 있다(각 필드는 독립적으로 null 가능). 데몬은 DB 를 업데이트하고 해당 슬롯의 워치독 `last_seen_at` 을 현재 시각으로 리셋한다. `holding` 이 아닌 슬롯에 대한 요청은 무시(또는 에러 응답)한다(OQ-6 Deferred → REQ AC). - -## 비기능 요구사항 - -- NFR-1: forced_close_at 만기 감지 지연은 `TREX_FORCED_CLOSE_CHECK_INTERVAL` 이내(기본 60초, env 로 조정 가능). DB/메모리 스캔만 수행하므로 KIS 쿼터 무관. -- NFR-2: 자동 매도 실패(broker 오류) 시 기존 outbox retry 경로를 따르며, 슬롯 상태는 execution worker 결과에 따라 전이된다. -- NFR-3: 가격 조건 polling(`TTTC8397R`) 주기는 `TREX_STOP_LOSS_POLL_INTERVAL`(초, 기본 30) env 로 설정한다(stop-loss·take-profit 공통). 호출 수 상한 = 워치독 만료 슬롯 중 `stop_loss_price` 또는 `take_profit_price` 설정 슬롯 수 × (3600 / `TREX_STOP_LOSS_POLL_INTERVAL`) 로 산정해 KIS 쿼터를 초과하지 않도록 REQ 단계에서 상한을 명시한다. -- NFR-4: 전략 모듈의 snapshot RPC 호출 주기는 `TREX_WATCHDOG_TIMEOUT`(기본 120초) 보다 짧아야 한다. 이 조건이 위반되면 정상 동작 중인 슬롯에서도 워치독 오탐이 발생한다. REQ 에서 권장 관계식(`snapshot_interval < TREX_WATCHDOG_TIMEOUT / 2`, 즉 기본값 기준 60초 이내)을 명시한다. - -## 범위 경계 - -- In Scope - - per-slot 워치독(snapshot RPC 기반 `last_seen_at` 갱신 + 만료 판정) - - `forced_close_at` 만기 자동 매도(워치독 만료 슬롯만) - - `stop_loss_price`·`take_profit_price` 필드 추가 및 가격 기반 자동 매도(워치독 만료 슬롯만, 동일 polling 루프) - - 자동 매도 내부 경로(`SlotService.sell()` 직접 호출) - - 장 마감 강제 청산(`TREX_MARKET_CLOSE_LIQUIDATION_TIME`, 워치독 무관) - - DB 마이그레이션: `stop_loss_price`, `take_profit_price` 컬럼 추가 - - intent RPC payload 에 `stop_loss_price`, `take_profit_price` 설정 및 `update_thresholds` 갱신 API 계약 업데이트(`forced_close_at` 포함) -- Out of Scope - - 운영자 수동 즉시 청산(kill-switch): 별도 Discovery(trex-core#3 기반) - - 부분 매도(일부 수량만 청산): REL-001 Feedback Handoff Discovery Input 에 등록된 별도 후보 - - trailing stop 등 고급 손절 전략: 단순 임계값 이하 감지만 구현 - - 전략 모듈 주도 stop-loss·take-profit 관리: "trex-core 자율" 구현만 대상 - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (Low): stop-loss 가격 소스는 KIS `TTTC8397R` polling 으로 확정(OQ-1 Answered). 워치독 만료 슬롯에만 polling 하므로 전략 모듈 정상 동작 중에는 KIS 쿼터 소비 없음. - - 완화 방안: 워치독 만료 슬롯 수를 기준으로 polling 호출 수를 산정(NFR-3). KIS 쿼터 초과 시 polling 간격을 자동 조정하는 AC 를 REQ 에 추가한다. -- R-2 (Medium): 장 마감 강제 청산(`TREX_MARKET_CLOSE_LIQUIDATION_TIME`) 시 모든 `holding` 슬롯 매도 intent 가 동시에 폭증할 수 있다. execution worker 단일 순차 루프 지연 위험. - - 완화 방안: FR-4(idempotency) 로 중복 방지. execution worker FIFO 큐 처리. 장 마감 전 전략 모듈이 이미 일부 매도했다면 `holding` 슬롯 수가 줄어 폭증 완화. REQ 에서 수용 기준 명시. -- R-3 (Low): trex-core#3 kill-switch 가 별도 Discovery 로 분리됨. FR-3 내부 매도 경로를 kill-switch Discovery 에서 재사용할 수 있도록 별도 함수로 격리한다. - - 완화 방안: FR-3 internal trigger 경로를 별도 함수로 격리해 후속 kill-switch Discovery 에서 재사용 가능하도록 설계한다. -- R-4 (Low): 데몬 재시작 시 워치독 store(in-memory) 가 초기화된다. `TREX_WATCHDOG_GRACE_PERIOD` 동안 만료 판정을 유예해 오탐을 방지한다(OQ-4 Answered). grace period 중에는 forced_close_at / stop-loss 보호 공백이 발생하나, grace period 를 짧게 유지하면 허용 가능한 수준이다. - - 완화 방안: `TREX_WATCHDOG_GRACE_PERIOD` 기본값 60초로 확정됨(A-4). grace period 중에도 FR-6(장 마감 청산)은 동작함을 명시한다. - -## 가정 - -- A-1: 자동 매도 intent 는 `SlotService.sell()` 내부 함수 직접 호출로 제출한다. RabbitMQ intent RPC 를 경유하지 않는다. (**확정** — RabbitMQ 장애와 무관하게 동작해야 하는 "transport 독립" 요건에 부합.) -- A-2: `forced_close_at` 이 설정된 슬롯은 전략 모듈이 의도적으로 만기 청산을 요청한 것이므로, 만기 시점 데몬 자율 매도는 정책적으로 허용된다. -- A-3: stop-loss·take-profit 가격 소스는 KIS API `TTTC8397R`(실전) / `VTTC8397R`(모의) polling 으로 결정됐다(OQ-1 Answered). 평가 기준가는 `stck_prpr`(현재가, 직전 체결가). 트리거 조건: `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절). 동일 polling 루프에서 평가. -- A-4: 워치독 store 는 in-memory `dict[slot_id, datetime]` 이며 Postgres 에 영속하지 않는다. 데몬 재시작 시 초기화. 재시작 후 `TREX_WATCHDOG_GRACE_PERIOD`(기본 60초, env) 동안 워치독 만료 판정을 유예해 전략 모듈 재연결 시간을 확보한다. 만료 임계값은 `TREX_WATCHDOG_TIMEOUT`(기본 120초, env)(OQ-4 Answered — Grace period). - -## 오픈 질문 - -- OQ-1: stop-loss 를 위한 실시간 가격 소스를 어떻게 조달하는가? - - 상태: Answered - - 결정: KIS API polling(`TTTC8397R` 실전 / `VTTC8397R` 모의), 평가 기준가 `stck_prpr`(현재가). 트리거 조건: `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절). - - 본문 반영: FR-2, NFR-3, A-3, 코드 영향(KIS 현재가 polling 어댑터). -- OQ-2: trex-core#3(강제 청산/kill-switch) 이 이번 Discovery 에 포함되는가, 별도 Discovery 인가? - - 상태: Answered - - 결정: 별도 Discovery 로 분리. 이번 dcy-007 은 자동 조건 트리거(시간 만기·가격 임계값·장 마감)만 다룬다. - - 본문 반영: Out of Scope(kill-switch 분리 확정), R-3(Low 하향), 제약사항, 사용자 시나리오. -- OQ-3: forced_close_at checker 폴링 간격을 env 로 설정 가능하게 하는가, 고정값으로 하는가? - - 상태: Answered - - 결정: forced_close_at 체크와 stop-loss 가격 polling 을 별도 간격으로 분리하며 둘 다 env 로 설정 가능. `TREX_FORCED_CLOSE_CHECK_INTERVAL`(기본 60초) / `TREX_STOP_LOSS_POLL_INTERVAL`(기본 30초). - - 본문 반영: FR-1, FR-2, NFR-1, NFR-3, 코드 영향(config.py + daemon.py). -- OQ-4: 데몬 재시작 시 워치독이 초기화된 직후 safety-sell 체크를 어떻게 처리하는가? - - 상태: Answered - - 결정: 후보 A — Grace period. 재시작 후 `TREX_WATCHDOG_GRACE_PERIOD`(기본 60초, env) 동안 워치독 만료 판정을 유예. `TREX_WATCHDOG_TIMEOUT` 기본값 120초. - - 본문 반영: A-4(가정 확정 + 기본값), NFR-4(기본값 명시), R-4(Low 하향), 코드 영향(config.py 기본값), DECIDE(OQ-4 완료). - -- OQ-5: `update_thresholds` 가 `forced_close_at` 도 갱신 대상에 포함하는가? - - 상태: Answered - - 결정: 포함. `update_thresholds` 는 `stop_loss_price`·`take_profit_price`·`forced_close_at` 세 필드를 모두 갱신 대상으로 한다(각 필드는 독립적으로 null 가능). - - 본문 반영: FR-7(갱신 대상 확장), 코드 영향(`service.py` `update_thresholds()` 시그니처, `messaging_handlers.py` 파싱), S-6(성공 기준 갱신). -- OQ-6: `update_thresholds` 요청이 `holding` 이 아닌 슬롯에 오면 무시(silent)인가, 에러 응답인가? - - 상태: Deferred — REQ AC 단계에서 결정. 에러 응답 시 RPC reply 구조 필요, 무시 시 간단. Freeze 를 막지 않는다. - - 처리 방안: REQ 초안 작성 시 AC 에 명시. - - 종료 조건: REQ 초안의 FR-7 AC 에 반영되면 Answered. - -### 오픈 질문 처리 기준 - -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 - -- S-1: 워치독이 만료된 슬롯 중 `forced_close_at <= now()` 인 슬롯에 자동 매도 intent 가 제출되고 슬롯이 `idle` 로 전이된다. -- S-2: 워치독이 만료된 슬롯에서 `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 자동 매도 intent 가 제출되고 슬롯이 `idle` 로 전이된다. -- S-3: 이미 `pending_sell` 인 슬롯에 대해 자동 매도 checker 가 추가 intent 를 제출하지 않는다(idempotency). -- S-4: 워치독이 활성(last_seen_at 갱신 중)인 슬롯은 forced_close_at / stop-loss / take-profit 체크를 건너뛴다. -- S-5: `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 설정 시 해당 시각에 워치독 상태와 무관하게 모든 `holding` 슬롯에 매도 intent 가 제출된다. 미설정 시 동작 없음. -- S-6: `update_thresholds` 메시지 수신 시 `holding` 슬롯의 `stop_loss_price`·`take_profit_price`·`forced_close_at` 가 DB 에 갱신되고, 해당 슬롯의 워치독 `last_seen_at` 이 리셋된다. `holding` 이 아닌 슬롯 요청은 무시된다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: 단위 테스트 — 워치독 만료 + `forced_close_at = now() - 1s` 슬롯에 checker 1회 수행 후 SlotState 전이 확인 / 데이터 출처: pytest 결과 -- S-2 측정 방식: 단위 테스트(mock KIS polling) — 워치독 만료 + 손절 조건(임계값 이하) / 익절 조건(임계값 이상) 각각 주입 후 매도 trigger 확인 / 데이터 출처: pytest 결과 -- S-3 측정 방식: 단위 테스트 — `pending_sell` 슬롯에 checker 수행 시 추가 intent 미제출 확인 / 데이터 출처: pytest 결과 -- S-4 측정 방식: 단위 테스트 — 워치독 활성 슬롯에 checker 수행 시 intent 미제출 확인 / 데이터 출처: pytest 결과 -- S-5 측정 방식: 단위 테스트 — 장 마감 시각 mock 주입 후 워치독 활성/만료 슬롯 모두 매도 intent 제출 확인 / 데이터 출처: pytest 결과 -- S-6 측정 방식: 단위 테스트 — `update_thresholds` 수신 후 DB 갱신 + `last_seen_at` 리셋 확인; `pending_buy` 슬롯 요청 시 무시 확인 / 데이터 출처: pytest 결과 - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-2(kill-switch 포함 여부) 결정 후 범위 경계 확정. → **완료**: kill-switch 별도 Discovery 분리 확정. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: OQ-1(가격 소스) 결정 후 S-2 측정 방식 확정. → **완료**: KIS `TTTC8397R` mock 단위 테스트. -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(High) — OQ-1 Answered 로 해소. 잔여 R-2(Medium)·R-4(Low) → REQ 단계 AC 로 완화. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료. → **완료**: OQ-5 Answered, OQ-6 Deferred. 잔여 Open 항목 없음. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 가 Freeze 승인. - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md -- 참조 문서: - - docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md (A-5 가정 참조) - - docs/batches/bat-007_20260605_strategy-module-rabbitmq-transport/index.md (R-1 High 참조) - - docs/releases/rel-001_20260606_trex-core-slot-core-baseline.md (Excluded 확인) - - trex-core#3 (강제 청산/kill-switch GitHub issue — OQ-2 범위 근거) - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- ~~OQ-1: Answered — KIS API polling(`TTTC8397R`), 현재가(`stck_prpr`), 트리거 `현재가 <= stop_loss_price`.~~ -- ~~OQ-2: Answered — kill-switch 는 별도 Discovery(trex-core#3 기반)로 분리.~~ -- ~~OQ-4: Answered — Grace period. `TREX_WATCHDOG_GRACE_PERIOD`(초, env) 동안 만료 판정 유예.~~ -- ~~OQ-5: Answered — `update_thresholds` 는 `stop_loss_price`·`take_profit_price`·`forced_close_at` 세 필드를 모두 갱신 대상으로 한다.~~ - -## CONFIRM - -- ~~OQ-3: Answered — `TREX_FORCED_CLOSE_CHECK_INTERVAL`(60초) / `TREX_STOP_LOSS_POLL_INTERVAL`(30초) 별도 env.~~ -- ~~A-1: 확정 — `SlotService.sell()` 내부 함수 직접 호출, RabbitMQ intent RPC 우회. transport 독립 요건 부합.~~ - -## DATA - -- trex-core#3 GitHub issue 상세 내용: kill-switch 범위와 구현 방향 확인(OQ-2 resolved, 후속 Discovery 작성 시 참조). - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: 승인 -- Handoff Rationale: OQ-5 Answered, OQ-6 Deferred. 모든 Open 항목 해소. FR-7 범위 확정(forced_close_at 포함). -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-07 diff --git a/docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md b/docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md deleted file mode 100644 index 7941ad8..0000000 --- a/docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md +++ /dev/null @@ -1,222 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-008_20260607_emergency-stop-kill-switch -- 이슈명: 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외 -- 작성 시각(KST): 2026-06-07 -- 마지막 갱신 시각(KST): 2026-06-07 11:11 (draft-req: REQ-043~047 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 참조 파일: GitHub #3 (feat: 비상 정지/강제 청산(kill-switch) — portfolio/전역 단위 슬롯 청산 + 당일 전략 제외) - - 관련 TODO/메모: dcy-007 Out of Scope "운영자 수동 즉시 청산(kill-switch): 별도 Discovery(trex-core#3 기반)" 명시 -- 해석: - - dcy-007(core 자율 안전 매도)이 자동 조건 기반 청산(워치독 만료 + 가격/시간 트리거)을 구현했지만, 운영자가 즉시 개입할 수 있는 비상 정지 수단은 범위에서 제외됐다. 기존 pause(REQ-032)는 신규 매매만 중단하고 현재 holding 포지션을 청산하지 않으므로 비상 시 실제 보호 수단이 되지 못한다. - - 이번 Discovery는 (1) 운영자가 명령 한 번으로 portfolio/전역 단위 holding 슬롯을 즉시 청산하고, (2) 당일 전략 실행을 중단시키며, (3) 다음 거래일에 자동 복귀하는 비상 정지 기능의 범위·제약·오픈 질문을 정의한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-007이 명시적으로 "운영자 수동 즉시 청산(kill-switch): 별도 Discovery(trex-core#3 기반)"를 Out of Scope로 분리했다. - - 기존 pause(REQ-032)는 holding 슬롯을 청산하지 않으며, day-scoped 제외도 지원하지 않는다. 기능 성격이 근본적으로 다르다. - -# 3. 문제점의 요약 - -- 현재 상태 - - trex-core는 `pause/resume` RPC(REQ-032, `trex.pause.req`)로 신규 매매를 중단할 수 있으나, 현재 `holding` 슬롯을 즉시 매도하는 운영자 트리거가 없다(`trex_core/adapters/persistence/postgresql.py`, `PostgreSQLPauseStore`). - - 자율 안전 매도(dcy-007, REL-003)는 워치독 만료 + 가격·시간 조건이 충족된 경우에만 청산하며, 운영자가 즉시 개입하는 비상 경로가 없다. - - kill-switch를 위한 슬롯 청산 경로(`SlotService.sell()`, REQ-036/BAT-010)와 실행 인프라(outbox, execution worker)는 이미 구현되어 있다. 호출 진입점만 없는 상태다. -- 기대 상태 - - 운영자가 RPC 메시지 또는 CLI 명령으로 즉시 모든(또는 특정 portfolio의) holding 슬롯을 청산하고 당일 전략 실행을 중단시킬 수 있다. - - 당일 전략 제외는 다음 거래일 자동 복귀한다(day-scoped, pause와 달리 수동 resume 불필요). - - `SlotService.sell()`(REQ-036)을 재사용해 기존 outbox 내구성·슬롯 상태 전이·ledger 기록 경로를 그대로 따른다. -- 이해관계자 - - 운영자(Justin Ko): 실 KIS 브로커 연동 환경에서 이상 상황 발생 시 즉시 포지션 청산이 필요한 주요 사용자 - - trex-core 데몬 운영자: kill-switch 발동 후 데몬 상태·로그 모니터링 담당 - - 전략 모듈 개발자(orb-v1): kill-switch 발동 시 당일 추가 매수 의도가 거부됨을 인지해야 함 -- 제약사항 - - `SlotService.sell()`(REQ-036)은 HOLDING 슬롯만 대상이며, PENDING_SELL 슬롯에는 idempotent로 동작한다. PENDING_BUY 슬롯 처리는 별도 결정 필요(OQ-3). - - 당일 제외 상태는 데몬 재시작 후에도 유지되어야 하므로 영속화가 필요하다(pause_store 패턴 참조). - - trex-core sync+thread 런타임 — kill-switch 발동 시 다수 슬롯 순차 sell()이 execution worker FIFO 큐에 집중될 수 있다. - -## 주요 사용자 시나리오 - -- 실 KIS 브로커 환경에서 예기치 않은 시장 급변이 발생한 경우 — 운영자가 kill-switch를 발동해 전체 holding 포지션을 즉시 청산하고 당일 추가 매수를 차단한다. -- 특정 portfolio의 전략 오동작을 감지한 경우 — portfolio 단위로 kill-switch를 발동해 해당 portfolio 슬롯만 청산하고, 다른 portfolio는 정상 운행한다. -- 데몬 점검 또는 긴급 배포 전 포지션 청산이 필요한 경우 — 수동 kill-switch로 전체 포지션을 먼저 닫고 데몬을 안전하게 중단한다. - -# 4. 이번에 정의할 변경 - -- **kill-switch 트리거 진입점 추가**: 운영자가 RPC 메시지(`trex.control.req`, `action: "kill_switch"`) 또는 CLI로 kill-switch를 발동하면, 대상 범위(portfolio/전역)의 모든 holding 슬롯에 `SlotService.sell()`을 순차 호출한다. 기존 `trex.pause.req`를 `trex.control.req`로 개념 확장하고 action 필드 기반 dispatch로 구현한다(새 큐 불필요). -- **day-scoped 전략 제외 기능 추가**: kill-switch 발동 후 대상 범위 자산을 당일 전략 실행에서 제외한다. snapshot RPC 응답의 masking 소스에 "kill-switch 당일 제외"를 추가한다. 다음 거래일 데몬 부팅 시 자동 복귀한다. -- **당일 제외 상태 영속화**: kill-switch 당일 제외 상태를 데몬 재시작 후에도 유지하기 위해 DB에 영속한다. **별도 테이블/store 어댑터를 신설**한다(OQ-2 Answered) — `snapshot_pause`(영구 pause)와 분리하고 `trading_date` 키로 day-scoped 표현. 다음 거래일 부팅 시 자동 무효. -- **수동 해제(resume) 지원**: 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제할 수 있도록 `trex.control.req` 에 `action: "resume_kill_switch"`(범위: portfolio/전역)를 추가한다(OQ-4 Answered). 해당 범위의 day-scoped 제외 row 를 삭제해 정상 매매를 재개한다. 단, 이미 청산된 슬롯은 되돌리지 않는다(매도 비가역). - -## 영향 범위 - -- 코드 영향: - - `trex_core/domain/slot/service.py`: kill-switch 발동 시 복수 슬롯에 sell()을 순차 호출하는 진입점(메서드 또는 헬퍼) - - `trex_core/application/messaging_handlers.py`: `trex.control.req` action dispatch — kill-switch handler 추가 - - `trex_core/adapters/persistence/postgresql.py` 또는 신규 어댑터: day-scoped 제외 상태 영속 read/write - - `trex_core/runtime/daemon.py`: pause consumer를 control consumer(`trex.control.req`)로 확장 — action 기반 dispatch - - `trex_core/application/portfolio.py` 또는 snapshot handler: masking 소스에 "kill-switch 당일 제외" 추가 -- 문서 영향: - - `docs/interface-contract.md`: kill-switch RPC 메시지 계약(payload 구조, 대상 범위, 응답 구조) - - `docs/runtime-flows.md`: kill-switch 발동 흐름(트리거 → 순차 sell() → day-scoped 제외) 추가 - - `docs/project-structure.md`: kill-switch 관련 신규 모듈/handler 반영 -- 운영 영향: - - DB 스키마: day-scoped 제외 상태 영속 컬럼 또는 테이블 추가(additive) - - `trex.pause.req` → `trex.control.req` 개념 확장. kill-switch는 `action: "kill_switch"` 필드로 dispatch(새 큐 불필요). daemon.py의 pause consumer 스레드를 control consumer로 교체. - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: 운영자가 kill-switch 명령을 발행하면 대상 범위(portfolio_id 지정 또는 전역)의 모든 `holding` 슬롯에 `SlotService.sell(actor="kill-switch", reason="emergency_stop")`을 순차 호출해 즉시 청산을 제출한다. -- FR-2: kill-switch 발동 후 대상 범위의 자산(또는 모든 자산)을 당일 거래일 전략 실행에서 제외한다. snapshot RPC 응답의 masking 소스에 "kill-switch 당일 제외" 항목을 추가한다. -- FR-3: 당일 제외 상태는 데몬 재시작 후에도 유지된다. 다음 거래일 첫 부팅 시 자동으로 해제된다(day-scoped). -- FR-4: portfolio 단위(특정 portfolio_id의 슬롯만)와 전역(모든 슬롯)을 구분해 지원한다. -- FR-5: 이미 `pending_sell` 상태인 슬롯에 대해 sell()을 중복 호출하지 않는다(REQ-036 idempotency 적용). -- FR-6: kill-switch 발동 이력(actor, 발동 시각, 대상 범위, 처리 슬롯 수)이 데몬 로그에 기록된다. - -## 비기능 요구사항 - -- NFR-1: kill-switch 발동 후 holding 슬롯 전체에 대한 sell() 제출 완료까지 지연이 슬롯 수 N 기준 N × 100ms + 5s 이내이어야 한다(순차 처리 기준, 실 KIS 연동 outbox 전송 제외). -- NFR-2: kill-switch 발동이 기존 자율 안전 매도 루프(dcy-007/REL-003)의 동작에 영향을 주지 않는다. 두 기능은 독립적으로 동작한다. -- NFR-3: kill-switch 당일 제외 상태가 영속화되어 데몬 재시작 후에도 snapshot RPC masking이 유지된다. - -## 범위 경계 - -- In Scope - - holding 슬롯 즉시 청산 (portfolio/전역 단위, `SlotService.sell()` 재사용) - - 당일 전략 실행 제외 + 다음 거래일 자동 복귀 (day-scoped, 영속 필요) - - kill-switch 트리거 표면: `trex.control.req` RPC, `action: "kill_switch"` 메시지 (CLI는 REQ 단계에서 결정) - - kill-switch 발동 후 당일 추가 매수 거부 (masking 소스 확장) - - kill-switch 발동 이력 로깅 -- Out of Scope - - 부분 수량 청산 (GitHub #5 별도 Discovery) - - 전략 모듈 프로세스 강제 종료 (trex-core 범위 밖) - - pending_buy 슬롯의 롤백 처리 (OQ-3 Deferred — REQ 단계에서 결정) - - kill-switch 발동 후 자율 안전 매도(dcy-007) 비활성화 - - trailing stop 등 고급 종료 전략 - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (Medium): kill-switch 발동 시 다수 슬롯에 순차 sell()을 호출하면 execution worker FIFO 큐에 부하가 집중된다. 슬롯 수가 많을 경우 처리 지연이 발생할 수 있다. - - 완화 방안: sell() 호출은 PENDING_SELL 전이만 수행하므로 빠르다(DB write 1회). execution worker는 기존 순차 처리 유지. 슬롯 수 10 미만 운영 환경 기준으로 NFR-1 허용 지연 설정. -- R-2 (해소): 당일 제외 상태 영속 방식 — **별도 테이블/store 신설로 결정**(OQ-2 Answered). pause는 "resume 전까지 유지(persistent)", kill-switch 제외는 "당일만(day-scoped, `trading_date` 키)"으로 테이블 분리 → 의미 충돌 없음, 기존 pause 마스킹 코드 무영향. -- R-3 (Low): kill-switch 발동 중 브로커 오류로 일부 슬롯의 sell()이 실패하거나 지연될 수 있다. - - 완화 방안: sell() → PENDING_SELL 전이 성공 시 기존 outbox retry 경로가 eventual consistency를 보장한다. 발동 이력(FR-6)으로 미처리 슬롯 추적 가능. -- R-4 (Low): kill-switch와 자율 안전 매도(dcy-007)가 동시에 같은 슬롯에 sell()을 호출하면 중복 전이가 발생할 수 있다. - - 완화 방안: REQ-036 idempotency가 PENDING_SELL 상태 중복 sell()을 무시한다. 코드 충돌 없음. - -## 가정 - -- A-1: `SlotService.sell()`(REQ-036/BAT-010)이 이미 구현되어 있어 kill-switch가 직접 재사용할 수 있다. -- A-2: 당일 전략 제외는 snapshot RPC 응답의 masking 소스 확장으로 구현한다. 기존 pause masking 코드 패턴(`portfolio.py`)을 참조한다. -- A-3: kill-switch 발동 후에도 기존 자율 안전 매도 루프(dcy-007)는 독립적으로 동작한다. -- A-4: 운영 환경의 holding 슬롯 수는 통상 10 미만이다. 대규모 병렬 청산 최적화는 Out of Scope. - -## 오픈 질문 - -- OQ-1: kill-switch 트리거 표면 — 기존 `trex.pause.req` 채널에 `action: "kill_switch"` 메시지를 추가할지, 신규 채널(`trex.killswitch.req`)을 별도로 열지? - - 상태: Answered - - 결정: `trex.pause.req` → `trex.control.req` 개념 확장. kill-switch는 `action: "kill_switch"` 필드로 라우팅. 새 큐 불필요. 기능마다 채널을 추가하면 consumer 스레드/커넥션이 기능 수에 비례해 증가하므로 단일 제어 채널 + action discriminator 패턴을 채택한다. - - 반영 위치: # 4 변경 정의, # 4 영향 범위 운영 영향, # 5 범위 경계 In Scope -- OQ-2: 당일 제외 상태 영속 방식 — `PostgreSQLPauseStore`(pause_store)를 확장해 day-scoped 컬럼을 추가할지, 별도 테이블/어댑터를 신설할지? - - 상태: Answered - - 결정: **별도 테이블/어댑터를 신설**한다. kill-switch 당일 제외는 `trading_date` 키로 day-scoped 표현이 자연스럽고, 기존 `snapshot_pause`(portfolio_id PK, resume까지 영구·날짜 없음)와 의미가 달라 같은 테이블에 섞으면 마스킹/만료 로직 복잡도가 올라간다(R-2). 신규 테이블(예: `killswitch_exclusion(portfolio_id, trading_date, actor, ...)`) + 전용 store 어댑터로 관심사를 분리하고, snapshot 마스킹은 `mask = pending | warning | pause | killswitch_today`로 합성한다. 기존 pause 코드는 무영향. - - 반영 위치: # 4 변경 정의(당일 제외 상태 영속화), # 4 영향 범위(신규 테이블/마이그레이션 + store 어댑터), # 6 R-2 완화, # 5 범위 경계 - - 종료 조건: 충족(REQ 단계에서 정확한 테이블 컬럼/만료 기준 = trading_date 경과 자동 무효 상세화). -- OQ-3: kill-switch 발동 시 `pending_buy` 상태 슬롯 처리 — 현재 진행 중인 매수를 무시하고 HOLDING이 된 이후에 청산하도록 기다릴지, 즉시 롤백(PENDING_BUY→IDLE) 처리를 별도로 추가할지? - - 상태: Deferred → REQ 단계 - - 처리 방안: PENDING_BUY 롤백은 execution 루프와 상호작용이 복잡하므로 REQ 단계에서 상세 설계. 기본 구현은 HOLDING 슬롯만 대상. - - 종료 조건: REQ 초안 작성 시 pending_buy 범위 명시. -- OQ-4: 전역 halt 수동 해제 지원 여부 — kill-switch 발동 후 같은 거래일 내에 운영자가 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 하는가? - - 상태: Answered - - 결정: **수동 해제를 지원한다.** 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 한다. `trex.control.req` 에 `action: "resume_kill_switch"`(범위: portfolio/전역) 를 추가하고, 해당 범위의 day-scoped 제외 row(OQ-2 신규 테이블)를 삭제(또는 무효화)한다. 해제는 "당일 제외"만 풀며 이미 청산된 슬롯을 되돌리지 않는다(매도는 비가역). - - 반영 위치: # 4 변경 정의(수동 해제 action), # 4 영향 범위(control RPC handler), # 5 범위 경계 In Scope, # 10 DECIDE - - 종료 조건: 충족(REQ 단계에서 `resume_kill_switch` payload/응답 + 권한/감사 상세화). - -### 오픈 질문 처리 기준 - -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. - -# 7. 초기 성공 기준 - -- S-1: kill-switch 명령 발동 후 모든 holding 슬롯에 PENDING_SELL 전이 제출이 완료된다(슬롯 수 N 기준 NFR-1 이내). -- S-2: kill-switch 발동 후 다음 snapshot RPC 응답에서 대상 자산이 masking되어 buyable/held 뷰에 노출되지 않는다(당일 전략 제외 확인). -- S-3: 데몬 재시작 후에도 kill-switch 당일 제외 상태가 유지되어, 당일 전략 실행이 차단된다. -- S-4: 다음 거래일 데몬 부팅 시 kill-switch 당일 제외 상태가 자동 해제되어 정상 매매 대상이 된다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: kill-switch 발동 후 `slot_repo.list_slots(day)` 결과에서 PENDING_SELL 수 == 발동 전 HOLDING 수 확인 / 데이터 출처: 단위 테스트(mock repo) + 실 e2e 로그 -- S-2 측정 방식: kill-switch 발동 후 snapshot RPC 요청 → 응답 assets 목록에서 청산된 symbol 부재 확인 / 데이터 출처: 실 RabbitMQ e2e 테스트 또는 단위 테스트(masking 로직) -- S-3 측정 방식: kill-switch 발동 → 데몬 재시작 → snapshot RPC → masking 지속 확인 / 데이터 출처: 통합 테스트 또는 manual e2e -- S-4 측정 방식: 다음 거래일 날짜로 데몬 부팅 → snapshot RPC → 청산된 symbol이 buyable 뷰에 복귀 확인 / 데이터 출처: 통합 테스트(날짜 mock) 또는 manual e2e - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - OQ-1/2/4 Answered, OQ-3 REQ Deferred. scope = portfolio+전역 둘 다(FR-4). 확정. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - S-1~S-4 각각 단위 테스트/e2e 근거 명시(# 7 측정 방식). 확정. -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - R-1(큐 부하) 완화 = 순차 sell()·N<10 가정으로 NFR-1 인계, R-2(영속 방식) 해소(별도 테이블). REQ 단계 인계. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - OQ-1/2/4 Answered, OQ-3 Deferred → `Open` 없음. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - Confirmed By = Justin. - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md -- 참조 문서: - - GitHub #3: feat: 비상 정지/강제 청산(kill-switch) — portfolio/전역 단위 슬롯 청산 + 당일 전략 제외 - - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md (Out of Scope 분리 근거) - - docs/srs/Interface/req-036_auto-sell-internal-path.md (SlotService.sell() 재사용) - - docs/srs/Interface/req-032_runtime-pause-resume-control.md (pause 패턴 참조) - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- ✅ DECIDE_PERSIST_METHOD (OQ-2, Answered): 당일 제외 상태 영속 방식 = **별도 테이블/store 신설**(`snapshot_pause`와 분리, `trading_date` day-scoped). -- ✅ DECIDE_MANUAL_RESUME (OQ-4, Answered): 수동 해제 **지원** — `trex.control.req action: "resume_kill_switch"` 추가, day-scoped 제외 해제(청산된 슬롯은 비가역). - -## CONFIRM - -- ✅ CONFIRM_SCOPE_PORTFOLIO_AND_GLOBAL: **portfolio 단위 + 전역 단위 둘 다 포함**. 본문 FR-4("portfolio 단위와 전역 단위 둘 다 구분해 지원") + In Scope("portfolio/전역 단위") + 이슈명에서 이미 확정 — 동일하게 유지. -- ✅ CONFIRM_PENDING_BUY_DEFERRED: **REQ 단계 Deferred로 처리(동의)**. OQ-3(Deferred → REQ 단계) + Out of Scope("pending_buy 슬롯의 롤백 처리 — OQ-3 Deferred")에 비차단으로 명시됨. 기본 구현은 HOLDING 슬롯만 대상. - -## DATA - -- (비차단, REQ 단계 계측) DATA_SLOT_COUNT_UPPER_BOUND: 실 운영 환경의 최대 동시 holding 슬롯 수. A-4 가정(통상 10 미만)으로 NFR-1(N×100ms+5s) 산정 충분. 실측 상한은 REQ acceptance/verification 단계에서 확정 — Discovery confirm 차단 아님. - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1(trex.control.req+action), OQ-2(별도 테이블/store 신설), OQ-4(수동 해제=resume_kill_switch) Answered, OQ-3(pending_buy) REQ Deferred. CONFIRM(scope=portfolio+전역=FR-4, pending_buy=REQ Deferred) 본문 근거로 확정, DATA(slot 수 상한)는 REQ 단계 계측(비차단). 코드 사실관계(SlotService.sell REQ-036, trex.pause.req) 일치, baseline 모순 없음. REQ 초안 작성 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-07 11:11 diff --git a/docs/discovery/dcy-009_20260607_multi-instance-rabbitmq-topology.md b/docs/discovery/dcy-009_20260607_multi-instance-rabbitmq-topology.md deleted file mode 100644 index c7be70c..0000000 --- a/docs/discovery/dcy-009_20260607_multi-instance-rabbitmq-topology.md +++ /dev/null @@ -1,227 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-009_20260607_multi-instance-rabbitmq-topology -- 이슈명: 인스턴스별 독립 RabbitMQ 채널 토폴로지 — trex-core 다중 인스턴스 운영을 위한 Topic Exchange + Fanout Exchange per-instance 큐 분리 -- 작성 시각(KST): 2026-06-07 -- 마지막 갱신 시각(KST): 2026-06-07 (draft-req: REQ-048~051 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-048, REQ-049, REQ-050, REQ-051 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 참조 파일: trex_core/adapters/messaging/topology.py, trex_core/adapters/messaging/rabbitmq.py, trex_core/runtime/daemon.py, trex_core/runtime/config.py, trex_core/application/watchdog.py - - 관련 TODO/메모: dcy-006(RabbitMQ 도입, confirmed)에서 단일 인스턴스 기준 토폴로지 설계 완료. 다중 인스턴스 시 split-brain 문제와 per-instance 설정 파일 관리는 dcy-006 범위 밖이었음. -- 해석: - - trex-core는 현재 고정 큐명과 단일 `.env` 기반 설정으로 운영되며, 다중 인스턴스를 동일 RabbitMQ에 연결하면 경쟁 소비로 인해 WatchdogStore split-brain·SafetySellService 중복 발동·이벤트 중복 발행 문제가 발생한다. - - 이번 Discovery는 인스턴스별 TOML 설정 파일(`instance_key`, `portfolio_hub_token` 포함)을 도입하고, `instance_key` 기반 큐명 suffix화 + `trex.direct`(topic) + `trex.broadcast`(fanout) exchange 도입으로 인스턴스별 독립 채널을 확보하는 것을 목표로 한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-006(RabbitMQ 도입)은 단일 인스턴스 기준 초기 큐/exchange 설계를 다뤘으며 이미 confirmed + delivered 상태다. 다중 인스턴스 토폴로지와 per-instance TOML 설정 파일은 dcy-006의 연장이 아닌 별도 인프라 변경 주제다. - - 현재 어떤 Discovery/REQ도 `instance_key`, per-instance TOML 설정 파일, Topic exchange 기반 directed routing, Fanout exchange 기반 broadcast를 다루지 않는다. - -# 3. 문제점의 요약 - -- 현재 상태 - - `trex_core/adapters/messaging/topology.py`: 모든 큐명이 리터럴 상수(`SNAPSHOT_REQ_QUEUE = "trex.snapshot.req"` 등)로 하드코딩. 인스턴스 suffix 없음. - - `trex_core/adapters/messaging/rabbitmq.py`: `declare_topology()`가 고정 큐명으로 직접 큐를 선언한다. 제어 명령용 directed exchange나 broadcast exchange는 없다. - - `trex_core/runtime/daemon.py`: "single-writer" 설계 명시. 3개 consumer 스레드(intent/snapshot/pause)가 고정 큐명을 구독. 인스턴스 식별자 개념 없음. - - `trex_core/runtime/config.py`: `DaemonConfig.from_env()`가 모든 설정을 환경 변수에서 읽는다. `TREX_PORTFOLIO_HUB_TOKEN`을 포함한 민감값이 `.env`에 위치한다. per-instance 설정 파일 로더 없음. - - `trex_core/application/watchdog.py`: `WatchdogStore._store: dict[str, datetime]` — 프로세스 내 in-memory 저장. 다중 인스턴스 간 공유 불가. - - 문제: 다중 인스턴스를 동일 큐에 연결하면 RabbitMQ가 round-robin으로 메시지를 분배 → snapshot RPC를 받지 못한 인스턴스의 WatchdogStore가 갱신되지 않아 split-brain 발생 → 해당 인스턴스가 모든 슬롯을 watchdog 만료로 판정해 false safety-sell 발동 위험. -- 기대 상태 - - 각 인스턴스는 자신의 TOML 설정 파일을 가진다. `instance_key`와 `portfolio_hub_token`은 TOML에 기입하며 `.env`에 넣지 않는 것을 원칙으로 한다. - - `instance_key` 기반 큐명 suffix(`trex.intent.req.{instance_key}`)로 인스턴스별 독립 큐를 선언한다. - - `trex.direct`(topic exchange) 도입: strategy 모듈이 특정 인스턴스에만 메시지를 전달할 때 routing key `{instance_key}.{channel_type}`으로 publish. - - `trex.broadcast`(fanout exchange) 도입: 전역 제어 명령(global kill-switch 등)을 모든 인스턴스의 control 큐에 동시 전달. -- 이해관계자 - - 운영자(Justin Ko): 인스턴스별 TOML 설정 파일 관리 및 다중 포트폴리오 분산 운영 담당 - - 전략 모듈 개발자(orb-v1): routing key를 `instance_key` 기준으로 publish하도록 인터페이스 계약 변경 인지 필요 - - trex-core 데몬 운영자: `--config {path}.toml` CLI 인자로 인스턴스별 설정 파일 지정 + 큐 모니터링 -- 제약사항 - - pika `BlockingConnection`은 thread-safe하지 않다 — 현재 consumer 스레드별 독립 Connection 패턴은 변경 없이 유지해야 한다. - - 기존 durable 큐(`trex.intent.req` 등)가 이미 RabbitMQ 서버에 선언되어 있을 수 있다 → 새 큐명으로 전환 후 자연 소멸을 허용하며, RabbitMQ admin console에서 적절한 시점에 수동 삭제한다(OQ-1 Answered). - - strategy 모듈(orb-v1)이 routing key 변경에 맞게 publish 코드를 업데이트해야 한다 — 인터페이스 계약 정의가 선행되어야 한다. - - `portfolio_hub_token`과 `instance_key`는 `.env`에 넣지 않는다 — 기존 `config.py`의 `from_env()` 패턴 단독으로는 이 원칙을 지킬 수 없으므로 TOML 로더가 필수다. - -## 주요 사용자 시나리오 - -- orb-v1 전략이 2개의 portfolio를 독립적으로 운영하려는 경우 — 인스턴스A(`instance_key: inst-1`)와 인스턴스B(`instance_key: inst-2`)를 각자의 TOML 파일로 구동해 WatchdogStore/SafetySellService 독립 동작을 보장한다. -- 전체 인스턴스에 global kill-switch를 발동하는 경우 — `trex.broadcast` exchange에 publish하면 모든 인스턴스의 control 큐에 동시 전달되어 인스턴스별 명시 없이 전역 제어가 가능하다. -- 단일 인스턴스 운영을 유지하면서 향후 다중 인스턴스로 전환하는 경우 — TOML 파일에 `instance_key = "default"`로 두고, 추후 인스턴스별 키를 달리해 큐를 분리한다. - -# 4. 이번에 정의할 변경 - -- **per-instance TOML 설정 파일 도입**: 데몬 기동 시 `--config .toml` CLI 인자로 인스턴스별 TOML 파일을 지정한다. 최소 필수 필드: `instance_key`(인스턴스 식별자), `portfolio_hub_token`(portfolio-hub 인증 토큰). 이 두 값은 `.env`에 넣지 않는 것을 원칙으로 한다. `config.py`에 `DaemonInstanceConfig.from_toml(path)` 로더를 추가하고, 기존 `DaemonConfig.from_env()`와 병합해 최종 설정을 구성한다. -- **큐명 동적화 및 `instance_key` 기반 suffix**: `topology.py`의 고정 큐명 상수를 `{base_name}.{instance_key}` 형식의 동적 함수로 교체한다. `daemon.py`가 TOML에서 읽은 `instance_key`를 `declare_topology(instance_key)`에 전달한다. -- **`trex.direct`(topic exchange) 도입 + per-instance 큐 바인딩**: `trex.direct` exchange(type: topic, durable)를 신설하고, 각 인스턴스의 intent/control/snapshot 큐를 routing key `{instance_key}.intent`, `{instance_key}.control`, `{instance_key}.snapshot`으로 바인딩한다. -- **`trex.broadcast`(fanout exchange) 도입 + control 큐 dual binding**: `trex.broadcast` exchange(type: fanout, durable)를 신설하고, 각 인스턴스의 control 큐를 이 exchange에 추가 바인딩한다. global kill-switch 등 전체 인스턴스 대상 제어 명령은 이 exchange에 publish한다. -- **baseline 문서 갱신**: `docs/runtime-flows.md`와 `docs/project-structure.md`에 multi-instance topology 구조와 TOML 기반 기동 흐름을 반영한다. - -## 영향 범위 - -- 코드 영향: - - `trex_core/runtime/config.py`: `DaemonInstanceConfig` dataclass 추가(`instance_key`, `portfolio_hub_token`). `from_toml(path)` 로더 추가. 기존 `DaemonConfig.from_env()`는 유지하되, TOML 로더와 병합하는 `from_config(toml_path)` 팩토리 추가. - - `trex_core/adapters/messaging/topology.py`: 고정 큐명 상수 → 동적 함수(`intent_queue(instance_key)` 등). `DIRECT_EXCHANGE`, `BROADCAST_EXCHANGE` 상수 추가. - - `trex_core/adapters/messaging/rabbitmq.py`: `declare_topology(instance_key)` signature 변경. topic/fanout exchange 선언 + per-instance 큐 바인딩 로직 추가. - - `trex_core/runtime/daemon.py`: `--config` CLI 인자 파싱. TOML에서 `instance_key` 읽기. 큐 선언/구독 시 `instance_key` 전달. - - 진입점(`trex_core/__main__.py` 또는 동등): `--config ` CLI 인자 추가. -- 문서 영향: - - `docs/runtime-flows.md`: multi-instance topology 흐름 추가(directed vs broadcast 분기), TOML 기반 기동 절차 반영 - - `docs/project-structure.md`: topology.py 역할 변경(동적 큐명 빌더), 설정 파일 계층 반영 - - `docs/interface-contract.md`: routing key 계약 갱신 — `{instance_key}.{channel_type}` 규칙 추가 -- 운영 영향: - - 인스턴스별 TOML 파일 생성 및 관리 필요(`instance_key`, `portfolio_hub_token` 포함) - - 기존 `trex.intent.req` 등 old 큐는 새 큐 전환 후 자연 소멸을 허용한다. RabbitMQ admin console에서 적절한 시점에 수동 삭제(OQ-1 Answered). - - `TREX_PORTFOLIO_HUB_TOKEN` env var는 TOML로 이관 후 `.env`에서 제거 필요 - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: 데몬 기동 시 `--config .toml` CLI 인자는 필수다. 인자 없이 기동하면 오류 메시지를 출력하고 즉시 종료한다. TOML 파일에서 최소 `instance_key`와 `portfolio_hub_token`을 읽는다. 이 두 값은 `.env`에서 읽지 않는다. -- FR-2: `instance_key` 값을 기반으로 모든 수신 큐명을 `{base}.{instance_key}` 형식으로 suffix화한다. -- FR-3: `trex.direct`(type: topic, durable) exchange를 선언하고, 각 인스턴스의 intent/control/snapshot 큐를 routing key `{instance_key}.intent`, `{instance_key}.control`, `{instance_key}.snapshot`으로 바인딩한다. -- FR-4: `trex.broadcast`(type: fanout, durable) exchange를 선언하고, 각 인스턴스의 control 큐를 이 exchange에 추가 바인딩(dual binding)한다. 전체 인스턴스 대상 제어 명령은 이 exchange에 publish한다. -- FR-5: `instance_key = "default"` 단일 인스턴스 운영 시 기존 intent/snapshot/control RPC 경로와 동작이 동일하게 유지된다(하위 호환 검증 기준: 기존 테스트 스위트 회귀 없음). -- FR-6: `docs/runtime-flows.md`와 `docs/project-structure.md`를 multi-instance topology 및 TOML 기반 기동 기준으로 갱신한다. - -## 비기능 요구사항 - -- NFR-1: `instance_key = "default"` TOML로 배포된 환경에서 토폴로지 변경 후 재시작 시 동작 회귀가 없어야 한다(pytest 전체 통과 기준). -- NFR-2: 기존 pika `BlockingConnection` per-thread 패턴을 유지한다. exchange 바인딩 추가로 인해 추가 스레드 또는 커넥션을 생성하지 않는다. -- NFR-3: 인스턴스별 독립 큐 사용 시 WatchdogStore, SafetySellService가 자신의 인스턴스 슬롯만 처리하므로 split-brain이 발생하지 않는다(다중 인스턴스 e2e 시나리오 통과). -- NFR-4: `portfolio_hub_token`과 `instance_key`가 코드/env/로그에 평문으로 노출되지 않는다. TOML 파일은 git에 커밋하지 않는다(`.gitignore` 추가). - -## 범위 경계 - -- In Scope - - per-instance TOML 설정 파일 로더(`instance_key`, `portfolio_hub_token`) - - `instance_key` 기반 큐명 suffix화 - - `trex.direct`(topic exchange) 신설 + per-instance 큐 바인딩 - - `trex.broadcast`(fanout exchange) 신설 + control 큐 dual binding - - daemon.py/topology.py/rabbitmq.py/config.py/진입점 변경 - - 인터페이스 계약(`docs/interface-contract.md`) routing key 규칙 추가 - - `docs/runtime-flows.md`, `docs/project-structure.md` 갱신 - - `.gitignore`에 `*.toml` 또는 `config/*.toml` 추가(인스턴스 설정 파일 git 제외) -- Out of Scope - - 전체 설정을 TOML로 일원화(KIS 자격증명 등 나머지 env var의 TOML 이관 — 별도 논의) - - strategy 모듈(orb-v1) 코드 변경 — 인터페이스 계약만 정의, 구현은 strategy 모듈 범위 - - WatchdogStore Redis 외부화 — competing consumer 방식(별도 Discovery) - - active-passive failover / leader election - - 기존 old 큐 자동 삭제 스크립트 — 운영 절차로 처리 - - 인스턴스↔portfolio 매핑 레지스트리 (trex-core 내부 DB 불필요) - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (Medium): strategy 모듈이 routing key를 변경하지 않으면 다중 인스턴스 환경에서 메시지가 올바른 인스턴스에 도달하지 않는다. - - 완화 방안: 인터페이스 계약에 routing key 규칙을 명시한다. `instance_key = "default"` 단일 인스턴스 시에는 routing key `default.intent` 등으로 publish하면 하위 호환 유지 가능. -- R-2 (Medium): 기존 durable 큐(`trex.intent.req` 등)가 RabbitMQ에 잔류하면 새 큐로 전환 후 old 큐에 consumer가 없어진다. - - 완화 방안: 자연 소멸 허용. 배포 후 old 큐에 새로 publish되는 메시지가 없으므로 메시지 누적 없음. 적절한 시점에 RabbitMQ admin console에서 수동 삭제(OQ-1 Answered). -- R-3 (Low): `trex.broadcast` exchange 메시지가 모든 인스턴스에 동시 전달되므로 인스턴스별로 동일 제어 명령을 중복 처리한다. - - 완화 방안: kill-switch/pause 핸들러에 idempotency 보장(REQ-036 패턴, DB advisory lock). 중복 처리는 무해하게 동작. -- R-4 (Low): TOML 설정 파일이 실수로 git에 커밋되면 `portfolio_hub_token` 등 민감값이 노출된다. - - 완화 방안: `.gitignore`에 인스턴스 TOML 경로 추가(FR-1 범위). TOML 파일은 배포 전용 경로에 위치시킨다. - -## 가정 - -- A-1: 현재 배포 환경은 단일 인스턴스 운영이다. 다중 인스턴스 전환은 향후 필요 시점에 진행한다. -- A-2: strategy 모듈(orb-v1)은 인터페이스 계약 변경에 맞춰 routing key를 업데이트할 수 있다. -- A-3: 인스턴스별 담당 portfolio 매핑은 TOML 설정 또는 strategy 모듈의 routing 설정으로 관리한다. trex-core 내부에 레지스트리 DB를 두지 않는다. -- A-4: `portfolio_hub_token`과 `instance_key`를 `.env`에서 TOML로 이관하면 기존 `.env` 기반 배포 스크립트를 수정해야 한다. KIS 자격증명 등 나머지 env var는 이번 범위에서 이관하지 않는다. -- A-5: TOML 파일 경로는 `--config` CLI 인자로 지정한다. `--config` 인자는 필수이며, 지정하지 않으면 데몬이 오류 메시지를 출력하고 즉시 종료한다(OQ-2 Answered). 기본값 fallback은 제공하지 않는다. - -## 오픈 질문 - -- OQ-1: 기존 durable 큐(`trex.intent.req` 등) 처리 방식 — 자연 소멸(수동 삭제) vs 배포 절차에 명시적 삭제 포함? - - 상태: Answered - - 결정: 자연 소멸 허용. 배포 후 old 큐에 새로 publish되는 메시지는 없으므로 메시지 누적 없음. RabbitMQ admin console에서 적절한 시점에 수동 삭제. 배포 절차에 명시적 삭제 단계를 추가하지 않는다. - - 반영 위치: R-2 완화 방안 -- OQ-2: `--config` 인자 없이 데몬 기동 시 동작 — 오류 종료(필수) vs `instance_key = "default"` 기본값으로 기동? - - 상태: Answered - - 결정: `--config` 필수. 인자 없이 기동 시 오류 메시지 출력 후 즉시 종료. 기본값 fallback 없음. 인스턴스 식별이 보장되지 않은 상태로 기동하는 것은 다중 인스턴스 환경에서 split-brain 위험을 내포하므로 명시적 실패가 안전하다. - - 반영 위치: A-5, FR-1 - -### 오픈 질문 처리 기준 - -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. - -# 7. 초기 성공 기준 - -- S-1: `--config inst-1.toml`(`instance_key = "inst-1"`)으로 인스턴스 기동 시 `trex.intent.req.inst-1`, `trex.snapshot.req.inst-1`, `trex.control.req.inst-1` 큐가 RabbitMQ에 선언되고, `trex.direct` exchange에 올바른 routing key로 바인딩된다. -- S-2: `instance_key = "inst-1"`과 `instance_key = "inst-2"` 인스턴스를 동시 기동했을 때, `inst-1` routing key로 publish된 메시지가 `trex.intent.req.inst-1` 큐에만 전달된다. -- S-3: `trex.broadcast` exchange에 publish된 제어 메시지가 `trex.control.req.inst-1`과 `trex.control.req.inst-2` 큐 모두에 전달된다. -- S-4: `instance_key = "default"` TOML로 기동 시 기존 pytest 스위트(단위 + 회귀) 전체 통과 — 동작 회귀 없음. -- S-5: `portfolio_hub_token`이 `.env`에 없고 TOML에서만 읽혀 `PortfolioHubConfig.token`에 올바르게 주입된다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: RabbitMQ Management API(`GET /api/queues`, `GET /api/bindings`) 또는 pika 큐 선언 성공 로그 / 데이터 출처: 통합 테스트 또는 manual e2e -- S-2 측정 방식: 두 인스턴스 기동 후 `inst-1` routing key로 intent publish → `trex.intent.req.inst-1` 메시지 카운트 +1, `trex.intent.req.inst-2` 카운트 0 확인 / 데이터 출처: RabbitMQ Management API 또는 통합 테스트 -- S-3 측정 방식: broadcast exchange에 publish → 두 인스턴스 control 큐 모두 메시지 카운트 +1 / 데이터 출처: 통합 테스트 또는 manual e2e -- S-4 측정 방식: `pytest` 전체 실행 → 0 failures / 데이터 출처: pytest 출력 -- S-5 측정 방식: `.env`에 `TREX_PORTFOLIO_HUB_TOKEN` 미설정 상태에서 TOML로 기동 → `PortfolioHubConfig.token` 값 확인 / 데이터 출처: 단위 테스트(config loader) - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1(old 큐 처리) Answered, OQ-2(TOML 필수) Answered -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 각각 테스트 또는 e2e 근거 확정 — S-1~S-5 측정 방식 및 데이터 출처 섹션 7에 전부 기입됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1~R-4 전부 Medium/Low — High 영향 리스크 없음. 모든 리스크에 완화 방안 기입됨. REQ 단계 인계 확정. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1, OQ-2 모두 Answered -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 확인 — Section 11 Confirmed By: Justin Ko - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-009_20260607_multi-instance-rabbitmq-topology.md -- 참조 문서: - - trex_core/adapters/messaging/topology.py (고정 큐명 상수 현황 — 변경 대상) - - trex_core/adapters/messaging/rabbitmq.py (declare_topology 현황 — 변경 대상) - - trex_core/runtime/daemon.py (consumer thread 현황 — instance_key 추가 대상) - - trex_core/runtime/config.py (DaemonConfig.from_env() — TOML 로더 추가 대상) - - docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md (원래 RabbitMQ 도입 Discovery — 참조 기준) - - docs/runtime-flows.md (baseline 문서 — 갱신 대상, FR-6) - - docs/project-structure.md (baseline 문서 — 갱신 대상, FR-6) - -# 10. 사용자 결정 필요 항목 요약 - -## CONFIRM - -- CONFIRM_STRATEGY_MODULE_OUT_OF_SCOPE: 확인됨. strategy 모듈(orb-v1)의 routing key 구현 변경은 이번 Discovery 범위 밖. trex-core는 exchange·바인딩 계약만 정의하며, strategy 모듈의 publish 코드 변경은 별도 Discovery 또는 REQ에서 다룬다(Section 5 Out of Scope 반영). -- CONFIRM_ENV_RESIDUAL: 확인됨. KIS 자격증명(`TREX_KIS_*`), PostgreSQL DSN, TREX_RABBITMQ_URL 등 기존 env var는 이번 범위에서 `.env` 유지. TOML 이관 대상은 `portfolio_hub_token`과 `instance_key`만(Section 5 Out of Scope + A-4 반영). - -## DATA - -- DATA_TOML_SCHEMA_FULL: REQ 단계 확정. 최소 필드: `instance_key` (string), `portfolio_hub_token` (string). 추가 필드는 REQ 단계 스키마 정의에서 확정한다. -- DATA_MULTI_INSTANCE_TRIGGER_TIMING: REQ/배포 단계 계측 항목. Discovery 단계 범위 밖. 실제 다중 인스턴스 운영 계획은 배포 시점 REQ에서 반영한다. - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: TOML 설정 파일 도입 + instance_key 기반 토폴로지 설계 완료. OQ-1(old 큐 자연 소멸), OQ-2(TOML 필수) 모두 Answered. CONFIRM/DATA 항목은 REQ 단계에서 확정 가능 — confirm-discovery 후 REQ 초안 작성 진행 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-07 diff --git a/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md b/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md deleted file mode 100644 index 5c992b6..0000000 --- a/docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md +++ /dev/null @@ -1,219 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-010_20260607_safety-sell-persistent-rejection-backoff -- 이슈명: safety-sell 영속 거부 재시도 백오프 — 장 휴장·잔고 부족·규제 등 지속 거부 상황에서 KIS 주문 spam 방지를 위한 슬롯별 재시도 backoff/cooldown 정책 -- 작성 시각(KST): 2026-06-07 -- 마지막 갱신 시각(KST): 2026-06-08 (draft-req: REQ-052~054 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-052, REQ-053, REQ-054 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 참조 파일: trex_core/application/safety_sell.py, trex_core/application/execution.py, trex_core/domain/slot/service.py - - 관련 TODO/메모: REL-003 Feedback Handoff — Change Request Input "safety-sell 영속 거부 시 재시도 백오프/쿨다운(신규, 우선순위 ↑)"로 기록됨. 2026-06-07 실 KIS LIVE SL/TP e2e에서 직접 확인된 운영 위험. -- 해석: - - `SafetySellService`가 매도 시도(`HOLDING → PENDING_SELL`)에 성공해도, 브로커 거부로 인한 `PENDING_SELL → HOLDING` 롤백은 별도 `trex-execution` 스레드에서 일어나며 safety-sell이 이를 인지할 수 없다. 결과적으로 트리거 조건이 해소되지 않는 한 동일 슬롯에 매도를 무한 재제출한다. - - 이번 Discovery는 슬롯별 마지막 시도 시각을 `SafetySellService` 인메모리로 추적하는 단순 쿨다운을 도입해, 추가 스레드·DB 스키마 변경 없이 KIS 주문 spam을 방지하는 방법을 정의한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-007(core 자율 안전 매도, confirmed + Implemented)은 watchdog 기반 triggered sell 메커니즘을 정의했으나, 브로커 거부 시 재시도 제한·쿨다운·백오프 정책은 전혀 포함하지 않는다. - - 현재 어떤 Discovery/REQ도 `SafetySellService`의 per-slot 재시도 상태 추적, 최소 재시도 간격, 영속 거부 운영자 경고를 다루지 않는다. - -# 3. 문제점의 요약 - -- 현재 상태 - - `trex_core/application/safety_sell.py`: `SafetySellService._safe_sell()`이 `SlotService.sell()` 호출 성공(HOLDING → PENDING_SELL) 시 즉시 `True`를 반환한다. 브로커가 해당 주문을 거부하는지는 알 수 없다. - - `trex_core/application/execution.py`: `_rollback()`이 PENDING_SELL 거부 슬롯을 `PENDING_SELL → HOLDING`으로 전환하고 `clear_dispatch()`한다. 이 사실이 `SafetySellService`에 전달될 피드백 채널이 없다. - - `SafetySellService`에는 per-slot 마지막 시도 시각, 시도 횟수, 쿨다운 만료 시각 등 재시도 상태가 전혀 없다. - - 기본 cadence: stop-loss/take-profit은 30s마다, forced_close는 60s마다 트리거를 재평가한다. 장 휴장(9시간)에서 stop-loss 트리거가 지속되면 최대 9×3600÷30 = 1,080회 KIS 매도 주문이 제출·거부된다. - - paper 브로커는 `reject_symbols` 설정 없이는 주문을 거부하지 않으므로 테스트 환경에서 이 문제가 재현되지 않았다. -- 기대 상태 - - `SafetySellService`가 슬롯별 마지막 매도 시도 시각을 인메모리로 추적한다. - - 이전 시도로부터 `min_retry_interval_sec` 이내인 슬롯에는 재시도를 건너뛴다. - - `min_retry_interval_sec` 경과 후 트리거 조건이 여전히 충족되면 자동으로 재시도한다. - - 일정 횟수 이상 재시도가 발생한 슬롯에 대해 운영자 경고 로그를 발행한다. - - DB 스키마 변경 없이 인메모리 상태만으로 구현한다. -- 이해관계자 - - 운영자(Justin Ko): KIS 쿼터 보호 및 휴장/규제 상황에서의 안전 운용 책임 - - trex-core 데몬 개발자: `SafetySellService` + `SafetySellConfig` 수정 범위 판단 - - KIS API: 분당/시간당 쿼터 제한 존재. 영속 거부 시 재제출이 쿼터를 소모해 정상 거래일 운용에 영향 가능. -- 제약사항 - - `SafetySellService`와 `ExecutionService`는 별도 스레드이며 공유 데이터 구조에 직접 접근하지 않는다. 피드백 채널 신설은 스레드 간 결합을 증가시키므로 본 Discovery 범위 밖. - - 쿨다운 상태는 데몬 재시작 시 초기화된다. 재시작 직후 1회 재시도는 허용 범위로 본다. - - `SafetySellService`는 단일 스레드에서 호출되므로 쿨다운 `dict` 접근에 별도 락 불필요. - - 브로커 거부 원인 코드(`ack.reason`)를 `SafetySellService`가 직접 수신하는 설계는 이번 Discovery 밖. - -## 주요 사용자 시나리오 - -- (휴장) KIS 장 휴장일에 stop-loss 임계값이 설정된 holding 슬롯이 있을 때 — 쿨다운 없이 30s마다 KIS에 매도 제출·거부가 반복됨. 쿨다운 도입 후: 첫 거부 후 `min_retry_interval_sec` 동안 재시도 없음, 장 재개 후 트리거 조건 재평가. -- (잔고 부족) 계좌 잔고 부족으로 KIS가 매도를 거부하는 경우 — 동일 패턴으로 반복 거부. 쿨다운 중 운영자 경고 로그가 발행되어 수동 조치 가능. -- (규제) 특정 종목 매도 규제가 일시적으로 적용된 경우 — 쿨다운이 규제 해제 시점까지 재시도를 막고, 이후 자동 재평가. -- (정상 거래일) 워치독 만료 슬롯에 stop-loss 트리거 발동 → KIS 매도 접수·체결 → 슬롯 CLOSED → `_holding()`에서 제외. 쿨다운 미발동, 동작 변화 없음. - -# 4. 이번에 정의할 변경 - -- **`SafetySellService` per-slot 쿨다운 추적 도입**: `_last_attempt: dict[UUID, datetime]`와 `_attempt_count: dict[UUID, int]`를 추가한다. `_safe_sell()` 호출 직전 쿨다운 체크를 수행하고, 시도 후 `last_attempt`와 `attempt_count`를 갱신한다. 슬롯이 CLOSED 또는 IDLE로 전환되면(= `_holding()`에서 사라지면) 해당 슬롯의 쿨다운 상태를 자동 정리한다. -- **`SafetySellConfig` 확장**: `min_retry_interval_sec`(기본값 `600`, OQ-1 Answered), `max_retry_warn_count`(기본값 `3`, OQ-2 Answered) 두 env var을 추가한다. 기존 5종 env var(`TREX_WATCHDOG_TIMEOUT` 등)과 같은 방식으로 optional 파싱한다. -- **운영자 경고 로그**: `attempt_count >= max_retry_warn_count`인 슬롯에 대해 `_log.warning("safety-sell repeated rejection: slot=%s count=%d reason=persistent_rejection", ...)` 경고를 발행한다. 이 경고는 쿨다운 체크 통과 후 재시도 직전에 발행한다. - -## 영향 범위 - -- 코드 영향: - - `trex_core/application/safety_sell.py`: `SafetySellService.__init__` 인스턴스 변수 추가(`_last_attempt`, `_attempt_count`). `_safe_sell()` 호출 전후에 쿨다운 check/update 로직 추가. 슬롯 추적 정리 로직 추가. - - `trex_core/runtime/config.py`: `SafetySellConfig`에 `min_retry_interval_sec: int`, `max_retry_warn_count: int` 필드 추가. `from_env()`에서 `TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, `TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT` 파싱 추가. -- 문서 영향: - - `docs/runtime-flows.md`: safety-sell worker tick 흐름에 "쿨다운 체크 → 건너뜀 or 재시도" 단계 추가 -- 운영 영향: - - `TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, `TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT` 두 env var 신설(전부 optional, 기본값 있음). 기존 `.env` 구조 호환. - - 데몬 재시작 시 쿨다운 상태 초기화. 재시작이 의도적이라면 허용 범위. - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: `SafetySellService`는 슬롯별 마지막 매도 시도 시각(`last_attempt_at`)과 연속 시도 횟수(`attempt_count`)를 인메모리로 추적한다. 각 트리거(`forced_close`, `stop_loss`, `take_profit`, `market_close`)가 `_safe_sell()`을 호출하기 전에 해당 슬롯의 쿨다운 상태를 확인하고, `now - last_attempt_at < min_retry_interval_sec`이면 해당 슬롯을 건너뛴다. -- FR-2: 쿨다운 만료(`now - last_attempt_at >= min_retry_interval_sec`) 후 트리거 조건이 여전히 충족되면 재시도한다. 재시도 성공 여부와 무관하게 `last_attempt_at`을 갱신한다. -- FR-3: `attempt_count >= max_retry_warn_count`인 슬롯에 대해 재시도 직전에 `WARNING` 로그를 발행한다. 로그에는 `slot_id`, `attempt_count`, `reason`, `last_attempt_at`을 포함한다. -- FR-4: 슬롯이 HOLDING 상태를 벗어나 `_holding()` 목록에서 사라지면(CLOSED, IDLE 등) 해당 슬롯의 `last_attempt_at`과 `attempt_count` 항목을 제거한다. -- FR-5: `SafetySellConfig`에 `min_retry_interval_sec`(`TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, optional, 기본값 `600`)과 `max_retry_warn_count`(`TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`, optional, 기본값 `3`) 두 env var를 추가한다. 미설정 시 기본값으로 동작한다. -- FR-6: `docs/runtime-flows.md`를 갱신해 safety-sell tick 흐름에 "쿨다운 체크" 단계를 반영한다. - -## 비기능 요구사항 - -- NFR-1: 쿨다운 상태는 `SafetySellService` 인메모리 `dict`로만 유지한다. DB 스키마 변경(마이그레이션) 없음. -- NFR-2: `SafetySellService`는 단일 스레드에서 호출되므로 쿨다운 `dict` 접근에 별도 락을 두지 않는다. 다중 스레드 접근이 발생하는 구조 변경은 이번 범위 밖. -- NFR-3: `instance_key = "default"` 환경에서 기존 pytest 스위트(단위 + 회귀) 전체 통과 — 동작 회귀 없음. -- NFR-4: paper 브로커 `reject_symbols` 설정으로 거부를 시뮬레이션했을 때 쿨다운이 발동하고, 쿨다운 만료 후 재시도함을 단위 테스트로 검증한다. - -## 범위 경계 - -- In Scope - - `SafetySellService` 인메모리 per-slot 쿨다운 추적 - - `SafetySellConfig` 두 env var 확장(`min_retry_interval_sec`, `max_retry_warn_count`) - - 영속 거부 시 운영자 WARNING 로그 - - `docs/runtime-flows.md` safety-sell 흐름 갱신(FR-6) -- Out of Scope - - `ExecutionService` → `SafetySellService` 거부 사유 피드백 채널 신설 - - 브로커 거부 원인별(휴장/잔고/규제) 차등 쿨다운 정책 - - 쿨다운 상태의 DB 영속화(재시작 후 쿨다운 복원) - - 수동 쿨다운 해제 API(`resume_kill_switch` 유사) - - market_close 트리거에 대한 별도 쿨다운 분리 — market_close는 하루 1회 발동(already guarded by `_last_liquidation_date`) - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (Medium): 쿨다운 중 트리거 조건이 실제로 해소됐을 때(예: 슬롯 수동 청산, 가격 회복) 불필요한 경고 로그가 계속 발행될 수 있다. - - 완화 방안: 슬롯이 `_holding()`에서 사라지면 쿨다운 상태를 즉시 정리한다(FR-4). 가격 회복으로 트리거 조건 자체가 해소되면 `check_prices()`에서 `_safe_sell()`을 호출하지 않으므로 `attempt_count` 증가도 없다. -- R-2 (Low): 데몬 재시작 시 쿨다운 초기화 → 재시작 직후 영속 거부 슬롯에 1회 즉시 재시도 발생. - - 완화 방안: 재시작은 의도적 행위이므로 1회 재시도는 허용. 재시작 후 재시도가 또 거부되면 쿨다운에 즉시 재진입. 문서에 운영 주의사항으로 명시. -- R-3 (Low): paper 브로커(`reject_symbols` 미설정)에서는 쿨다운이 발동하지 않아 단위 테스트 범위가 제한될 수 있다. - - 완화 방안: paper 브로커 `reject_symbols`에 테스트 대상 종목을 설정해 거부를 시뮬레이션하는 단위 테스트를 추가(NFR-4). - -## 가정 - -- A-1: 영속 거부의 주요 원인(휴장, 잔고 부족, 규제)은 수십 분~수 시간 지속된다. `min_retry_interval_sec = 600s`(기본값)는 일시 거부 원인이 해소되기를 기다리는 중간값으로 적합하다(OQ-1 Answered). -- A-2: 슬롯이 쿨다운 중 HOLDING으로 돌아온 것은 이전 매도 시도가 브로커에서 거부됐음을 의미한다. 별도 피드백 채널 없이도 `_holding()` 재등장으로 암묵적 거부를 감지할 수 있다. -- A-3: market_close 트리거(`check_market_close`)는 하루 1회 `_last_liquidation_date`로 이미 중복 방지가 되어 있어 쿨다운 적용 대상에서 제외한다. -- A-4: `SafetySellService.run_once()`는 `trex-safety-sell` 단일 스레드에서만 호출되므로 쿨다운 `dict` 동시성 문제 없음. - -## 오픈 질문 - -- OQ-1: `min_retry_interval_sec` 기본값 — 어느 정도가 적합한가? (예: 300s / 600s / 1800s) - - 상태: Answered - - 결정: `600s` (10분). 대부분의 일시 거부 원인(잔고 부족, 일시 규제)이 해소되기를 기다리는 중간값. 휴장 9시간 기준 최대 54회 재시도로 쿼터 소모를 수용 가능한 수준으로 제한. - - 반영 위치: FR-5 env var 기본값, A-1 -- OQ-2: `max_retry_warn_count` 기본값 및 경고 이후 동작 — 경고 로그만? 아니면 N회 초과 시 쿨다운 무기한 전환 + 수동 해제? - - 상태: Answered - - 결정: `max_retry_warn_count = 3`, 경고 로그만 발행. 무기한 차단 없음. 3회(600s × 3 = 30분 지속 거부) 시점에서 WARNING 로그 발행으로 운영자 인지. 재시도는 쿨다운 만료 후 계속 허용. - - 반영 위치: FR-3, FR-5 env var 기본값 -- OQ-3: 트리거 종류별(`forced_close` vs `stop_loss/take_profit`) 쿨다운 분리 여부 — 단일 per-slot 쿨다운으로 통합할지, 트리거별로 독립 쿨다운을 유지할지? - - 상태: Answered - - 결정: 단일 per-slot 쿨다운. 거부 원인(휴장/잔고/규제)은 트리거 종류와 무관하게 슬롯·시장 상태에 종속적이므로 트리거별 분리는 복잡도만 늘린다. `_last_attempt: dict[UUID, datetime]` 단일 구조로 구현. - - 반영 위치: FR-1, Section 4 쿨다운 추적 설계 - -### 오픈 질문 처리 기준 - -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. - -# 7. 초기 성공 기준 - -- S-1: paper 브로커 `reject_symbols`에 테스트 종목을 설정한 상태에서 stop-loss 트리거 발동 후 `min_retry_interval_sec` 이내에 동일 슬롯 재시도가 없음을 확인한다. -- S-2: 쿨다운 만료 후 트리거 조건이 여전히 충족되면 재시도 1회 실행 및 `last_attempt_at` 갱신 확인. -- S-3: `attempt_count >= max_retry_warn_count` 도달 시 `WARNING` 로그가 발행되고 `slot_id`, `attempt_count`, `reason`이 포함됨을 확인. -- S-4: 슬롯이 CLOSED 전환 후 `_last_attempt`와 `_attempt_count`에서 해당 슬롯 항목이 제거됨을 단위 테스트로 확인. -- S-5: `instance_key = "default"` 환경에서 기존 pytest 스위트 전체 통과 — 동작 회귀 없음. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: `reject_symbols` 설정 + 단위 테스트에서 `run_once()` N회 호출 후 `_safe_sell` 호출 횟수 assert / 데이터 출처: pytest 단위 테스트 -- S-2 측정 방식: `last_attempt_at`을 과거 시각으로 설정 후 `run_once()` 호출 → `_safe_sell` 재호출 여부 + `last_attempt_at` 갱신 확인 / 데이터 출처: pytest 단위 테스트 -- S-3 측정 방식: `attempt_count`를 `max_retry_warn_count - 1`로 설정 후 `run_once()` → `caplog`에서 WARNING 로그 포함 여부 / 데이터 출처: pytest + `caplog` -- S-4 측정 방식: slot CLOSED 전환 후 `run_once()` → `_last_attempt[slot_id]` KeyError 또는 absent 확인 / 데이터 출처: pytest 단위 테스트 -- S-5 측정 방식: `pytest` 전체 실행 → 0 failures / 데이터 출처: pytest 출력 - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1(min_retry_interval_sec 기본값), OQ-2(max_retry_warn_count 및 경고 후 동작), OQ-3(쿨다운 분리 여부) 모두 Answered 또는 Deferred -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 각각 pytest 단위 테스트 또는 manual e2e 근거 확정 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1~R-3 완화 방안 REQ 단계 인계 여부 확인 (현재 Medium/Low — High 없음) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1, OQ-2, OQ-3 모두 Answered 또는 Deferred -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 확인 - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md -- 참조 문서: - - trex_core/application/safety_sell.py (SafetySellService — 변경 대상) - - trex_core/runtime/config.py (SafetySellConfig — env var 확장 대상) - - trex_core/application/execution.py (_rollback 로직 — 참조 기준, 직접 변경 없음) - - docs/runtime-flows.md (baseline 문서 — safety-sell tick 흐름 갱신 대상, FR-6) - - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md (원래 safety-sell Discovery — 참조 기준) - - docs/releases/rel-003_20260607_core-autonomous-safety-sell.md (REL-003 Feedback Handoff — 발견 근거) - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- DECIDE_MIN_RETRY_INTERVAL: 확정됨. `min_retry_interval_sec = 600s` (OQ-1 Answered). -- DECIDE_MAX_RETRY_WARN_AND_ACTION: 확정됨. `max_retry_warn_count = 3`, 경고 로그만 발행 (OQ-2 Answered). -- DECIDE_COOLDOWN_GRANULARITY: 확정됨. 단일 per-slot 쿨다운 (OQ-3 Answered). - -## CONFIRM - -- CONFIRM_MARKET_CLOSE_OUT_OF_SCOPE: 확인됨. `check_market_close`는 `_last_liquidation_date`(safety_sell.py:160)로 하루 1회 이미 보호되어 있어 본 Discovery의 per-slot 쿨다운 적용 대상 밖으로 확정됨(A-3). - -## DATA - -- DATA_REJECTION_DURATION_STATS: 비차단 운영 데이터. OQ-1이 600s로 확정되어 이 수치 없이도 REQ 초안 작성 가능. 실제 운용 후 수집된 데이터가 있다면 다음 Discovery 또는 change-req 입력으로 반영. - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: Safety-sell 쿨다운 메커니즘 설계 완료. OQ-1(600s), OQ-2(3회 경고 로그), OQ-3(단일 per-slot) 모두 Answered. confirm-discovery 전 Justin Ko 최종 승인 필요. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-07 diff --git a/docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md b/docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md deleted file mode 100644 index 89e459e..0000000 --- a/docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md +++ /dev/null @@ -1,237 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve -- 이슈명: buy intent — portfolio_id를 필수 키로, symbol은 universe(SnapshotStore)에서 resolve (+ portfolio_id 누락 체결의 ledger silent skip 가시화) -- 작성 시각(KST): 2026-06-08 11:08 KST -- 마지막 갱신 시각(KST): 2026-06-08 14:10 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-007(change-req CHG-20260608-01), REQ-018(change-req CHG-20260608-01), REQ-021(change-req CHG-20260608-01), REQ-055(신규) — delivery: BAT-015 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #6, issue #11 (fourblocks2020/trex-core) -- 해석: - - 매수(buy) intent 계약이 `symbol` 필수 / `portfolio_id` optional 로 되어 있어, 정식 식별자(asset_id=portfolio_id) 기반 원장 귀속·safety 보호 범위와 정합이 깨지는 문제를 정식 SDLC 주기로 착수한다. - - 이번 Discovery에서는 buy intent payload 계약을 `portfolio_id` 필수 + 서버측 `symbol` resolve 로 전환하는 범위/리스크/하위호환 경계를 판정한다. 더불어 issue #11 에서 관측된 동일 근본 원인(portfolio_id 없는 슬롯 체결이 ledger 에 silent skip 되어 데이터 누락·운영 가시성 부재)을 함께 흡수해, 예방(admission 거부)과 탐지/가시화(skip 영구 기록·경고)를 한 주기로 다룬다. 이미 Implemented·released(REL-003) 된 계약 변경이므로 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-007/021/018) `change-req` 를 우선하고, 기존 REQ 가 다루지 않는 신규 기능(운영 점검 쿼리 등)만 신규 REQ 로 생성한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-010) 중 buy intent 식별자 계약(portfolio_id 필수화 / symbol resolve)을 다룬 문서는 없다. dcy-004(원장 연동)·dcy-005(snapshot read)·dcy-007/dcy-008(safety)은 portfolio_id 를 전제하지만 buy 제출 계약 자체를 재정의하지 않았다. - - 변경이 이미 Implemented·released 된 buy intent 인터페이스 계약(REQ-007 제출, REQ-021/022 메시지 계약, REL-003 동작)에 닿아 핵심 식별자 의미를 바꾸므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/application/intents.py` 의 `BuyIntent` dataclass 는 `symbol: str`(필수, intents.py:50)이고 `portfolio_id: Optional[str] = None`(optional, intents.py:56) 이다. `_admit_buy`(intents.py:141~)는 `find_idle_slot` 으로 idle 슬롯을 자동 배정한 뒤 `symbol`/`portfolio_id` 를 슬롯에 그대로 기록한다 — symbol 은 필수로 들어오고 portfolio_id 는 없을 수 있다. - - `trex_core/application/messaging_handlers.py` buy 핸들러(73~88행)는 `symbol=str(body.get("symbol") or "")`, `portfolio_id=_opt_str(body.get("portfolio_id"))` 로 파싱해 현 계약(symbol 필수 추정, portfolio_id optional)을 그대로 반영한다. - - universe→symbol resolve 의 기반은 이미 존재한다: `trex_core/application/portfolio.py` 의 `SnapshotStore.get(portfolio_id)`(portfolio.py:130)가 `SnapshotAsset{portfolio_id, symbol, ...}` 를 반환한다. 즉 portfolio_id→symbol 조회 경로는 구현돼 있으나 admission 에서 사용하지 않는다. - - `docs/interface-contract.md` 의 buy payload 계약(79~86행)은 `"symbol": str` 을 기본 필드로, `"portfolio_id": str?` 를 optional 로 명시한다. 즉 문서 계약도 현 코드와 일치한다. - - (issue #11) `trex_core/application/execution.py` 의 `_record_ledger`(execution.py:153~164)는 `slot.portfolio_id` 가 falsy 면 `r.ledger_skipped += 1` 후 즉시 반환한다. 슬롯은 holding/closed 로 정상 전이되지만 `ledger_outbox` 에 행이 생기지 않아 portfolio-hub 에 거래가 전혀 기록되지 않는다. 스킵은 `ExecutionResult` 의 메모리 카운터로만 남고 로그·DB 어디에도 영구화되지 않아 운영자가 누락을 인지하기 어렵다(무성 실패). 실관측: 슬롯 `6e75f3ec-...`(symbol 316140)는 buy/sell 모두 체결돼 closed 됐으나 portfolio_id=None 으로 outbox 0건. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신이다. buy admission 은 기존 runtime flow 에 포함돼 있고, 이번 변경은 그 흐름에 symbol resolve 단계를 추가한다. -- 기대 상태 - - buy intent 는 `portfolio_id`(=asset_id, 정식 식별자) 를 필수로 요구하고, `symbol` 은 admission 시 서버가 `SnapshotStore` universe 에서 resolve 해 슬롯에 기록한다. - - portfolio_id 가 universe 에 없으면 명확한 reject(`unknown_portfolio`)로 처리되어, 원장 귀속 불가·safety 보호 누락 슬롯이 생기지 않는다. - - (issue #11) 만에 하나 portfolio_id/execution_id 누락으로 ledger skip 이 발생하더라도 무성으로 사라지지 않고 WARN/ERROR 로그·메트릭으로 노출되며, closed 슬롯 vs outbox 대조 점검 쿼리로 운영자가 누락을 탐지·집계할 수 있다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — 계약 변경 및 하위호환 영향 승인 - - 운영 담당자 — released(REL-003) buy 계약 변경에 따른 전략 모듈/배포 영향 관리 - - 전략 모듈 개발자 — buy intent 제출 호출부(payload) 변경 대상 -- 제약사항 - - 이미 released(REL-003) 된 인터페이스 계약 변경이므로 ad-hoc 수정이 아니라 정식 SDLC(change-req 또는 신규 Discovery) 경유가 필요하다. - - 매수 시점 universe(`SnapshotStore`)에 해당 portfolio_id 가 존재한다는 보장이 거래일/refresh 주기에 의존한다 — resolve 실패 처리 정책이 필요하다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략 모듈이 universe snapshot(`PortfolioItem{portfolio_id, symbol, ...}`)에서 매수 대상을 고른 뒤, buy intent 에 `portfolio_id` 만 실어 제출하면 core 가 symbol 을 universe 에서 resolve 해 슬롯·KIS 주문에 사용한다. -- 운영자가 특정 portfolio 를 pause/kill-switch 로 보호한 상태에서, portfolio_id 필수화 덕분에 모든 buy 슬롯이 보호 범위에 정확히 귀속된다. -- portfolio_id 가 당일 universe 에 없을 때(상장폐지·종목 제외 등) core 가 `unknown_portfolio` 로 매수를 거부해, 귀속 불가능한 체결이 원장에 들어가지 않는다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- buy intent payload 계약을 `portfolio_id` 필수로 전환하고, `symbol` 은 클라이언트 payload 에서 제거해 서버가 universe 에서 resolve 하는 값으로만 사용한다(OQ-1·OQ-2 결정 — override/fallback·레거시 호환 없음). -- admission(`_admit_buy`) 시 `SnapshotStore` 에서 `portfolio_id → symbol` 을 resolve 해 슬롯에 기록하고, universe 에 portfolio_id 부재 시 reject(`unknown_portfolio`) 처리한다. -- (issue #11 제안 ②) `_record_ledger` 의 ledger skip 을 무성에서 가시화로 전환한다 — skip 발생 시 WARN/ERROR 로그·메트릭 노출(OQ-4 결정 — outbox 마커 행은 도입하지 않음; defense-in-depth: portfolio_id 필수화 이후에도 잔존할 수 있는 누락을 탐지 가능하게). -- (issue #11 제안 ③) closed 슬롯 vs `ledger_outbox` 대조 운영 점검 쿼리/알람을 추가해 ledger 누락 슬롯을 주기·온디맨드로 탐지한다. -- 인터페이스 계약 문서(`docs/interface-contract.md`)와 buy admission runtime flow 기술을 새 계약에 맞춰 갱신한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/intents.py` — `BuyIntent`(portfolio_id 필수화, symbol 은 클라이언트 입력에서 제거하고 서버 resolve 로 채움), `_admit_buy`(symbol resolve + unknown_portfolio reject) - - `trex_core/application/messaging_handlers.py` — buy 핸들러 payload 파싱/검증(portfolio_id 필수 검사) - - `trex_core/application/portfolio.py` — `SnapshotStore` portfolio_id→symbol 조회 활용(기존 `get` 재사용, 신규 메서드 필요 시 추가) - - (issue #11) `trex_core/application/execution.py` — `_record_ledger` skip 지점 가시화(로그/메트릭). OQ-4 결정으로 `ledger_outbox` 스키마/`LedgerWriter` 변경은 없음(마커 행 미도입) - - (issue #11) 운영 점검 쿼리(FR-5) — `trex_core/adapters/persistence/sql/` 의 slots↔`ledger_outbox` anti-join 점검 SQL 추가(읽기 전용, 스키마 변경 없음) -- 문서 영향: - - `docs/interface-contract.md` — buy payload 계약(portfolio_id 필수, symbol 필드 제거·서버 resolve) 갱신 - - `docs/runtime-flows.md` — buy admission 흐름에 symbol resolve 단계 + ledger skip 가시화 반영 -- 운영 영향: - - released(REL-003) buy 계약 변경 → 전략 모듈 호출부 마이그레이션 안내 필요 - - 거래일 universe refresh 와 매수 시점 정합 운영 점검 필요 - - (issue #11) ledger skip 가시화 + 운영 점검 쿼리로 누락을 운영자가 탐지·집계 가능 → 회계 정합(P&L/포지션) 리스크 완화 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: buy intent payload 는 `portfolio_id` 를 필수 키로 요구하고, 누락 시 admission 단계에서 `portfolio_id_required` 로 거부한다. (issue #11 제안 ① — 근본 원인 제거) -- FR-2: admission 시 `SnapshotStore` universe 에서 `portfolio_id → symbol` 을 resolve 해 슬롯(및 후속 KIS 주문)에 사용할 symbol 로 기록한다. buy payload 는 클라이언트 symbol 을 받지 않는다(OQ-1·OQ-2 결정 — 완전 서버 resolve). -- FR-3: universe 에 해당 `portfolio_id` 가 없으면 `unknown_portfolio` 로 매수를 거부한다. (OQ-1 결정: 단일 reject 정책 — 클라이언트 symbol 로의 override fallback 은 두지 않는다.) -- FR-4: (issue #11 제안 ②) `_record_ledger` 의 ledger skip 발생 시 무성으로 반환하지 않고 WARN/ERROR 로그 + 메트릭으로 노출한다(OQ-4 결정 — `ledger_outbox` 마커 행은 도입하지 않고 영구 탐지는 FR-5 점검 쿼리에 위임). portfolio_id 누락·execution_id 누락 두 skip 경로 모두 포함한다. -- FR-5: (issue #11 제안 ③) closed 슬롯 중 대응 `ledger_outbox` 행이 없는 케이스를 anti-join 으로 식별하는 운영 점검 쿼리/알람을 제공한다(slots 테이블이 durable 하므로 outbox 마커 없이도 영구 탐지 성립 — OQ-4 영구성 위임). 주기 실행 또는 온디맨드 점검으로 누락을 운영자가 탐지·집계할 수 있어야 한다. -- FR-6: `docs/interface-contract.md` 와 `docs/runtime-flows.md` 를 새 buy 계약(portfolio_id 필수 + symbol resolve) 및 ledger skip 가시화/운영 점검에 맞춰 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 원장 정합성 — 모든 매수 슬롯이 `portfolio_id`(asset_id)에 귀속되어 outbox correlation_id(`asset_id:order_id:execution_id`) 및 portfolio-hub ledger 기록이 누락 없이 성립해야 한다. -- NFR-2: safety 일관성 — pause(REQ-032)·kill-switch(dcy-008)·포지션 정합(REQ-031)·snapshot masking 의 portfolio_id 기준 보호 범위에서 누락되는 buy 슬롯이 없어야 한다. -- NFR-3: 즉시 cutover 정합 — 하위호환을 두지 않으므로(OQ-2 결정), 기존 symbol 기반 호출부(전략 모듈/테스트)를 모두 식별해 portfolio_id 기반으로 동시 전환하고, 전환 누락 시 명확히 실패(거부)하도록 보장한다. -- NFR-4: (issue #11) 운영 가시성 — ledger skip 같은 회계 누락 이벤트는 메모리 카운터에만 남지 않고 로그/메트릭/DB 중 최소 하나로 영구 관측 가능해야 한다(무성 실패 금지). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - buy intent 계약의 portfolio_id 필수화 및 admission 시 symbol resolve - - universe 부재 시 reject 처리 및 인터페이스/런타임 문서 갱신 - - (issue #11 제안 ②) ledger skip 의 가시화(로그/메트릭) — defense-in-depth - - (issue #11 제안 ③) closed 슬롯 vs ledger_outbox 대조 운영 점검 쿼리/알람 (OQ-5 결정: 포함) -- Out of Scope - - sell intent 계약 변경(이미 portfolio_id/slot 기반으로 동작) - - universe refresh 주기/스케줄 자체의 재설계 - - symbol-only 레거시 호출 한시 허용(호환 창구) — OQ-2 결정으로 미지원(즉시 hard cutover) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): released(REL-003) buy 계약을 깨는 변경이라 기존 symbol 기반 전략 모듈/테스트 호출이 즉시 실패할 수 있다. - - 완화 방안: (OQ-2 결정 — 즉시 cutover) 호환 창구는 두지 않으므로, 전략 모듈·테스트 등 모든 호출부를 사전 식별해 동시에 portfolio_id 기반으로 전환하고, change-req 로 REQ-007/021(및 #11 관련 REQ-018) 변경 이력을 남긴다. -- R-2 (Medium): 매수 시점 universe(`SnapshotStore`)에 portfolio_id 가 없으면(refresh 타이밍·종목 제외) 정상 매수가 `unknown_portfolio` 로 거부될 수 있다. - - 완화 방안: resolve 실패 시 `unknown_portfolio` 단일 reject 정책(OQ-1)을 적용하고, 거부 시 진단 가능한 reason/로그를 남긴다. 거래일 universe refresh 와 매수 시점 정합은 운영 점검 대상으로 둔다. -- R-3 (Low, OQ-1·OQ-2 결정으로 해소): symbol override/fallback·레거시 호환을 모두 두지 않으므로, override 값과 resolve 값의 불일치 모호성이 발생하지 않는다. - - 완화 방안: symbol 은 클라이언트 입력 없이 항상 서버가 universe 에서 resolve 한 값만 사용한다. -- R-4 (Medium, issue #11): portfolio_id 필수화 이전(또는 마이그레이션 창구 중)에 발생한 누락 체결은 이미 ledger_outbox 에 행이 없어 탐지/대응이 늦어질 수 있다. - - 완화 방안: skip 가시화(FR-4)와 운영 점검 쿼리(FR-5)로 신규/잔존 누락을 탐지·집계한다. (과거 누락분의 데이터 복구는 #11 의 명시 요구가 아니므로 본 주기 범위 밖이며, 필요 시 별도 운영 이슈로 다룬다.) - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: `SnapshotStore` 가 admission 시점에 당일 universe 를 보유하며 `get(portfolio_id)` 로 symbol 을 신뢰성 있게 조회할 수 있다. -- A-2: 전략 모듈은 이미 universe snapshot 에서 portfolio_id 를 받아 매수 결정을 내리므로, payload 에서 symbol 을 빼고 portfolio_id 를 싣는 변경이 전략 측 로직에 큰 부담이 아니다. -- A-3: 이번 변경은 buy 경로에 한정되며 sell/kill-switch/pause 등 portfolio_id 기반 기능은 그대로 호환된다. -- A-4: (issue #11) `_record_ledger` 의 execution_id 누락 skip 경로도 동일한 가시화 처리 대상이며, ledger skip 가시화는 슬롯 전이 동작 자체를 바꾸지 않고 관측만 추가한다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: universe 에 portfolio_id 가 없을 때 처리 정책 — `reject(unknown_portfolio)` 단일 정책인가, symbol override fallback 을 허용하는가? - - 상태: Answered (2026-06-08, 사용자 결정 — `reject(unknown_portfolio)` 단일 정책) - - 결정: universe 부재 시 항상 `unknown_portfolio` 로 거부한다. 클라이언트가 보낸 symbol 로의 fallback 은 두지 않는다(원장 정합·식별자 유일성 우선). - - 반영 위치: FR-3, R-3(override fallback 경로 제거로 불일치 위험 해소) -- OQ-2: 하위호환 — 기존 symbol 기반 호출(전략 모듈/테스트)을 한시 허용하는 마이그레이션 창구를 둘지, 즉시 hard cutover 할지? - - 상태: Answered (2026-06-08, 사용자 결정 — 하위호환 없음 / 즉시 hard cutover) - - 결정: 호환 창구를 두지 않는다. symbol 은 payload 에서 제거하고, 모든 호출부를 portfolio_id 기반으로 동시 전환한다. 전환 안 된 호출은 `portfolio_id_required` 등으로 명확히 거부된다. - - 반영 위치: FR-1/FR-2, NFR-3, 범위 경계 Out of Scope, R-1 -- OQ-3: 거버넌스 경로 — 본 변경을 신규 REQ(이 Discovery 기반)로 추적할지, 기존 REQ-007/021/022 의 change-req 로 처리할지? - - 상태: Answered (2026-06-08, 사용자 결정 — 가능하면 기존 REQ change-req, 필요 시에만 신규 REQ) - - 결정: 기존 Implemented REQ 본문 수정으로 다룰 수 있는 변경은 `change-req` 로 처리한다. change-req 우선 대상 — REQ-007(intent 제출 API: portfolio_id 필수화), REQ-021(메시지 계약 매핑: buy payload 에서 symbol 제거), REQ-018(체결→원장 기록 fill hook: ledger skip 로그/메트릭 가시화). 기존 REQ 가 다루지 않는 신규 기능(closed 슬롯 vs ledger 대조 운영 점검 쿼리/알람, FR-5)은 신규 REQ 로 생성한다. (OQ-4 로 outbox 마커 행을 두지 않으므로 REQ-019 outbox durability 본문 변경은 원칙적으로 불필요 — REQ 정제에서 영향 확인 후 필요 시에만 포함.) - - 반영 위치: 4. 거버넌스 해석, R-1 완화 방안, 8. 확인 체크 -- OQ-4: (issue #11) ledger skip 영구 가시화 방식 — WARN/ERROR 로그+메트릭만으로 충분한가, `ledger_outbox` 에 `skipped` 상태 행까지 남겨 사후 대조 가능하게 할 것인가? - - 상태: Answered (2026-06-08, 사용자 결정 — 로그+메트릭만) - - 결정: skip 시 WARN/ERROR 로그 + 메트릭만 노출하고 `ledger_outbox` 마커 행은 도입하지 않는다. 근거 — skip 마커는 정작 outbox 행 식별자(portfolio_id/execution_id)가 비어 있는 기형 행이 되어 전송 큐 의미를 오염시키고 dispatcher 필터링·스키마 마이그레이션을 요구한다. 영구 탐지는 slots↔outbox anti-join(FR-5)으로 충족된다. 또한 OQ-1·OQ-2 로 portfolio_id 누락 skip 주원인이 admission 에서 제거되어 잔여 skip(execution_id None)은 드문 방어 케이스다. - - 반영 위치: FR-4, FR-5 -- OQ-5: (issue #11 제안 ③) closed 슬롯 vs ledger_outbox 대조 운영 점검 쿼리/알람을 이번 주기에 포함할지, 후속으로 분리할지? - - 상태: Answered (2026-06-08, 사용자 결정 — 포함) - - 결정: 운영 점검 쿼리/알람을 이번 주기 In Scope 로 포함한다(FR-5 반영). - - 반영 위치: FR-5, 범위 경계 In Scope, R-4 완화 방안 - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: portfolio_id 누락 buy intent 가 100% `portfolio_id_required` 로 거부되고, portfolio_id 만으로 제출한 buy 가 symbol resolve 를 거쳐 정상 admit 된다. -- S-2: admit 된 모든 매수 슬롯이 portfolio_id(asset_id)에 귀속되어 outbox correlation_id 와 ledger 기록이 누락 없이 생성된다. -- S-3: (issue #11) ledger skip 이 발생하는 테스트 시나리오에서 무성 반환이 아니라 WARN/ERROR 로그·메트릭이 100% 남는다. -- S-4: (issue #11 제안 ③) 운영 점검 쿼리가 ledger 누락 closed 슬롯을 정확히 식별한다(누락 슬롯 주입 시 검출, 정상 케이스 시 0건). -- S-5: 변경된 buy 계약이 `docs/interface-contract.md` 와 `docs/runtime-flows.md` 에 반영되고, 코드(intents.py / messaging_handlers.py / execution.py)와 문서가 일치한다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 단위/통합 테스트(portfolio_id 누락·정상·unknown_portfolio 케이스) 통과 / 데이터 출처: `_admit_buy`, buy 핸들러 테스트 결과 -- S-2 측정 방식: 매수 시나리오 후 슬롯·outbox·ledger 귀속 검증 / 데이터 출처: 슬롯 스냅샷, outbox correlation_id, portfolio-hub ledger 레코드 -- S-3 측정 방식: portfolio_id/execution_id 누락 강제 주입 테스트에서 로그·메트릭 발생 확인 / 데이터 출처: `_record_ledger` 테스트, 데몬 로그, 메트릭 -- S-4 측정 방식: 누락 슬롯 주입 후 운영 점검 쿼리 실행 결과 대조 / 데이터 출처: 점검 쿼리 결과, 슬롯·`ledger_outbox` 테이블 -- S-5 측정 방식: 코드-문서 정합 리뷰(diff 대조) / 데이터 출처: `docs/interface-contract.md`, `docs/runtime-flows.md`, 변경 코드 diff - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 하위호환 정책이 확정됨 (OQ-2 — 즉시 hard cutover, 호환 창구 없음). 호출부 전환 대상의 구체 식별·수정은 REQ/batch 단계 작업으로 넘김(비차단). -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1 의 change-req 대상(REQ-007/021/018, REQ-019 는 조건부) 추적 방식이 OQ-3 으로 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계 담당자 할당 문제로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-5 모두 `Answered`, `Open` 상태 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md -- 참조 문서: - - GitHub issue #6 (fourblocks2020/trex-core) — buy intent portfolio_id 필수화 제안 - - GitHub issue #11 (fourblocks2020/trex-core) — portfolio_id 없는 거래의 ledger silent skip 버그(동일 근본 원인, 가시화 범위 흡수) - - docs/interface-contract.md — 현행 buy payload 계약(symbol 필수 / portfolio_id optional) - - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md — asset_id=portfolio_id 원장 귀속 근거 - - trex_core/application/execution.py:153-178 — `_record_ledger` ledger skip 지점(issue #11) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — 모든 결정 항목이 OQ-1~OQ-5 확정으로 해소됨) - -> 결정 완료(2026-06-08): OQ-1 universe 부재 처리 = `reject(unknown_portfolio)` 단일 정책 / OQ-2 하위호환 = 없음(즉시 hard cutover) / OQ-3 거버넌스 = 기존 REQ change-req 우선·필요 시 신규 REQ / OQ-4 skip 가시화 = 로그+메트릭만(outbox 마커 없음) / OQ-5 운영 점검 쿼리·알람 = `포함`(FR-5). 모두 결정 항목에서 제외됨. - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(기존 REQ-007/021/018/019 change-req 우선, 신규 기능만 신규 REQ). - -## DATA -- 없음(차단 항목 기준) — 매수 시점 universe(SnapshotStore)에 대상 portfolio_id 존재 보장 여부는 운영 런타임 데이터로, REQ/delivery 단계에서 계측·확정한다. 부재 케이스는 FR-3 의 `unknown_portfolio` reject 와 R-2 로 이미 처리되므로 Discovery confirm 을 막지 않는다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 오픈 질문 OQ-1~OQ-5 모두 Answered (universe 부재=reject 단일 정책 / 하위호환=없음·즉시 cutover / 거버넌스=기존 REQ change-req 우선·필요 시 신규 REQ / skip 가시화=로그+메트릭만 / 운영 점검 쿼리=포함). C 유형 플레이스홀더 0개, 저장소 사실관계 일치 확인. universe portfolio_id 존재 보장(DATA)은 REQ/delivery 단계 계측 항목으로 비차단 분류. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-08 12:23 KST diff --git a/docs/discovery/dcy-012_20260608_sell-order-pricing-entry-price-bug.md b/docs/discovery/dcy-012_20260608_sell-order-pricing-entry-price-bug.md deleted file mode 100644 index 04ac920..0000000 --- a/docs/discovery/dcy-012_20260608_sell-order-pricing-entry-price-bug.md +++ /dev/null @@ -1,233 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-012_20260608_sell-order-pricing-entry-price-bug -- 이슈명: 매도 주문이 현재가가 아닌 entry_price(매수가) 지정가로 나가는 버그 — sell dispatch 가격 정책 부재 -- 작성 시각(KST): 2026-06-08 12:30 KST -- 마지막 갱신 시각(KST): 2026-06-08 15:40 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-013/007/036/002/006/003/015 (전부 change-req, 신규 REQ 없음 — OQ-3). REQ-007=CHG-20260608-02, 그 외=CHG-20260608-01. delivery: BAT-016 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #10 (fourblocks2020/trex-core), 관련 issue #9(pending_sell 갇힘) -- 해석: - - 매도(sell) 주문 집행 경로(`_dispatch`)가 buy/sell 구분 없이 `slot.entry_price`(매수가)를 지정가로 사용해, 매도가 현재 시장가가 아닌 매수가에 걸린 지정가 주문으로 나가는 정합성 버그를 정식 SDLC 주기로 착수한다. - - 이번 Discovery에서는 sell dispatch 의 가격 결정 정책(시장가 vs 현재가 지정가 vs 인텐트 전달 가격)을 판정하고, safety-sell(자율 매도)과 RPC 재량 매도에 어떤 정책을 적용할지 범위를 정한다. 이미 Implemented·released(REL-001/003) 된 집행 경로(dcy-003 broker execution) 변경이고, 매도 지정가 운반을 위해 슬롯 `entry_price` 를 side 무관 `limit_price` 로 내부 rename(Option A) 하므로, 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-013/007/036/002/006/003/015) `change-req` 로 처리하고 신규 REQ 는 생성하지 않는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-011) 중 sell dispatch 의 주문 가격 결정 정책을 다룬 문서는 없다. dcy-003(broker execution + outbox)은 `_dispatch`/주문 제출 경로를 도입했지만 buy/sell 가격 분기를 정의하지 않았고, dcy-007(safety-sell)은 손절/익절 트리거 조건과 `get_current_price` 평가를 다루되 집행 시 매도 주문 가격을 재정의하지 않았다. dcy-010(영속 거부 backoff)은 거부 재시도만 다룬다. - - 변경이 이미 Implemented·released 된 집행 경로(`_dispatch`)의 주문 가격/주문유형(지정가 vs 시장가) 의미를 바꾸고, 매도 인텐트 계약(`SellIntent`)에 optional 가격 필드를 추가하므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/application/execution.py` 의 `_dispatch`(execution.py:84~)는 `side = "buy" if PENDING_BUY else "sell"` 로 side 를 구하지만, 바로 다음 `limit = int(slot.entry_price) if slot.entry_price else None`(execution.py:91)에서 side 를 무시하고 buy/sell 공통으로 `entry_price`(매수가)를 지정가로 쓴다. `order_type` 은 `"00"(지정가) if limit is not None else "01"(시장가)`(execution.py:99)로 결정되므로, 매도도 매수가에 걸린 지정가 주문으로 나간다. - - 버그의 발현은 슬롯의 매수 유형에 의존한다. `entry_price` 는 지정가 매수일 때만 채워지고 시장가 매수에서는 NULL 로 남으며(체결가는 `avg_price` 에만 기록, 매수 체결 시 `entry_price` 미갱신), 매수 후에는 `_dispatch` 의 limit 계산이 유일한 행위적 소비처다. 따라서 (a) 지정가 매수로 진입한 슬롯은 매도 시 `entry_price`(매수가)로 지정가 매도가 나가 버그가 발현되고, (b) 시장가 매수로 진입한 슬롯은 `entry_price=NULL → limit=None → order_type="01"` 로 우연히 시장가 매도가 나가 정상 동작한다. 즉 issue #10 은 지정가 매수 슬롯에서 재현된다. - - `trex_core/application/intents.py` 의 `SellIntent`(intents.py:68~)는 `intent_id/slot_id/reason/sell_strategy_id/extra` 만 가지며 가격 필드가 없다. `_admit_sell`(intents.py:193~)은 `HOLDING → PENDING_SELL` 상태 전이만 수행하고 가격 파라미터를 넘기지 않아, 슬롯의 `entry_price` 는 매수 때 값 그대로 유지된다. - - `trex_core/application/broker_port.py` 의 `get_current_price(symbol)`(broker_port.py:94)는 존재하지만 `_dispatch` 에서 호출되지 않는다. 현재 이 메서드는 safety-sell 워커의 손절/익절 평가에서만 쓰인다(`trex_core/application/safety_sell.py:182`). - - `trex_core/domain/slot/models.py` 의 Slot 은 `avg_price`(매수 체결가, models.py:27)·`entry_price`(매수 지정가, models.py:28)만 가지며 매도 시점 시장가를 담을 필드가 없다. - - `docs/interface-contract.md`(92행)의 sell payload 계약은 `{intent_type:"sell", intent_id, slot_id, reason, sell_strategy_id}` 로, 가격 필드가 없다(현 코드와 일치). - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신이다. sell 집행은 기존 runtime flow(주문 dispatch)에 포함돼 있고, 이번 변경은 그 흐름의 매도 가격 결정 단계를 정정한다. -- 기대 상태 - - 매도(`pending_sell`) 집행 시 매수 지정가가 아니라 OQ-1 하이브리드 정책에 맞는 가격으로 주문이 나간다 — 인텐트가 가격을 지정하면 그 지정가, 미지정이면 시장가. (현재가 자동 조회 지정가는 미채택.) - - 손절(stop-loss) 상황에서는 가격 미전달로 시장가 매도가 나가 의도대로 체결되어, 미체결 매도가 `pending_sell` 에 갇히는 문제(issue #9)의 직접 원인이 제거된다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — 매도 가격 정책 및 집행 경로 변경 승인 - - 운영 담당자 — released 집행 경로 변경에 따른 실거래 영향 관리 - - 전략 모듈 개발자 — 재량 매도(RPC) 인텐트 호출부 변경 가능성 -- 제약사항 - - 이미 released(REL-001/003) 된 집행 경로 변경이므로 ad-hoc 수정이 아니라 정식 SDLC(change-req 또는 신규 REQ) 경유가 필요하다. - - KIS 주문 계약상 `ORD_DVSN`(00 지정가 / 01 시장가)과 `ORD_UNPR` 의미가 정해져 있어, 매도 가격 유무가 곧 주문유형·가격 필드 매핑을 결정한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- safety-sell 워커가 손절 조건(현재가 < stop_loss_price)으로 매도를 트리거했는데, 집행이 매수가 지정가로 나가 체결되지 않고 슬롯이 `pending_sell` 에 갇힌다 → 정책에 맞는 가격으로 즉시 체결되어야 한다. -- 전략 모듈이 RabbitMQ 로 재량 매도(sell intent)를 보내면, 현재 시장가 기반으로 매도가 집행되어 의도한 청산이 이뤄진다. -- 현재가가 매수가보다 높은 상황에서 매도 시, 매수가에 걸린 불리한 지정가가 아니라 시장가(또는 인텐트가 지정한 가격)로 체결되어 손실 없이 청산된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 슬롯의 매수 전용 `entry_price` 를 **side 무관 "현재 pending 주문 지정가" 단일 필드(내부 rename: `limit_price`)** 로 재정의한다. pending 진입 시(매수: buy intent 지정가 / 매도: sell intent optional 가격) set 하고, pending 이탈(→HOLDING, →CLOSED) 시 None 으로 clear 한다(매수 지정가의 매도 누수 = #10 근본 원인을 구조적으로 차단). 감사 이력은 기존처럼 transition_log 가 보존한다. -- `_dispatch` 는 side 분기로 가격을 고르지 않고 `slot.limit_price` 하나만 읽는다 — 값이 있으면 지정가(`order_type="00"`), 없으면 시장가(`order_type="01"`)(OQ-1 하이브리드). -- `SellIntent`/sell payload 에 optional 매도 가격 필드를 추가하고, `_admit_sell`(및 `SlotService.sell` optional 파라미터)이 그 가격을 슬롯 `limit_price` 로 운반한다. safety-sell/kill-switch 는 가격 미전달 → 시장가. -- 인터페이스 계약 문서(`docs/interface-contract.md`)와 sell 집행 runtime flow 기술을 새 정책에 맞춰 갱신한다. (단, buy/sell payload 의 외부 필드명 `entry_price` rename 은 dcy-012 범위 밖 — REQ-007/dcy-011 과 조율; 본 주기는 내부 슬롯 컬럼/도메인 필드 rename 까지.) - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/domain/slot/models.py` — 슬롯 필드 `entry_price` → `limit_price`(side 무관 "현재 pending 주문 지정가") 내부 rename - - `trex_core/domain/slot/service.py` — `transit_slot`/`_apply_transition_fields` 에서 →HOLDING·→CLOSED 시 `limit_price` 를 None 으로 clear(현 →IDLE 리셋 분기 패턴 재사용), `SlotService.sell` 에 optional 매도 지정가 파라미터 추가(safety-sell/kill-switch 미전달 → 시장가), 매수 경로는 buy 지정가를 `limit_price` 로 set - - `trex_core/application/execution.py` — `_dispatch` 가 `slot.limit_price` 단일 필드만 읽어 가격/주문유형 결정(side 분기 제거, 핵심 버그 지점) - - `trex_core/application/intents.py` — `SellIntent` optional 매도 가격 필드, `_admit_sell` 이 가격을 슬롯 `limit_price` 로 운반. `BuyIntent`/`_admit_buy` 의 매수 지정가도 `limit_price` 로 매핑 - - `trex_core/application/messaging_handlers.py` — 외부 payload 필드명(`entry_price`)과 내부 `limit_price` 의 경계 매핑(외부 필드명 rename 은 본 주기 밖) - - `trex_core/adapters/persistence/postgresql.py`, `trex_core/adapters/persistence/sql/` — 슬롯 컬럼 `entry_price` → `limit_price` rename 마이그레이션 + 매핑 갱신 - - `trex_core/adapters/broker/kis.py` — `submit_order` 의 `ORD_DVSN`/`ORD_UNPR` 매핑은 OrderRequest 변경에 따라 자동 반영(직접 수정 최소) -- 문서 영향: - - `docs/interface-contract.md` — sell payload 매도 가격 필드 추가 및 주문유형 매핑 갱신 - - `docs/runtime-flows.md` — sell 집행 흐름의 가격 결정 단계 정정 + limit_price 생애주기(set/clear) - - `docs/data-model.md` — 슬롯 `entry_price` → `limit_price` 컬럼 의미/이름 갱신 -- 운영 영향: - - released 집행 경로의 매도 동작 변경 + 슬롯 컬럼 rename 마이그레이션 → 실거래 매도 체결 방식 변화 및 스키마 변경, 배포 전 검증·마이그레이션 절차 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 슬롯의 매수 전용 `entry_price` 를 side 무관 단일 지정가 필드 `limit_price`(내부 rename)로 재정의한다. `limit_price` 는 pending 진입 시 해당 주문(매수/매도)의 지정가로 set 되고, pending 이탈(→HOLDING, →CLOSED) 시 None 으로 clear 되어 매수 지정가가 매도로 누수되지 않는다. -- FR-2: `_dispatch` 는 side 분기 없이 `slot.limit_price` 만 읽어 주문유형/가격을 결정한다 — 값이 있으면 `order_type="00"`(지정가, 그 가격), 없으면 `order_type="01"`(시장가). 매수가(과거 entry_price 의미)를 매도 지정가로 재사용하지 않는다. -- FR-3: `SellIntent` 및 sell payload 계약에 optional 매도 가격 필드를 추가하고, `_admit_sell`(및 `SlotService.sell` optional 파라미터)이 이를 슬롯 `limit_price` 로 전달한다. 매수 경로는 buy 지정가를 동일 `limit_price` 로 set 한다. -- FR-4: safety-sell·kill-switch(자율 매도)는 가격 미전달로 시장가, RPC 재량 매도는 가격 지정 시 지정가로 동작한다 — 단일 메커니즘(가격 유무 분기)을 모든 매도에 일관 적용한다(OQ-2). -- FR-5: `docs/interface-contract.md`·`docs/runtime-flows.md`·`docs/data-model.md` 를 확정된 매도 가격 정책과 `limit_price` 생애주기(set/clear)·컬럼 rename 에 맞춰 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 체결성 — 매도 트리거(특히 손절)는 가격 미전달로 시장가가 되어 의도대로 즉시 체결되고, 미체결로 `pending_sell` 에 갇히는 케이스(issue #9 연계)를 유발하지 않아야 한다. -- NFR-2: 가격 안전성 — 매도가 매수 지정가에 걸려 불리하게 체결되거나 영원히 미체결되는 정합성 오류가 재발하지 않아야 한다. `limit_price` 의 clear-on-exit 로 매수 지정가가 매도 주문에 잔존하지 않아야 한다. -- NFR-3: 계약 호환 — sell payload 의 매도 가격 필드는 optional 이어야 하며, 미지정(기존 호출 형태)은 시장가로 안전하게 동작해 기존 재량 매도 호출을 깨지 않아야 한다. 외부 payload/read 필드명(`entry_price`)은 본 주기에서 rename 하지 않아 기존 계약 호환을 유지한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 슬롯 `entry_price` → `limit_price` 내부 rename(도메인 모델/DB 컬럼/persistence) + pending 이탈 시 clear 생애주기 - - `_dispatch` 가 `slot.limit_price` 단일 필드로 가격/주문유형 결정(side 분기 제거) - - `SellIntent`/sell payload optional 가격 필드 + 슬롯 `limit_price` 운반(`_admit_sell`/`SlotService.sell`) - - 인터페이스/런타임/데이터모델 문서 갱신, 슬롯 컬럼 rename 마이그레이션 -- Out of Scope - - buy 주문 가격 의미 변경(매수 지정가는 그대로 `limit_price` 로 set — 동작 동일, 저장 위치명만 통일) - - 외부 buy/sell payload·slot read 의 필드명(`entry_price`) rename — REQ-007/dcy-011 과 조율할 별도 계약 변경 - - issue #9(KIS 외부 취소 시 pending_sell 갇힘) 자체의 복구 로직 — 본 버그는 #9 의 한 원인을 제거하나, #9 의 별도 복구는 후속 이슈로 분리 - - dispatch 시 현재가 자동 조회(`get_current_price`) 기반 지정가 — OQ-1 에서 미채택(급락 손절 시 재차 미체결 위험) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): released 집행 경로의 매도 동작을 바꾸므로, 정책 선택이 잘못되면 실거래 매도가 의도와 다르게 체결될 수 있다. - - 완화 방안: paper-broker/스텁으로 시나리오(손절/익절/현재가>매수가/현재가<매수가) 검증 후 배포하고, change-req 로 REQ-013/007/036/002/006/003/015 변경 이력을 남긴다. -- R-2 (Medium): 재량 매도가 지정가로 들어오면, 시장가가 그 지정가에 닿지 않을 때 미체결로 `pending_sell` 에 머물 수 있다(지정가의 본질적 특성). - - 완화 방안: 지정가 미체결 처리(취소/재시도/타임아웃)는 기존 dispatch 폴/롤백 경로와 issue #9 복구 범위에서 다루고, 본 주기는 가격 결정 분기에 집중한다. 운영자는 재량 매도 지정가의 미체결 리스크를 인지한다. -- R-3 (Low, OQ-2 결정으로 해소): reason 별 코드 차등을 두지 않고 가격 유무로 분기하므로, safety-sell 은 가격 미전달로 항상 시장가가 되어 손절 체결성이 보장되고 재량 매도만 선택적으로 지정가를 쓴다. - - 완화 방안: `SlotService.sell` 의 가격 파라미터 기본값을 None(시장가)으로 두어 자율 매도 경로가 실수로 지정가를 받지 않도록 한다. -- R-4 (Medium): `entry_price → limit_price` 슬롯 컬럼 rename 과 clear-on-exit 도입은 도메인/persistence/마이그레이션을 건드려, 누락 시 매수 지정가 복원이나 영속 매핑이 깨질 수 있다. - - 완화 방안: 컬럼 rename 마이그레이션과 전이별 set/clear(특히 →HOLDING·→CLOSED clear, →PENDING set)를 단위 테스트로 검증하고, 외부 payload/read 필드명은 그대로 유지(경계 매핑)해 계약 호환을 보존한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-011(BAT-015) 이 Implemented 되어 매수 시 `symbol` 이 항상 서버 resolve 로 슬롯에 채워지므로, 매도 dispatch 의 KIS 주문에 쓸 `slot.symbol` 이 신뢰성 있게 존재한다(가정 충족 확인됨). -- A-2: safety-sell([safety_sell.py:98](trex_core/application/safety_sell.py))·kill-switch([killswitch.py:61](trex_core/application/killswitch.py))는 `SlotService.sell(slot_id, actor, reason)` 로 가격을 전달하지 않으므로, 가격 파라미터를 optional(default None)로 추가해도 자율 매도 경로는 자동으로 시장가가 된다. -- A-3: 매수 주문 가격 의미는 그대로 유지된다 — buy 지정가는 동일 `limit_price` 로 set 되어 매수 동작은 변하지 않고, 슬롯 상태 전이 규칙(허용 전이 집합)도 유지된다. 변경은 가격 필드의 의미 통일(side 무관)과 pending 이탈 시 clear 추가뿐이다. -- A-4: KIS 시장가 매도(`ORD_DVSN="01"`)가 paper 및 실계좌에서 정상 수용된다(DATA 로 확인). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 매도 가격 정책 — (1) 시장가 매도, (2) 현재가 기반 지정가, (3) 인텐트 전달 가격 중 무엇을 채택하는가(단일 또는 조합)? - - 상태: Answered (2026-06-08, 사용자 결정 — 하이브리드: 인텐트가 가격을 지정하면 지정가, 미지정이면 시장가) - - 결정: `SellIntent`/sell payload 에 optional 매도 가격 필드를 둔다. 가격이 있으면 `order_type="00"`(지정가)로 그 가격에, 없으면 `order_type="01"`(시장가)로 매도한다. 매수가(`entry_price`)는 매도 가격으로 절대 사용하지 않는다. dispatch 에서 `get_current_price` 조회는 하지 않는다(옵션 2 미채택). - - 반영 위치: FR-1/FR-2/FR-3, 4. 변경 항목, 영향 범위 -- OQ-2: 정책 적용 범위 — safety-sell(손절/익절/긴급)과 RPC 재량 매도에 동일 정책을 적용하는가, reason 별 차등(예: 손절=시장가, 재량=지정가)을 두는가? - - 상태: Answered (2026-06-08, 사용자 결정 — 가격 유무로 분기, reason 별 코드 차등 없음) - - 결정: 단일 메커니즘(가격 유무 분기)을 모든 매도에 적용한다. safety-sell/kill-switch 는 `SlotService.sell(slot_id, actor, reason)` 로 가격을 전달하지 않으므로 자동으로 시장가가 되어 체결성이 보장된다. 재량 매도(RPC)만 가격을 optional 로 실어 지정가 통제를 쓸 수 있다. - - 반영 위치: FR-4, A-2, R-3 -- OQ-3: 거버넌스 경로 — 본 변경을 기존 REQ(dcy-003 집행 계열, sell 인텐트 계약 REQ)의 change-req 로 처리할지, 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-013(주문 집행 루프: `_dispatch` 가 `slot.limit_price` 단일 필드로 결정), REQ-007(intent 제출 API: sell payload optional 매도 가격 필드), REQ-036(자동 매도 내부 경로: `SlotService.sell` optional 가격 파라미터), REQ-002(slot 순수 도메인: `entry_price→limit_price` rename + pending 이탈 clear 전이 의미), REQ-006(PostgreSQL 영속화+스키마: 컬럼 rename 마이그레이션), REQ-003(컬럼 계약 가드 테스트: 컬럼명 변경 반영), REQ-015(브로커/집행 baseline 문서 정합). 하이브리드+rename 모두 기존 REQ 범위 안에서 표현 가능하므로 신규 REQ 는 생성하지 않는다. (Option A 의 rename 으로 change-req 대상이 slot 도메인/persistence 까지 넓어짐 — 비용은 거버넌스 footprint 증가, 이득은 매수 지정가의 매도 누수 구조적 차단.) - - 반영 위치: 1. 거버넌스 해석, R-1 완화 방안, 8. 확인 체크 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 매도 dispatch 가 매수 지정가를 재사용하지 않음을 코드/테스트로 보장한다 — `limit_price` 가 pending 이탈 시 clear 되어, 가격 미전달 매도는 `order_type="01"`(시장가), 가격 전달 매도는 `order_type="00"`(해당 지정가)로 발행. -- S-2: safety-sell 손절 트리거 시나리오(가격 미전달)에서 매도가 시장가로 정상 체결되어 `pending_sell` 갇힘이 발생하지 않는다. -- S-3: `entry_price → limit_price` 컬럼 rename 마이그레이션 후 기존 슬롯 데이터가 보존되고, `limit_price` 생애주기(→PENDING set / →HOLDING·→CLOSED clear)가 의도대로 동작한다. -- S-4: 변경된 매도 가격 정책·rename 이 `docs/interface-contract.md`·`docs/runtime-flows.md`·`docs/data-model.md` 에 반영되고, 코드(execution.py, intents.py, slot service/models, persistence)와 문서가 일치한다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 단위/통합 테스트(가격 미전달→시장가, 가격 전달→지정가, entry_price 미사용 케이스) 통과 / 데이터 출처: `_dispatch` 테스트, paper-broker 주문 기록(OrderRequest) - - 검증 필수 조건: 버그는 `entry_price` 가 채워진 **지정가 매수 슬롯**의 매도에서만 재현되므로, 검증 시나리오는 반드시 **지정가 매수(entry_price 채워짐)로 진입한 슬롯의 매도** 케이스를 포함해야 한다. 시장가 매수(entry_price=NULL) 슬롯만 테스트하면 수정 전후 모두 시장가 매도가 나가 버그를 검출하지 못한다. 권장 케이스 — (a) 지정가 매수 슬롯 + 가격 미전달 매도 → 시장가(entry_price 미사용 증명), (b) 지정가 매수 슬롯 + 가격 전달 매도 → 그 지정가, (c) 시장가 매수 슬롯 매도 → 시장가(회귀 안전성). -- S-2 측정 방식: safety-sell 손절 시나리오 e2e 검증 후 슬롯 상태가 closed 로 전이 / 데이터 출처: 슬롯 상태 스냅샷, 주문/체결 로그 -- S-3 측정 방식: 마이그레이션 실행 후 슬롯 행 보존 확인 + 전이별 set/clear 단위 테스트(→PENDING set, →HOLDING·→CLOSED clear) 통과 / 데이터 출처: 마이그레이션 SQL, `transit_slot` 테스트, persistence 라운드트립 -- S-4 측정 방식: 코드-문서 정합 리뷰(diff 대조) / 데이터 출처: `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/data-model.md`, 변경 코드 diff - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 매도 가격 정책(OQ-1 하이브리드)·적용 범위(OQ-2 가격 유무 분기)·거버넌스(OQ-3) 모두 확정됨 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(실거래 매도 동작 변경)·R-4(컬럼 rename/마이그레이션)의 change-req 대상(REQ-013/007/036/002/006/003/015)이 OQ-3 으로 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-3 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-012_20260608_sell-order-pricing-entry-price-bug.md -- 참조 문서: - - GitHub issue #10 (fourblocks2020/trex-core) — 매도가 entry_price 지정가로 나가는 버그 - - docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md — `_dispatch`/주문 제출 경로 도입 근거 - - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md — safety-sell 트리거·`get_current_price` 평가 - - docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md — slot.symbol 서버 resolve 보장(가정 A-1) - - trex_core/application/execution.py:84-104 — `_dispatch` 매도 가격 버그 지점(limit 줄 91) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2 확정으로 매도 가격 정책 해소) - -> 결정 완료(2026-06-08): OQ-1 매도 가격 정책 = 하이브리드(가격 지정 시 지정가, 미지정 시 시장가) / OQ-2 적용 범위 = 가격 유무 분기 단일 메커니즘(reason 별 차등 없음). - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-013/007/036/002/006/003/015 change-req, 신규 REQ 없음). - -## DATA -- (비차단) KIS 시장가 매도(`ORD_DVSN="01"`)의 paper/실계좌 수용 및 체결 특성 — REQ/delivery 단계의 acceptance/verification 에서 계측·확인할 운영 데이터(A-4). Discovery 본문만으로 FR/NFR·범위·성공 기준이 REQ 초안으로 충분하므로 confirm 차단 사유가 아니다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. Option A(슬롯 `entry_price→limit_price` 내부 rename + clear-on-exit) 설계 반영. 오픈 질문 OQ-1~OQ-3 모두 Answered (매도 가격=하이브리드: 가격 지정 시 지정가/미지정 시 시장가 / 가격 유무 분기, reason 차등 없음 / 거버넌스=REQ-013/007/036/002/006/003/015 change-req, 신규 REQ 없음). C 유형 플레이스홀더 0개, 저장소 사실관계 일치(줄번호 정정 포함). DATA(KIS 시장가 매도)는 REQ/delivery 단계 계측 비차단. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-08 14:50 KST diff --git a/docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md b/docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md deleted file mode 100644 index 16eb527..0000000 --- a/docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md +++ /dev/null @@ -1,224 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-013_20260608_kis-external-cancel-pending-sell-stuck -- 이슈명: KIS 외부 주문 취소 시 슬롯이 pending_sell 에 영구히 갇힘 — cncl_yn 미파싱 -- 작성 시각(KST): 2026-06-08 13:29 KST -- 마지막 갱신 시각(KST): 2026-06-08 16:30 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-012/013/018/031/014/015 (전부 change-req, 신규 REQ 없음 — OQ-3). REQ-013/018/015=CHG-20260608-02, REQ-012/031/014=CHG-20260608-01. 워치독 임계 env 는 REQ-014 에 흡수. delivery: BAT-017 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #9 (fourblocks2020/trex-core), 관련 issue #10(dcy-012, 매도 가격 버그) -- 해석: - - 실제 KIS `poll_fill` 이 일별주문체결조회의 취소여부(`cncl_yn`)를 파싱하지 않아, 계좌에서 직접(trex-core 외부) 매도 주문을 취소하면 슬롯이 `pending_sell` 에 영구히 갇히고 데몬이 죽은 주문을 매 tick 무한 폴링하는 버그를 정식 SDLC 주기로 착수한다. - - 이번 Discovery에서는 KIS 어댑터의 취소 감지 갭을 메우는 핵심 수정과, pending 주문의 stale 처리(워치독/타임아웃) 보강 범위를 판정한다. 이미 Implemented·released(REL-001/003) 된 집행/브로커 경로(dcy-003) 변경이므로, 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-012/013/018/031/014/015) `change-req` 로 처리하고 신규 REQ 는 원칙적으로 생성하지 않는다(워치독 임계 env 는 조건부). - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-012) 중 KIS 외부 취소 감지(`cncl_yn` 파싱)나 pending 주문 stale 워치독을 다룬 문서는 없다. dcy-003(broker execution+outbox)은 집행 루프·롤백 경로를 도입했고, dcy-012(issue #10)는 매도 *가격* 버그를 다뤘으나 `poll_fill` 의 취소 *감지* 갭은 명시적으로 범위 밖(후속 이슈)으로 분리했다. - - 변경이 이미 Implemented·released 된 KIS 어댑터(`poll_fill`)와 집행 reconciliation 의미를 바꾸므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 취소를 종료 상태로 다루는 설계는 이미 존재한다 — `trex_core/application/broker_port.py` 의 `FillStatus.is_terminal_reject`(broker_port.py:63~64)는 `rejected`·`cancelled` 를 모두 포함하고, `_poll`(execution.py:113~)은 `is_terminal_reject` 면 `_rollback`(execution.py:198~)을 호출해 `PENDING_SELL → HOLDING` 으로 되돌리고 `clear_dispatch` 로 `broker_order_id` 를 비운다(execution.py:207~210). - - 그러나 실제 KIS `poll_fill`(`trex_core/adapters/broker/kis.py:163~208`)은 취소여부(`cncl_yn`)를 파싱하지 않는다. 계좌에서 직접 취소하면 일별주문체결조회(`inquire-daily-ccld`)에 주문 row 는 남지만 체결수량(`tot_ccld_qty`)이 0이므로, `if ccld_qty <= 0: return FillStatus(state="accepted", ...)`(kis.py:191~192)로 빠져 **`accepted`(접수됨)** 로 판정된다. `_poll` 은 `accepted` 를 "다음 tick 대기"로 처리해 슬롯이 영구히 `pending_sell` 에 머문다. - - 부분 체결 후 취소(`0 < ccld_qty < ord_qty`)도 같은 문제다 — `partially_filled`(kis.py:207~)로 판정되어 역시 "다음 tick 대기"로 처리되어 갇힌다. - - paper 브로커는 취소를 올바르게 처리한다 — `trex_core/adapters/broker/paper.py:74~75` 에서 `o.cancelled` 면 `FillStatus(state="cancelled")` 를 반환한다. 즉 갭은 **실제 KIS 어댑터에만** 존재한다. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신이다. 집행 폴/롤백은 기존 runtime flow 에 포함돼 있고, 이번 변경은 KIS 어댑터의 취소 감지 단계를 메운다. -- 기대 상태 - - KIS `poll_fill` 이 외부 취소를 `FillStatus(state="cancelled")` 로 매핑하면, 기존 롤백 경로가 동작해 슬롯이 `HOLDING` 으로 복귀하고 `broker_order_id` 가 비워져 자동 회복된다. (dcy-012 구현 가정 — HOLDING 진입 시 `limit_price` 가 clear 되어 재매도는 시장가로 나가 체결성이 보장된다.) - - 죽은 주문에 대한 무한 1초 폴링이 멈추고, 운영자 수동 개입 없이 자동 복구된다. 보강책으로 pending 주문이 비정상적으로 오래 머무는 경우 stale 처리(워치독/타임아웃)가 안전망이 된다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — KIS 어댑터·reconciliation 변경 승인 - - 운영 담당자 — 외부 취소·갇힘 상황의 자동 복구로 수동 개입 부담 감소 - - 전략 모듈 — 슬롯이 자동 복구되어 일관된 상태를 관측 -- 제약사항 - - 이미 released(REL-001/003) 된 KIS 어댑터/집행 경로 변경이므로 ad-hoc 수정이 아니라 정식 SDLC(change-req 또는 신규 REQ) 경유가 필요하다. - - KIS `inquire-daily-ccld` 응답의 실제 `cncl_yn`(또는 동등) 필드명·값 규약을 실데이터로 확인해야 정확히 매핑할 수 있다. - - 부분 체결 후 취소는 체결분 원장 귀속과 잔여 포지션 정합을 함께 고려해야 한다(단순 롤백으로 과매도 위험). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 KIS HTS/MTS 에서 trex-core 가 낸 매도 주문을 직접 취소한다 → core 가 다음 tick 에 취소를 감지해 슬롯을 `HOLDING` 으로 복귀시키고, 필요 시 재매도(시장가)한다. -- 부분 체결 후 운영자가 잔량을 취소한다 → 체결분은 원장에 기록되고 잔여 포지션만 정합 처리되어 과매도가 발생하지 않는다. -- 어떤 이유로 pending 주문이 비정상적으로 오래 머문다 → 워치독/타임아웃이 stale 로 감지해 reconciliation 또는 경고를 발생시킨다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- KIS `poll_fill` 이 `inquire-daily-ccld` 응답의 취소여부(`cncl_yn` 등)를 파싱해, 취소된 주문(체결 0 또는 부분 체결 후 취소)을 `FillStatus(state="cancelled")` 로 매핑한다 — 기존 `is_terminal_reject` → `_rollback` 경로가 동작하도록. -- 부분 체결 후 취소(OQ-1 A안): 체결분 원장 기록 + 슬롯 qty 차감 + 잔여 HOLDING 복귀의 부분 체결 회계를 도입한다(과매도/원장 누락 방지). -- (OQ-2 — Alert-only) pending 주문 stale 워치독을 도입한다 — 임계 초과 시 WARN/메트릭 가시화만, 상태 미변경(auto-remediation 은 후속). -- 인터페이스/런타임 baseline 문서에 취소 감지·복구 흐름을 반영한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/adapters/broker/kis.py` — `poll_fill`(163~208) 에 `cncl_yn` 파싱 + `state="cancelled"` 매핑(핵심 갭) - - `trex_core/application/broker_port.py` — `FillStatus` 계약(필요 시 부분 체결+취소 표현 보강) - - `trex_core/application/execution.py` — 완전 취소(체결 0)는 기존 `_poll`/`_rollback` 경로 자동 재사용. 부분 체결+취소(OQ-1 A안)는 체결분 원장 기록 + 슬롯 qty 차감 + 잔여 HOLDING 복귀의 부분 체결 회계 경로 신규(`_on_filled`/`_record_ledger` 가 전량 체결만 다루던 것을 부분에도 확장) - - `trex_core/domain/slot/` — 슬롯 qty 부분 차감 후 HOLDING 복귀 전이(잔여 수량 갱신) - - (워치독 채택 시) pending stale 감지 컴포넌트 — 집행 reconciliation 경로 -- 문서 영향: - - `docs/interface-contract.md` — KIS poll/fill 상태 매핑(취소 포함) 반영 - - `docs/runtime-flows.md` — 외부 취소 감지 → 롤백 → 재매도/회복 흐름 반영 -- 운영 영향: - - 외부 취소·갇힘의 자동 복구로 수동 개입·불필요 폴링 감소 - - (워치독) stale 임계·알람 운영 파라미터 추가 가능 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: KIS `poll_fill` 은 `inquire-daily-ccld` 응답의 취소여부를 파싱해, 외부 취소된 주문(체결 0)을 `FillStatus(state="cancelled")` 로 매핑한다. 이로써 기존 `is_terminal_reject → _rollback` 경로가 동작해 슬롯이 `HOLDING` 으로 자동 복귀한다. -- FR-2: 부분 체결 후 취소(`0 < ccld_qty < ord_qty`)를 감지해 (OQ-1 A안) ① 체결분(`ccld_qty`)을 원장에 1회 기록, ② 슬롯 qty 를 `ccld_qty` 만큼 차감, ③ 잔여 qty 로 `HOLDING` 복귀해 잔여분만 재매도 가능하게 한다. `avg_price` 는 유지하여 잔여 포지션 정합을 보장하고 과매도/원장 누락을 방지한다. -- FR-3: (OQ-2 — Alert-only) pending 주문이 configurable 임계 시간을 초과해 머물면 stale 로 감지해 WARN 로그·메트릭으로 가시화한다. 슬롯/주문 상태는 변경하지 않는다(자동 롤백/취소는 후속). cncl_yn 매핑 오류(R-1) 등 미지 원인의 무한 갇힘을 silent 가 아니라 운영자에게 노출하는 안전망이다. -- FR-4: `docs/interface-contract.md` 와 `docs/runtime-flows.md` 에 취소 감지·복구(및 워치독) 흐름을 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 자동 회복성 — 외부 취소된 슬롯이 운영자 개입 없이 다음 tick 내에 `HOLDING` 으로 복구되어 `pending_sell` 영구 갇힘이 발생하지 않아야 한다. -- NFR-2: 자원 효율 — 죽은(취소된) 주문에 대한 무한 폴링이 중단되어 불필요한 KIS API 호출이 발생하지 않아야 한다. -- NFR-3: 정합성 — 부분 체결 후 취소에서 체결분이 원장에 정확히 귀속되고 잔여 포지션이 실제 잔고와 일치해야 한다(과매도/이중매도 금지). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - KIS `poll_fill` 의 `cncl_yn` 파싱 + `cancelled` 매핑(핵심 갭) - - 부분 체결 후 취소 처리(OQ-1 A안: 체결분 원장 기록 + qty 차감 + 잔여 HOLDING 복귀) - - pending stale 워치독(OQ-2 Alert-only: WARN/메트릭 가시화) - - 인터페이스/런타임 문서 갱신 -- Out of Scope - - buy 경로·paper 브로커 동작 변경(paper 는 이미 정상) - - 매도 가격 정책(dcy-012 에서 처리 — 본 주기는 취소 *감지*만) - - 워치독 auto-remediation(타임아웃 시 자동 롤백/취소) — 정상 slow fill·지정가 대기 상호작용·KIS 취소 능력 필요로 후속 분리 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): KIS `inquire-daily-ccld` 의 실제 취소 표현(필드명/값)이 문서 가정과 다르면 취소를 여전히 `accepted` 로 오판해 갇힘이 재현된다. - - 완화 방안: 실 응답 샘플(또는 KIS 명세)로 `cncl_yn` 등 필드를 확정하고, 미지 응답은 `unknown` 으로 보수 처리하되 OQ-2 의 Alert-only 워치독으로 무한 갇힘을 가시화하는 안전망을 둔다. -- R-2 (Medium): 외부 취소 → 롤백 → 재매도가 반복되면(운영자가 계속 취소) safety-sell 이 무한 재매도 루프를 돌 수 있다. - - 완화 방안: dcy-010(REQ-052) 의 per-slot 재시도 쿨다운/영속 거부 경고가 재매도 빈도를 제어하는지 확인하고, 필요 시 cancelled 도 쿨다운 트리거에 포함한다. -- R-3 (Medium, OQ-1 A안으로 완화): 부분 체결 후 취소를 단순 롤백하면 이미 팔린 수량을 다시 팔아 과매도가 날 수 있다. - - 완화 방안: OQ-1 A안(체결분 원장 기록 + 슬롯 qty 차감 + 잔여 HOLDING 복귀)으로 잔여분만 재매도되어 과매도가 구조적으로 차단된다. 부분 체결 회계 정확성은 REQ-018(원장)·REQ-031(포지션 정합) 검증과 연계해 보장한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-012 구현이 완료되어 슬롯 가격 필드가 `limit_price` 로 통일되고 HOLDING 진입 시 clear 되므로, 취소 후 롤백된 슬롯의 재매도는 시장가로 나가 체결성이 보장된다. -- A-2: `FillStatus.is_terminal_reject` 가 이미 `cancelled` 를 포함하고 `_rollback` 이 `PENDING_SELL → HOLDING` 을 수행하므로, KIS 어댑터가 `cancelled` 만 정확히 반환하면 application 경로 변경 없이 회복이 성립한다. -- A-3: paper 브로커는 이미 취소를 올바르게 처리하므로 본 변경은 KIS 어댑터(+필요 시 부분취소/워치독)에 국한된다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 부분 체결 후 취소 처리 정책 — 체결분을 원장에 기록하고 잔여 수량만 정합(예: HOLDING 잔량 갱신) 처리하는가, 아니면 다른 규칙을 쓰는가? - - 상태: Answered (2026-06-08, 사용자 결정 — A안: 한 번에 올바르게(정확 회계)) - - 결정: 부분 체결 후 취소 감지 시 ① 체결분(`ccld_qty`)을 원장에 1회 기록, ② 슬롯 qty 를 `ccld_qty` 만큼 차감, ③ 잔여 qty 로 `HOLDING` 복귀(재매도는 잔여분만)한다. `avg_price`(주당 원가)는 그대로 유지되어 잔여 포지션도 정합하며, 과매도·원장 누락을 동시에 방지한다. 단순 롤백(C)·격리 후속(B)은 미채택. - - 반영 위치: FR-2, 4. 변경 항목, 영향 범위, R-3 -- OQ-2: pending stale 워치독/타임아웃을 이번 주기에 포함하는가, 후속으로 분리하는가? 포함 시 임계 기준은? - - 상태: Answered (2026-06-08, 사용자 결정 — 포함, Alert-only) - - 결정: pending 주문이 임계 시간을 초과해 머물면 stale 로 감지해 WARN 로그·메트릭으로 **가시화만** 하고 슬롯/주문 상태는 변경하지 않는다(Alert-only). cncl_yn 매핑이 틀려도(R-1) 무한 갇힘이 silent 가 아니라 운영자에게 노출되는 안전망. 타임아웃 시 자동 롤백/취소(auto-remediation)는 정상 slow fill·지정가 대기와의 상호작용 및 KIS 주문 취소 능력이 필요해 후속으로 분리. 임계는 configurable(시장가 매도는 짧게, 지정가 매도는 관대하게 또는 단일 보수적 임계). - - 반영 위치: FR-3, 범위 경계, R-1 완화 -- OQ-3: 거버넌스 경로 — 기존 REQ(REQ-012 KIS 어댑터, REQ-014 reconciliation 등)의 change-req 로 처리할지, 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-012(KIS 어댑터: `cncl_yn` 파싱 + `cancelled` 매핑), REQ-013(주문 집행 루프: 부분 체결 회계 — qty 차감 + 잔여 HOLDING 복귀), REQ-018(체결→원장 기록: 부분 체결분 1회 기록), REQ-031(포지션 정합: 잔여 포지션 정합 연계), REQ-014(주문 핸드오프 durability + reconciliation: pending stale 워치독 Alert-only), REQ-015(브로커/집행 baseline 문서 정합: interface-contract/runtime-flows 갱신). 원칙적으로 신규 REQ 는 생성하지 않는다 — 단 워치독 임계 env 설정이 별도 추적을 요하면 REQ 정제에서 소형 config 신규 REQ 를 둘 수 있다(조건부). - - 반영 위치: 1. 거버넌스 해석, R-1 완화 방안, 8. 확인 체크 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 외부 취소된 매도 주문이 다음 tick 에 `cancelled` 로 감지되어 슬롯이 `HOLDING` 으로 복귀하고 `broker_order_id` 가 비워진다(영구 갇힘 0건). -- S-2: 취소된(죽은) 주문에 대한 추가 KIS 폴링이 발생하지 않는다. -- S-3: 부분 체결 후 취소 시 체결분이 원장에 1회 기록되고 잔여 포지션이 실제 잔고와 일치한다(과매도/이중기록 0건). -- S-4: pending 주문이 임계 시간을 초과하면 워치독이 WARN/메트릭으로 가시화한다(미지 원인 무한 갇힘이 silent 로 남지 않음). 슬롯/주문 상태는 변경하지 않는다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: KIS 어댑터 단위 테스트(취소 응답 fixture → `cancelled`) + 집행 통합 테스트(롤백→HOLDING) 통과 / 데이터 출처: `poll_fill` 테스트, 슬롯 상태 스냅샷, `inquire-daily-ccld` 응답 fixture -- S-2 측정 방식: 취소 감지 후 해당 slot 의 poll 호출 중단 확인 / 데이터 출처: 폴링 호출 로그/카운터 -- S-3 측정 방식: 부분 체결+취소 시나리오 테스트에서 원장 1건·잔여 수량 정합 확인 / 데이터 출처: ledger_outbox/포지션 정합(REQ-031) 검증, 슬롯 qty -- S-4 측정 방식: 임계 초과 pending 주입 테스트에서 WARN/메트릭 발생 및 상태 미변경 확인 / 데이터 출처: 워치독 테스트, 데몬 로그/메트릭, 슬롯 상태 스냅샷 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 부분취소 정책(OQ-1 A안)·워치독 포함 여부(OQ-2 Alert-only)·거버넌스(OQ-3) 모두 확정됨 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(취소 필드 규약 확인)의 change-req 대상(REQ-012/013/018/031/014/015)이 OQ-3 으로 확정됨. 해소 실무 책임자 배정·DATA(취소 필드 확인)는 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-3 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md -- 참조 문서: - - GitHub issue #9 (fourblocks2020/trex-core) — KIS 외부 취소 시 pending_sell 갇힘 - - docs/discovery/dcy-003_20260604_broker-execution-and-outbox.md — 집행 폴/롤백 경로 도입 근거 - - docs/discovery/dcy-012_20260608_sell-order-pricing-entry-price-bug.md — 매도 가격 버그(같은 pending_sell 갇힘 증상의 다른 원인, A-1) - - docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md — per-slot 재시도 쿨다운(재매도 루프 완화, R-2) - - trex_core/adapters/broker/kis.py:163-208 — `poll_fill` 취소 미파싱 갭 - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2 확정으로 해소) - -> 결정 완료(2026-06-08): OQ-1 부분 체결 후 취소 = A안(체결분 원장 기록 + 슬롯 qty 차감 + 잔여 HOLDING 복귀, 정확 회계) / OQ-2 워치독 = 포함(Alert-only: WARN/메트릭 가시화, auto-remediation 후속). 결정 항목에서 제외됨. - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-012/013/018/031/014/015 change-req, 신규 REQ 원칙적 없음). - -## DATA -- (R-1 연계, REQ/delivery 단계 확인) KIS `inquire-daily-ccld` 응답의 실제 취소 표현(필드명 `cncl_yn` 등·값 규약). 구현/검증 단계에서 실 응답·KIS 명세로 확정할 데이터이며, 미지 응답은 `unknown` 보수 처리 + 워치독 가시화로 안전망이 있어 Discovery confirm 을 차단하지 않는다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 오픈 질문 OQ-1~OQ-3 모두 Answered (부분 체결 후 취소=A안 정확 회계 / 워치독=포함 Alert-only / 거버넌스=REQ-012/013/018/031/014/015 change-req, 신규 REQ 원칙적 없음). C 유형 플레이스홀더 0개, 저장소 사실관계 일치. DATA(KIS 취소 필드)는 REQ/delivery 단계 확인 비차단. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 14:10 KST diff --git a/docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md b/docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md deleted file mode 100644 index ec6518b..0000000 --- a/docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md +++ /dev/null @@ -1,225 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-014_20260608_trading-date-configurable-timezone-kst -- 이슈명: 당일 슬롯 생성 기준 시간을 KST(설정 가능)로 분리 — trading_date 타임존 분리 + 일자 경계 preallocation -- 작성 시각(KST): 2026-06-08 13:58 KST -- 마지막 갱신 시각(KST): 2026-06-08 17:20 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-005/048/009 (전부 change-req, 신규 REQ 없음 — OQ-3; 스케줄러는 REQ-030 SnapshotScheduler 패턴 준용). 각 CHG-20260608-01. delivery: BAT-018 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #14 (fourblocks2020/trex-core) -- 해석: - - daemon 이 시작 시 `datetime.now(tz=timezone.utc).date()` 기준으로 슬롯을 1회 preallocate 해, `trading_date` 가 UTC 날짜로 생성되어 KST 기준 "오늘 슬롯"과 어긋나고, 재시작 없이는 다음날 슬롯이 자동 생성되지 않는 문제를 정식 SDLC 주기로 착수한다. - - 이번 Discovery에서는 `trading_date` 계산 기준을 설정 가능한 타임존(기본 `Asia/Seoul`)으로 분리하고, timestamp 저장은 UTC 유지하며, 일자 경계에 맞춰 다음날 슬롯이 자동 생성되도록 일일 preallocation 을 보강하는 범위를 판정한다. 이는 daemon 런타임/스케줄러/설정 baseline 변경이므로, 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-005/048/009, 스케줄러는 REQ-030 패턴 준용) `change-req` 로 처리하고 신규 REQ 는 원칙적으로 생성하지 않는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-013) 중 `trading_date` 타임존 기준이나 일자 경계 일일 preallocation 을 다룬 문서는 없다. dcy-002(슬롯 코어 데몬화)는 부트스트랩 시 preallocation 을 도입했으나 타임존·일자 경계 자동화를 정의하지 않았다. - - 변경이 이미 Implemented·released 된 daemon 부트스트랩(`setup`)·슬롯 도메인(`trading_date`)·설정 계약의 의미를 바꾸고, 일일 스케줄링이라는 런타임 동작을 추가하므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/runtime/daemon.py` 의 `setup()`(daemon.py:291~)은 `self.slot_service.preallocate_daily_slots(datetime.now(tz=timezone.utc).date(), self.config.total_slots)`(daemon.py:293~294)로, **UTC 날짜** 기준 슬롯을 데몬 시작 시 1회만 생성한다. - - `preallocate_daily_slots`(`trex_core/domain/slot/service.py:42~53`)는 동일 `trading_date` 에 대해 멱등(2회차 이후 0 반환)이다. 즉 같은 날짜에 대한 재실행은 안전하나, **날짜가 바뀌어도 자동으로 다음날 슬롯을 만들지 않는다**(재시작 의존). - - timestamp 저장은 UTC 다 — 슬롯 `created_at`/`updated_at`·전이 로그가 UTC. 그러나 `trading_date` 도 UTC 날짜라 KST 자정~오전 구간에 KST 캘린더 일자와 어긋난다. - - 타임존을 설정으로 분리하는 경로는 아직 없다 — `trex_core/runtime/config.py` 에 `trading_date` 용 타임존 설정이 없다. 다만 설정 인프라는 이미 두 표면을 갖춘다: env(`.env`, `TREX_*`, 대부분 설정)와 TOML(`DaemonInstanceConfig.from_toml`, `--config .toml`, REQ-048 per-instance). 즉 새 운영 설정은 기존 TOML 에 키를 추가하면 된다. - - 유사 패턴은 이미 존재한다 — `trex_core/application/snapshot_scheduler.py` 의 `SnapshotScheduler` 는 `_KST` 로 `now_kst.date()` 를 구해 **KST 일자당 1회** fire 하고(`_last_fired_date.date() == now_kst.date()`), tick 루프(`tick_sec`)로 동작한다. 일일 KST 경계 작업의 직접적인 선례다. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신이다. daemon 부트스트랩/스케줄러는 runtime flow 에 속하므로 이번 변경은 `docs/runtime-flows.md` 갱신을 동반한다. -- 기대 상태 - - `trading_date` 가 설정 가능한 타임존(기본 `Asia/Seoul`) 기준으로 계산되어, KST "오늘 슬롯" 과 DB `trading_date` 가 일치한다. - - timestamp(`created_at`/`updated_at`/전이 ts)는 UTC 저장을 유지한다. - - daemon 재시작 없이도 TZ 일자 경계에서 다음날 슬롯이 누락 없이 자동 생성된다(멱등 일일 preallocation). - - 운영자가 타임존을 TOML 설정으로 명시 지정할 수 있다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — 타임존·스케줄링·설정 계약 변경 승인 - - 운영 담당자 — 일자 경계 슬롯 생성·타임존 설정 운영 - - 전략 모듈/테스트 — KST 기준 "오늘 슬롯" 을 일관되게 관측 -- 제약사항 - - 이미 released 된 daemon 부트스트랩·슬롯 도메인·설정 변경이므로 ad-hoc 수정이 아니라 정식 SDLC(change-req 또는 신규 REQ) 경유가 필요하다. - - timestamp 는 UTC 를 유지하고 `trading_date` 만 TZ 기준으로 분리해, 저장 일관성과 일자 해석을 명확히 구분해야 한다. - - 다중 인스턴스(dcy-009) 운영 시 일일 preallocation 이 중복 생성되지 않도록 멱등성에 의존한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 KST 오전에 "오늘 슬롯" 을 조회한다 → DB `trading_date` 가 KST 당일과 일치해 직관적으로 해석된다(UTC 자정 경계 혼동 제거). -- daemon 이 며칠간 재시작 없이 떠 있다 → TZ 일자 경계마다 다음날 슬롯이 자동 생성되어 매일 거래 준비가 누락되지 않는다. -- 운영자가 다른 거래소/타임존을 쓰는 환경에서 TOML 로 타임존을 명시 설정한다 → 시스템 시간과 무관하게 일관된 `trading_date` 가 계산된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- `trading_date` 계산 기준을 시스템 UTC 가 아니라 설정 가능한 타임존(기본 `Asia/Seoul`)으로 분리한다. timestamp 저장은 UTC 유지. -- daemon 시작 1회 preallocation 을 TZ 일자 경계 기준 일일 preallocation 으로 보강한다 — `SnapshotScheduler` 패턴(tick 루프 + TZ 일자당 1회 fire)을 따르고 기존 멱등성을 활용한다. -- 본 기능의 운영 설정(타임존 + 일일 트리거 시각)을 기존 TOML 설정(REQ-048 `DaemonInstanceConfig`, `--config .toml`)에 추가하고, `docs/runtime-flows.md`·`docs/data-model.md` 에 trading_date 타임존·일일 생성 흐름을 반영한다. (이 프로젝트는 이미 env[.env, 대부분]와 TOML[per-instance, REQ-048]을 병용하므로, 본 설정은 TOML 측으로 자연스럽게 편입된다.) - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/runtime/config.py` — trading_date 타임존 + 일일 트리거 시각을 기존 TOML 설정(`DaemonInstanceConfig.from_toml`, REQ-048)에 추가(기본 `Asia/Seoul`/00:00). 신규 설정 표면이 아니라 기존 `--config .toml` 에 키 추가 - - `trex_core/runtime/daemon.py` — `setup()` 의 UTC 날짜 preallocation 을 TZ 기준으로 변경 + 일일 preallocation 루프/스케줄러 배선 - - `trex_core/application/` — 일일 preallocation 스케줄러(또는 `SnapshotScheduler` 유사 컴포넌트). 기존 멱등 `preallocate_daily_slots` 재사용 - - `trex_core/domain/slot/service.py` — `trading_date` 를 TZ 기준 date 로 받는 호출부 정합(도메인 시그니처는 `date` 유지) -- 문서 영향: - - `docs/runtime-flows.md` — daemon 부트스트랩/일일 preallocation·trading_date 타임존 흐름 반영 - - `docs/data-model.md` — `trading_date`(TZ 기준 거래일) vs timestamp(UTC) 구분 명시 -- 운영 영향: - - 타임존·트리거 시각 TOML 설정 추가 → 배포/운영 문서 안내 필요 - - 일자 경계 자동 생성으로 재시작 의존 제거 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `trading_date` 는 설정 가능한 타임존(기본 `Asia/Seoul`) 기준 날짜로 계산한다. 시스템 UTC 날짜에 의존하지 않는다. -- FR-2: timestamp(`created_at`/`updated_at`/전이 ts)는 UTC 저장을 유지한다(거래일과 시각 저장 분리). -- FR-3: daemon 재시작 없이 TZ 일자 경계에서 다음날 슬롯을 자동 생성한다 — tick 루프로 TZ 일자당 1회 멱등 preallocation(`SnapshotScheduler` 패턴 준용). 생성 트리거 시각은 기본 **00:00(TZ 자정)** 이며, TOML 설정으로 변경 가능하다(OQ-1). -- FR-4: 운영자가 타임존을 TOML 설정으로 명시 지정할 수 있다(기본 `Asia/Seoul`). 본 기능의 운영 설정(타임존 + 트리거 시각)은 TOML 로 일원화한다. -- FR-5: `docs/runtime-flows.md` 와 `docs/data-model.md` 에 trading_date 타임존·일일 preallocation 흐름을 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 저장 일관성 — 모든 timestamp 는 UTC, `trading_date` 만 TZ 기준으로 분리되어 해석 혼동이 없어야 한다. -- NFR-2: 멱등성 — 일일 preallocation 이 같은 `trading_date` 에 중복 슬롯을 만들지 않아야 한다(다중 인스턴스·중복 tick 안전). -- NFR-3: 자동화 안정성 — 날짜 경계에서 다음날 슬롯 생성이 누락되지 않고, daemon 재시작 없이 동작해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - `trading_date` 의 설정 가능한 타임존 분리(기본 KST) + timestamp UTC 유지 - - TZ 일자 경계 기준 일일 멱등 preallocation - - 타임존·트리거 시각 TOML 설정 + runtime-flows/data-model 문서 갱신 -- Out of Scope - - timestamp 저장 형식 변경(UTC 유지) - - 슬롯 용량/total_slots 정책 변경 - - (OQ-2 결정) 거래일 캘린더(주말·공휴일 비거래일 제외) — 매 캘린더일 생성으로 확정, 캘린더 도입은 후속 분리 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Low, OQ-1 결정으로 완화): 일일 preallocation 트리거 시점이 운영 기대와 어긋날 수 있다. - - 완화 방안: 기본 00:00(TZ 자정) + TOML 설정(OQ-1)으로 운영자가 조정 가능하게 하고, `SnapshotScheduler` 의 KST 일자당 1회 fire 패턴을 재사용해 검증 가능하게 한다. -- R-2 (Medium): 타임존 분리 전환 시점에 기존 UTC 날짜로 만들어진 슬롯과 새 TZ `trading_date` 가 경계 구간에서 불일치할 수 있다. - - 완화 방안: 전환 시 당일 경계 동작을 문서화하고, 멱등성으로 중복을 막되 경계 케이스를 테스트로 확인한다. -- R-3 (Low, OQ-2 결정으로 수용): 비거래일(주말/공휴일)에도 슬롯이 생성되어 idle 슬롯이 쌓일 수 있다. - - 완화 방안: OQ-2 결정에 따라 매 캘린더일 생성을 수용한다 — idle 슬롯은 멱등 생성이고 buy intent admit 시에만 활성화되어 무해하다. 거래일 캘린더(외부 데이터)는 후속 분리. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: `SnapshotScheduler`(snapshot_scheduler.py) 가 이미 `_KST` 기준 일자당 1회 fire + tick 루프를 구현하므로, 일일 preallocation 도 동일 패턴/유사 컴포넌트로 안전하게 구현할 수 있다. -- A-2: `preallocate_daily_slots` 가 `trading_date` 멱등이므로, 일일 재실행·다중 인스턴스에서도 중복 생성이 발생하지 않는다. -- A-3: 도메인의 `trading_date: date` 시그니처는 유지하고, 타임존 해석은 호출부(daemon/스케줄러)에서 수행하면 도메인 변경을 최소화할 수 있다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 일일 preallocation 트리거 시점 — TZ 자정(00:00) 경계에 생성하는가, 장 시작 전 특정 시각(설정)에 생성하는가? - - 상태: Answered (2026-06-08, 사용자 결정 — 기본 00:00(TZ 일자 경계), TOML 로 설정 가능) - - 결정: 일일 preallocation 은 설정 타임존의 **자정(00:00)** 일자 경계에 다음날 슬롯을 생성하는 것을 기본값으로 하고, 트리거 시각은 **TOML 설정**으로 운영자가 변경할 수 있다(`SnapshotScheduler` 의 KST 일자당 1회 fire 패턴 준용). 본 기능의 운영 설정(타임존 + 트리거 시각)은 TOML 로 일원화한다(FR-4 함께 결정). 이 프로젝트는 이미 env 와 TOML(REQ-048 per-instance)을 병용하므로, 본 설정은 기존 TOML(`--config`)에 키를 추가하는 형태로 편입된다(신규 설정 표면 아님). - - 반영 위치: FR-3, FR-4, R-1, 4. 변경 항목/영향 범위 -- OQ-2: 비거래일(주말/공휴일) 처리 — 매 캘린더일 슬롯을 생성하는가, 거래일만 생성하는가(거래일 캘린더 도입)? - - 상태: Answered (2026-06-08, 사용자 결정 — A안: 매 캘린더일 생성) - - 결정: 거래일 캘린더를 도입하지 않고 매 캘린더일에 슬롯을 생성한다. 휴장일 idle 슬롯은 무해하다 — 멱등 생성이고, 슬롯은 buy intent 가 admit 될 때만 활성화되므로 휴장일엔 비어 있다가 사라지며 safety-sell·워치독 등은 holding/pending 슬롯만 대상으로 한다. KRX 공휴일 캘린더(외부 데이터·매년 유지보수)는 idle 슬롯 회피 이득 대비 비용이 커 후속으로 분리한다(필요 시: 주말 스킵 또는 거래일 캘린더). - - 반영 위치: 범위 경계(Out of Scope), R-3 -- OQ-3: 거버넌스 경로 — 기존 REQ(데몬 부트스트랩·슬롯 도메인·설정)의 change-req 로 처리할지, 일일 스케줄러를 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-005(슬롯 코어 데몬 런타임 부트스트랩: `setup()` 의 UTC→TZ trading_date 계산 + 일일 preallocation 루프 배선), REQ-048(per-instance TOML 설정 로더: trading_date 타임존 + 일일 트리거 시각 키 추가), REQ-009(데몬/API/영속화 baseline 문서 정합: runtime-flows/data-model 갱신). 일일 preallocation 스케줄러는 REQ-030(일일 스냅샷 스케줄러)의 `SnapshotScheduler` 패턴을 준용한다. `preallocate_daily_slots` 가 이미 date 멱등이고 호출부(daemon)에서 TZ 계산을 수행하므로 REQ-002(slot 도메인) 본문 변경은 원칙적으로 불필요(정제에서 확인). 신규 REQ 는 원칙적으로 생성하지 않으며, 일일 preallocation 을 별도 컴포넌트로 분리해야 한다고 판단되면 REQ 정제에서 소형 신규 REQ 를 둘 수 있다(조건부). - - 반영 위치: 1. 거버넌스 해석, R-1 완화 방안, 8. 확인 체크 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 설정 타임존(기본 KST) 기준 "오늘 날짜" 와 생성된 슬롯의 DB `trading_date` 가 일치한다(UTC 자정 경계 포함). -- S-2: 모든 timestamp(`created_at`/`updated_at`/전이 ts)가 UTC 로 저장됨이 유지된다. -- S-3: daemon 재시작 없이 TZ 일자 경계를 넘기면 다음날 `trading_date` 슬롯이 자동 생성되고, 중복 생성은 0건이다. -- S-4: 운영자가 TOML 로 타임존을 설정하면 그 타임존 기준으로 `trading_date` 가 계산된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: KST 자정 전후 시점을 고정 clock 으로 주입한 단위 테스트에서 `trading_date` 일치 확인 / 데이터 출처: 슬롯 row `trading_date`, 주입 clock -- S-2 측정 방식: 슬롯 생성/전이 후 timestamp 컬럼 UTC 확인 / 데이터 출처: `created_at`/`updated_at`/transition_log -- S-3 측정 방식: 일자 경계를 넘기는 clock 주입 테스트에서 다음날 슬롯 1회 생성·재호출 0건(멱등) 확인 / 데이터 출처: preallocation 스케줄러 테스트, 슬롯 count -- S-4 측정 방식: 타임존 TOML 설정 케이스(KST/타 TZ) 테스트에서 `trading_date` 계산 확인 / 데이터 출처: config 로드, 슬롯 `trading_date` - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 트리거 시점(OQ-1)·거래일 캘린더(OQ-2)·거버넌스(OQ-3) 모두 확정됨 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 전환 경계(R-2)의 change-req 대상(REQ-005/048/009, 스케줄러 REQ-030 패턴)이 OQ-3 으로 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-3 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin Ko (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md -- 참조 문서: - - GitHub issue #14 (fourblocks2020/trex-core) — 당일 슬롯 생성 기준 시간 KST 분리 - - docs/discovery/dcy-002_20260603_slot-core-daemon-http-api.md — 데몬 부트스트랩·preallocation 도입 근거 - - trex_core/runtime/daemon.py:291-294 — UTC 날짜 1회 preallocation(현행 갭) - - trex_core/application/snapshot_scheduler.py — KST 일자당 1회 fire 스케줄러 선례(A-1) - - docs/runtime-flows.md, docs/data-model.md — 런타임/데이터 baseline 갱신 대상 - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2 확정으로 해소) - -> 결정 완료(2026-06-08): OQ-1 일일 preallocation 트리거 = 기본 00:00(TZ 자정 일자 경계), TOML 설정 가능(타임존도 TOML 일원화) / OQ-2 비거래일 = 매 캘린더일 생성(거래일 캘린더 후속 분리). 결정 항목에서 제외됨. - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-005/048/009 change-req, 스케줄러는 REQ-030 패턴 준용, 신규 REQ 원칙적 없음). - -## DATA -- (비차단, REQ/delivery 단계) 기본 타임존 `Asia/Seoul` 확정과 TOML 키/스키마 명명 규약. 운영 정책·키 명명은 REQ 정제/설계에서 확정하며, 기본값 KST 가 본 Discovery 의 FR/성공 기준을 충분히 정의하므로 confirm 을 차단하지 않는다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 오픈 질문 OQ-1~OQ-3 모두 Answered (트리거=기본 00:00 TZ 자정·TOML 설정[타임존 포함] / 비거래일=매 캘린더일 생성 / 거버넌스=REQ-005/048/009 change-req, 스케줄러 REQ-030 패턴, 신규 REQ 원칙적 없음). C 유형 플레이스홀더 0개, 저장소 사실관계 일치(TOML 설정 인프라 정정 반영). DATA(기본 TZ·TOML 키 명명)는 REQ/delivery 단계 비차단. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 14:50 KST diff --git a/docs/discovery/dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap.md b/docs/discovery/dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap.md deleted file mode 100644 index af998fd..0000000 --- a/docs/discovery/dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap.md +++ /dev/null @@ -1,229 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap -- 이슈명: portfolio-hub 404(미등록 포트폴리오)가 retryable 로 분류되어 백오프·상한 없이 영구 재시도됨 -- 작성 시각(KST): 2026-06-08 14:21 KST -- 마지막 갱신 시각(KST): 2026-06-08 18:10 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-017/019/018/020 (전부 change-req, 신규 REQ 없음 — OQ-3). REQ-018=CHG-20260608-03, REQ-017/019/020=CHG-20260608-01. 백오프 env 는 REQ-019 에 흡수. delivery: BAT-019 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #13 (fourblocks2020/trex-core), 관련 issue #11(dcy-011, ledger silent skip) -- 해석: - - portfolio-hub 가 미등록 포트폴리오에 404 를 반환하면 ledger outbox 가 이를 `LedgerRetryableError`(일시적 실패)로 분류해 **백오프·상한·터미널 상태 없이 초당 1회 영구 재시도**하는 버그를 정식 SDLC 주기로 착수한다(실측: buy 행 930회 재시도). - - 이번 Discovery에서는 (1) 영속(permanent) 오류(404 등 명백한 클라이언트 오류) 분류 분리, (2) retryable 에 지수 백오프 + max-attempts + dead-letter, (3) 터미널 실패 가시화의 범위를 판정한다. 이미 Implemented·released 된 ledger 어댑터/outbox 계약 변경이므로, 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-017/019/018/020) `change-req` 로 처리하고 신규 REQ 는 원칙적으로 생성하지 않는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-014) 중 ledger outbox 의 영속 오류 분류·백오프·상한·dead-letter 를 다룬 문서는 없다. dcy-004(portfolio-hub ledger 연동)는 경로를 도입했고, dcy-010(safety-sell 영속 거부 backoff)은 **다른 경로**(safety-sell→KIS)의 per-slot 쿨다운이며, dcy-011(issue #11)은 portfolio_id 누락 skip 가시화로 본 이슈(전송 단계 404)와 원인이 다르다. - - 변경이 이미 Implemented·released 된 LedgerPort/portfolio-hub 어댑터(REQ-017)와 outbox durability(REQ-019)의 오류 분류·상태 계약을 바꾸므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/adapters/ledger/portfolio_hub.py`(79~95)는 응답을 분류할 때 2xx→성공, 409→`LedgerConflictError`(터미널), **그 외 4xx/5xx 전부→`LedgerRetryableError`** 로 보낸다(파일 헤더 계약: `4xx (other)/5xx => LedgerRetryableError`). 따라서 404(portfolio 없음)처럼 **재시도로 절대 해소되지 않는 영속 오류**가 일시적 오류와 동일하게 취급된다. - - `trex_core/application/ledger_outbox.py`(137~142)는 `LedgerConflictError`→`conflict_blocked`(터미널), `LedgerRetryableError`→행을 `pending` 유지 + `attempts += 1` 이 전부다. **지수 백오프·max-attempts 상한·dead-letter 상태가 없다.** ledger 루프는 `ledger_interval_sec`(기본 1.0초)마다 `list_pending(limit=32)` 를 FIFO 로 다시 집어 재시도한다. - - `trex_core/adapters/persistence/sql/002_create_ledger_outbox.sql`(20~21)의 status CHECK 는 `pending`/`completed`/`conflict_blocked` 3개뿐이다. 즉 비-409 영속 실패를 담을 **터미널 실패 상태가 스키마에 없다**. - - 결과(실측, instance `trex-admin-daily-trading`, `portfolio_id=PF-test` 미등록): buy 행 `attempts=930`, sell 행 `attempts=9`, 둘 다 `status=pending last_outcome=retryable_failure`. 멈출 때까지 초당 1회 영구 호출. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`)는 존재하며 최신이다. ledger outbox 처리는 기존 runtime flow 에 포함된다. -- 기대 상태 - - 404 등 명백한 클라이언트(영속) 오류는 retryable 이 아니라 **터미널 실패 상태**로 분류되어 재시도가 즉시 중단된다. - - 진짜 일시적 오류(5xx·네트워크·429 등)는 **지수 백오프 + max-attempts** 로 재시도하되, 상한 초과 시 **dead-letter** 로 전이되어 무한 1초 재시도가 사라진다. - - 터미널/dead-letter 실패가 WARN 로그·메트릭과 구분되는 status 로 가시화되어, 운영자가 "기록이 영영 안 됨" 을 인지하고 head-of-line 잠식이 해소된다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — ledger 오류 분류·outbox 상태 계약 변경 승인 - - 운영 담당자 — 영속 실패 가시화·미등록 포트폴리오 조치 - - portfolio-hub 운영 — 무한 404 호출 spam 제거로 부하 경감 -- 제약사항 - - 이미 released 된 LedgerPort/portfolio-hub 어댑터·outbox durability 변경이므로 ad-hoc 수정이 아니라 정식 SDLC(change-req 또는 신규 REQ) 경유가 필요하다. - - outbox status enum 확장은 스키마 마이그레이션을 동반하며 기존 `conflict_blocked` 터미널 패턴과 정합해야 한다. - - 영속 오류 분류가 과하면(예: 등록 지연으로 인한 일시적 404) 정상 거래의 ledger 를 영구 실패시킬 수 있어 분류 경계가 중요하다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 미등록 portfolio_id 로 거래를 발생시킨다 → ledger 전송이 404 → 즉시 터미널 실패로 표시되고 WARN/메트릭으로 노출되어, 초당 무한 재시도 없이 운영자가 portfolio 등록 등 조치한다. -- portfolio-hub 가 일시적으로 5xx/네트워크 장애를 낸다 → ledger outbox 가 지수 백오프로 재시도하고, max-attempts 초과 시 dead-letter 로 전이해 무한 재시도를 막는다. -- 영속 실패 행이 다수 쌓여도 FIFO 배치(limit=32)를 잠식하지 않아 신규 ledger 행이 지연 없이 처리된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- ledger 어댑터(portfolio-hub) 오류 분류를 분리한다 — 404(및 명백한 클라이언트 오류)를 retryable 이 아니라 **영속 오류(LedgerPermanentError 등)** 로 매핑해 outbox 가 터미널 실패로 처리하게 한다. -- outbox retryable 처리에 **지수 백오프 + jitter + max-attempts** 를 도입하고, 상한 초과 시 단일 터미널 상태 `delivery_failed`(dead-letter, 사유 저장)로 전이한다. `list_pending` 이 `next_retry_at` 미도래 행을 건너뛰어 head-of-line 잠식을 막는다. -- outbox status enum 에 단일 터미널 `delivery_failed` 추가 + `next_retry_at` 컬럼 스키마 마이그레이션. (영속·dead-letter 공용, 사유로 구분) -- 터미널/dead-letter 실패를 WARN 로그·메트릭으로 가시화한다(dcy-011 #11 가시화 패턴 정합). -- ledger baseline 문서에 오류 분류·백오프·터미널 상태 흐름을 반영한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/adapters/ledger/portfolio_hub.py` — 404/400/422 등 영속 오류 분류(신규 `LedgerPermanentError` 매핑), 계약 헤더 갱신 - - `trex_core/application/ledger_outbox.py` — 영속 오류→`delivery_failed` 터미널, retryable 지수 백오프+jitter + max-attempts → `delivery_failed`(dead-letter), `list_pending` 의 `next_retry_at` 스킵 - - `trex_core/adapters/persistence/sql/` — ledger_outbox status enum 에 `delivery_failed` 추가 + `next_retry_at` 컬럼 마이그레이션 - - `trex_core/runtime/config.py` — 백오프 base/cap·max-attempts env 설정(dcy-010 관례) - - LedgerPort 계약(에러 타입) — 영속 오류 타입 추가 -- 문서 영향: - - `docs/runtime-flows.md` — ledger outbox 오류 분류·백오프·터미널/dead-letter 흐름 반영 - - `docs/data-model.md` — ledger_outbox status enum 확장 반영 - - `docs/interface-contract.md` — ledger 어댑터 오류 분류 계약(필요 시) -- 운영 영향: - - 미등록 포트폴리오의 무한 404 spam 제거 → portfolio-hub 부하·로그 오염 경감 - - 터미널/dead-letter 가시화 + 운영자 재처리 경로 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: portfolio-hub 어댑터는 영속(클라이언트) 오류를 retryable 과 분리한다 — **404·400·422**(OQ-1)를 `LedgerPermanentError`(가칭)로 매핑하고, outbox 는 이를 터미널 실패 상태로 처리해 재시도하지 않는다. 408·429·5xx·네트워크·(보수적으로)401/403 은 retryable 로 유지한다. -- FR-2: 진짜 일시적 오류(5xx·네트워크·408/429 등)는 **지수 백오프 + jitter**(`delay = min(base × 2^(attempts-1), cap)`, `next_retry_at` 기준)로 재시도하고, **max-attempts** 초과 시 터미널 `delivery_failed`(dead-letter)로 전이한다. 백오프 base/cap·max-attempts 는 env 설정(dcy-010 관례). 무한 1초 재시도를 제거한다. -- FR-3: ledger_outbox 에 **단일 터미널 실패 상태 `delivery_failed`** 를 추가(영속·dead-letter 공용, 사유 저장)하고 `next_retry_at` 컬럼 + status enum 마이그레이션을 추가한다. `list_pending` 은 `next_retry_at > now`(백오프 중) 행을 건너뛰어 FIFO head-of-line 잠식을 막는다. -- FR-4: 터미널/dead-letter 실패 발생 시 WARN 로그 + 메트릭으로 가시화하고, status 로 정상 `pending` 과 구분되게 한다(issue #11/dcy-011 가시화 패턴 정합). -- FR-5: `docs/runtime-flows.md`·`docs/data-model.md`(및 필요 시 `docs/interface-contract.md`)에 오류 분류·백오프·터미널 상태 흐름을 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 외부 호출 절제 — 영속 오류는 재시도하지 않고, 일시적 오류는 백오프로 호출 빈도를 제한해 portfolio-hub spam 이 발생하지 않아야 한다. -- NFR-2: 가시성 — ledger 가 끝내 기록되지 않는 실패는 메모리 카운터나 무성 pending 이 아니라 status·로그·메트릭으로 영구 관측 가능해야 한다(무성 영속 실패 금지). -- NFR-3: 처리량 보호 — 영속/백오프 대기 행이 FIFO 배치를 잠식해 신규 ledger 행 처리를 지연시키지 않아야 한다. -- NFR-4: 정합성 — 상태 전이(pending↔backoff↔terminal/dead-letter)와 멱등(correlation_id, REQ-019)이 깨지지 않아야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 영속 오류 분류 분리(404 등 → 터미널) - - retryable 지수 백오프 + max-attempts + dead-letter + list_pending 백오프 스킵 - - 터미널/dead-letter 가시화(로그/메트릭/status) - - status enum 마이그레이션 + ledger baseline 문서 갱신 -- Out of Scope - - safety-sell→KIS 영속 거부 backoff(dcy-010/REQ-052 에서 별도 처리 — 경로 다름) - - portfolio_id 누락 ledger skip(dcy-011/#11 에서 처리) - - dead-letter 행의 재처리 트리거(운영 명령/자동 재처리) — 이번 주기는 재처리 가능한 dead-letter 상태 제공까지이며, 재처리 트리거/자동화는 후속 분리(issue #13 핵심 해소에 불요) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Medium, OQ-1 으로 완화): 영속 오류 분류가 과하면 정상 거래의 ledger 가 영구 실패로 버려질 수 있다. - - 완화 방안: OQ-1 으로 permanent 범위를 404/400/422 로 한정했고, dcy-011 의 admission-time portfolio_id 검증으로 ledger 404 의 등록-지연 false-positive 가능성이 크게 줄었다. 잔여 false-positive 도 dead-letter 가 폐기가 아니라 **운영자 재처리 가능 상태**라 복구 가능하다. -- R-2 (Medium): status enum 확장 마이그레이션이 기존 `conflict_blocked`/`pending`/`completed` 와 정합하지 않으면 outbox 처리가 깨질 수 있다. - - 완화 방안: 기존 터미널(`conflict_blocked`) 패턴을 그대로 따르는 새 터미널 상태를 추가하고, 마이그레이션·상태 전이를 테스트로 검증한다. -- R-3 (Medium): dead-letter/터미널 행이 단순 포기되면 체결은 됐는데 ledger 영구 누락(P&L 왜곡)이 고착된다. - - 완화 방안: dead-letter 를 운영자 재처리 가능 상태로 두고, dcy-011 의 `find_ledger_gap_slots`(REQ-055)·skip 가시화와 연계해 누락을 추적·복구한다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-011(#11) 이 ledger skip 가시화(WARN+카운터, REQ-018)와 `find_ledger_gap_slots`(REQ-055) 를 이미 구현했으므로, 본 이슈의 터미널 실패 가시화도 동일 패턴으로 정합하게 추가할 수 있다. -- A-2: dcy-010(REQ-052/053) 의 백오프/쿨다운 + env 설정 관례가 있어, ledger 백오프 정책의 파라미터·설정 방식을 유사하게 설계할 수 있다(경로는 다름). -- A-3: 409 가 이미 `conflict_blocked` 터미널 상태로 처리되므로, 영속/ dead-letter 도 같은 터미널 상태 패턴으로 추가하면 outbox 처리 구조 변경을 최소화할 수 있다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 영속(permanent) 분류 범위 — 404 만 터미널로 보는가, 400/422 등 명백한 클라이언트 오류도 포함하는가? 408/429 같은 일시성 4xx 는 retryable 유지가 맞는가? - - 상태: Answered (2026-06-08, 사용자 결정 — 404/400/422 영속) - - 결정: **404·400·422 = 영속(터미널, 재시도 0)**, **408·429·5xx·네트워크 = retryable + 백오프**, 401/403 은 보수적으로 retryable+백오프(토큰 일시 갱신 가능성). 근거: dcy-011 이 admission 에서 portfolio_id 를 universe 검증(`unknown_portfolio` 거부)하므로 ledger 단계 404 는 타이밍 lag 이 아니라 portfolio-hub 미등록 = 진짜 config 갭 → 재시도로 해소 불가. 400/422 도 동일 payload 재전송으로 풀리지 않음. - - 반영 위치: FR-1, R-1, DATA -- OQ-2: 백오프·상한 정책 — 지수 백오프 파라미터(base/max), max-attempts 값, dead-letter 상태명·전이 조건은? - - 상태: Answered (2026-06-08, 사용자 결정 — 추천안 채택) - - 결정: (1) **지수 백오프 + jitter** — `delay = min(base × 2^(attempts-1), cap)`, `next_retry_at` 컬럼 추가, `list_pending` 은 `next_retry_at > now` 행을 건너뜀(head-of-line 해소). (2) **max-attempts** 초과 시 터미널 전이. 백오프 base/cap·max-attempts 는 **env 설정**(dcy-010 REQ-052/053 관례; 예: `TREX_LEDGER_RETRY_BASE_SEC`/`_CAP_SEC`/`TREX_LEDGER_MAX_ATTEMPTS`, 구체 default 는 REQ 정제에서 확정). (3) **단일 터미널 상태 `delivery_failed`** — 영속(즉시)과 dead-letter(백오프 소진) 둘 다 이 상태로 두고 **사유(reason/last_error)를 저장해 구분**(`conflict_blocked` 가 단일 상태인 것과 동일 패턴, 스키마 최소 변경). (4) **dead-letter 는 폐기 아님 — 운영자 재처리 가능 상태**. - - 반영 위치: FR-2, FR-3, 4. 변경 항목/영향 범위 -- OQ-3: 거버넌스 경로 — 기존 REQ(REQ-017 어댑터, REQ-019 outbox, REQ-018/055 가시화, REQ-020 문서)의 change-req 로 처리할지, 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-017(LedgerPort + portfolio-hub 어댑터: 404/400/422→`LedgerPermanentError` 분류, 계약 헤더 갱신), REQ-019(ledger outbox durability: 지수 백오프+jitter+`next_retry_at`+max-attempts+단일 터미널 `delivery_failed`+status enum/컬럼 마이그레이션+`list_pending` 백오프 스킵+env 설정), REQ-018(체결→원장 fill hook: 터미널/dead-letter WARN+메트릭 가시화 — dcy-011 의 `ledger_skipped` 카운터 패턴 정합, `find_ledger_gap_slots` REQ-055 연계), REQ-020(ledger 레이어 baseline 문서 정합: runtime-flows/data-model 갱신). 백오프 env 설정은 REQ-019 에 흡수. 신규 REQ 는 원칙적으로 생성하지 않으며, dead-letter 운영자 재처리 트리거를 이번 주기에 포함하기로 하면 REQ 정제에서 소형 신규 REQ 가능(조건부). - - 반영 위치: 1. 거버넌스 해석, R-1~R-3 완화 방안, 8. 확인 체크 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 404 응답이 즉시 터미널 실패 상태로 전이되고 재시도가 0회 추가된다(미등록 포트폴리오 시나리오에서 attempts 폭증 없음). -- S-2: 일시적 오류(5xx 등)가 지수 백오프로 재시도되고 max-attempts 초과 시 dead-letter 로 전이된다(무한 1초 재시도 0건). -- S-3: 터미널/dead-letter 실패가 WARN/메트릭·구분 status 로 가시화되어 정상 pending 과 구별된다. -- S-4: 영속/백오프 대기 행이 `list_pending` FIFO 배치를 잠식하지 않아 신규 행이 지연 없이 처리된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 404 응답 fixture 단위 테스트에서 터미널 상태 전이·재시도 0 확인 / 데이터 출처: portfolio_hub 어댑터·outbox 테스트, ledger_outbox status/attempts -- S-2 측정 방식: 5xx 반복 fixture 에서 백오프 간격 증가·max-attempts 후 dead-letter 확인 / 데이터 출처: outbox 처리 테스트, next_retry_at/attempts/status -- S-3 측정 방식: 터미널/dead-letter 발생 시 WARN·메트릭·status 확인 / 데이터 출처: 데몬 로그, 메트릭, ledger_outbox status -- S-4 측정 방식: 영속/백오프 대기 행 다수 + 신규 행 주입 시 신규 행 처리 확인 / 데이터 출처: list_pending 결과, 처리 순서/지연 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: permanent 분류(OQ-1)·백오프 정책(OQ-2)·거버넌스(OQ-3)·dead-letter 재처리 범위 확정으로 In/Out of Scope 채워짐 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(영속 분류 과대)·R-2(스키마 마이그레이션)·R-3(누락 고착)의 change-req 대상(REQ-017/019/018/020)이 OQ-3 으로 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-3 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin Ko (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap.md -- 참조 문서: - - GitHub issue #13 (fourblocks2020/trex-core) — portfolio-hub 404 영구 재시도 버그 - - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md — portfolio-hub ledger 경로 도입 근거 - - docs/discovery/dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md — ledger skip 가시화·find_ledger_gap_slots 패턴(A-1) - - docs/discovery/dcy-010_20260607_safety-sell-persistent-rejection-backoff.md — 백오프/쿨다운 설계 관례(A-2, 경로 다름) - - trex_core/adapters/ledger/portfolio_hub.py:79-95, trex_core/application/ledger_outbox.py — 404 분류·백오프 부재 지점 - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2/OQ-3 및 dead-letter 재처리 범위 확정으로 해소) - -> 결정 완료(2026-06-08): OQ-1 영속 분류 = 404/400/422 터미널(408/429/5xx/네트워크 retryable, 401/403 보수적 retryable) / OQ-2 백오프 = 지수+jitter+`next_retry_at`·max-attempts(env)·단일 `delivery_failed` 터미널(사유 저장)·dead-letter 운영자 재처리 가능 / dead-letter **재처리 트리거(운영 명령/자동화)는 본 주기 Out of Scope, 후속 분리**(이번 주기는 재처리 가능한 상태 제공까지). - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-017/019/018/020 change-req, REQ-055 연계, 신규 REQ 원칙적 없음). - -## DATA -- (비차단, REQ/delivery 단계) portfolio-hub 의 실제 4xx/5xx 응답 의미(404=영속, 429=rate-limit 등) 규약. 구현/검증 단계에서 실 응답·portfolio-hub 명세로 확정하며, OQ-1 의 404/400/422 영속 분류가 본 Discovery 의 FR/성공 기준을 충분히 정의하므로 confirm 을 차단하지 않는다. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 오픈 질문 OQ-1~OQ-3 모두 Answered (영속 분류=404/400/422 / 백오프=지수+jitter+next_retry_at·max-attempts env·단일 `delivery_failed` 터미널 / 거버넌스=REQ-017/019/018/020 change-req, 신규 REQ 원칙적 없음). C 유형 플레이스홀더 0개, 저장소 사실관계 일치. dead-letter 재처리 트리거는 Out of Scope(후속). DATA(portfolio-hub 오류 규약)는 REQ/delivery 단계 비차단. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 15:00 KST diff --git a/docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md b/docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md deleted file mode 100644 index 4221e73..0000000 --- a/docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md +++ /dev/null @@ -1,250 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-016_20260608_operator-cli-rpc-commands -- 이슈명: 운영자 CLI 명령 확장 — universe/슬롯 확인 + enter/exit + pause/resume (데몬 RPC 통일) -- 작성 시각(KST): 2026-06-08 15:49 KST -- 마지막 갱신 시각(KST): 2026-06-08 19:00 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-032/022/021/034/026 (전부 change-req, 신규 REQ 없음 — OQ-3). REQ-021=CHG-20260608-02, 그 외=CHG-20260608-01. pause/resume 는 direct-DB 유지. delivery: BAT-020 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 사용자 요청(운영자 CLI 명령 추가) -- 해석: - - 현재 `trex-core` CLI 는 `pause/resume/paused` 만 제공하고 PostgreSQL `PauseStore` 에 직접 접근한다. 운영자가 universe 확인·슬롯 상태 확인·매수(enter)·매도(exit) 를 CLI 로 수행할 표면이 없어, 운영 편의와 일관성을 위해 CLI 명령을 확장한다. - - 이번 Discovery에서는 universe/슬롯 읽기·enter/exit·pause/resume 를 **데몬의 기존 RabbitMQ RPC 표면(`trex.snapshot.req`/`trex.intent.req`/`trex.control.req`)으로 통일**해 단일 writer admission 을 보존하고, enter/exit 에 운영 안전장치를 넣는 범위를 판정한다. 서버 표면은 대체로 존재하나 운영자 조회의 워치독 부수효과(REQ-034)를 막는 서버측 억제 모드가 동반된다. 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-032/022/021/034/026) `change-req` 로 처리하고 신규 REQ 는 원칙적으로 생성하지 않는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-015) 중 운영자 CLI 의 universe/슬롯/enter/exit 명령을 다룬 문서는 없다. dcy-002(데몬+HTTP API), dcy-006(전략 transport), dcy-008(kill-switch) 등이 RPC 표면을 도입했으나 CLI 클라이언트 확장을 정의하지 않았다(REQ-032 의 CLI 는 pause/resume 만). - - 변경이 CLI 진입점(REQ-032)과 messaging transport(REQ-022) 사용을 확장하고 운영자에게 매수/매도 실행 표면을 추가하므로, 기존 Discovery 미세 조정이 아니라 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/runtime/cli.py`(전체 47줄)는 `pause`/`resume`/`paused` 3개 서브커맨드만 제공하고, `PostgreSQLPauseStore`(cli.py:32~42)에 **직접 접근**한다. universe/슬롯/매수/매도 명령은 없다. - - 데몬 RPC 표면은 이미 존재한다 — `trex_core/adapters/messaging/topology.py` 에 `SNAPSHOT_REQ_QUEUE="trex.snapshot.req"`(read-only), `INTENT_REQ_QUEUE="trex.intent.req"`(admission), `CONTROL_REQ_QUEUE="trex.control.req"`(pause/resume/kill_switch) 가 정의돼 있고, 모두 request-reply(`reply_to=amq.rabbitmq.reply-to`)다. - - 인스턴스 라우팅도 존재한다 — `_instance_queue(base, instance_key)`(topology.py:28~)는 named instance 면 `{base}.{instance_key}`, default 면 literal `base` 를 쓴다(dcy-009 다중 인스턴스). - - **주의(워치독 부수효과)**: `make_snapshot_rpc_handler`(messaging_handlers.py:196~200)는 read-only 응답 산출에 더해 **응답에 포함된 모든 holding 슬롯의 워치독 `last_seen_at` 을 갱신**한다(REQ-034). 이는 "전략 모듈 폴링 = 생존 신호"로, 폴링이 끊긴(워치독 만기) 슬롯만 safety-sell 트리거 대상이 되게 한다. 현재 이 갱신은 actor/플래그 구분 없이 모든 snapshot 요청에 적용된다. - - buy/sell 계약은 최근 Discovery 로 정리됐다 — buy 는 `portfolio_id` 필수 + symbol 서버 resolve(dcy-011, 구현 완료), sell 은 slot_id 대상 + optional 가격(없으면 시장가)(dcy-012, confirmed). - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`)는 존재하며 최신이다. CLI/운영 흐름은 runtime flow·interface-contract 에 속한다. -- 기대 상태 - - 운영자가 CLI 로 universe(snapshot RPC)·슬롯 상태(snapshot RPC)·enter(buy intent RPC)·exit(sell intent RPC)·pause/resume 를 수행한다. - - 모든 명령이 데몬 RPC 를 거쳐 단일 writer admission·불변식을 보존한다(특히 enter/exit 은 DB 직접 쓰기 금지). - - enter/exit 실행 전 확인 프롬프트·live 가드 등 안전장치가 동작해 오발주를 방지한다. - - 다중 인스턴스 환경에서 CLI 가 대상 인스턴스 큐로 정확히 라우팅한다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — CLI 표면·안전장치·트랜스포트 승인 - - 운영 담당자 — CLI 로 일상 운영(조회·매매·일시정지) 수행 - - 전략 모듈 개발자 — 동일 RPC 표면을 공유하므로 계약 일관성 관심 -- 제약사항 - - enter/exit(매수/매도)는 단일 writer 인 데몬 admission(슬롯 할당·portfolio_id 검증·상태 전이)을 반드시 거쳐야 하며 DB 직접 쓰기로 우회할 수 없다. - - RPC 통일은 데몬·RabbitMQ 가 가동 중일 때만 동작한다(오프라인 조회 불가는 의도된 트레이드오프). - - 다중 인스턴스(dcy-009)에서 CLI 는 대상 인스턴스를 지정해야 큐 라우팅이 맞는다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 `trex-core universe` 로 당일 거래 대상 자산(portfolio_id/symbol)을 확인한다(snapshot RPC). -- 운영자가 `trex-core slots --status holding` 으로 현재 슬롯 상태를 조회한다(snapshot RPC). -- 운영자가 `trex-core enter --capital N` 으로 매수를 넣되, 확인 프롬프트(또는 `--yes`)와 live 가드를 거쳐 안전하게 admission 한다. -- 운영자가 `trex-core exit [--price P]` 로 매도를 넣는다(가격 없으면 시장가, dcy-012). -- 운영자가 `trex-core pause/resume ` 로 portfolio 를 일시정지/해제한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- `trex-core` CLI 에 `universe`/`slots`/`enter`/`exit` 서브커맨드를 추가하고, 모두 **데몬 RabbitMQ RPC**(`trex.snapshot.req`/`trex.intent.req`)로 request-reply 한다(읽기도 RPC 통일 — 결정됨). -- enter/exit 에 운영 안전장치를 추가한다(OQ-2 A안) — 확인 프롬프트 기본 ON + `--yes`(paper) / `--yes --live`(live 이중 ack) + `--dry-run` 미리보기. -- 운영자 조회(`universe`/`slots`)가 워치독 생존 신호로 작동하지 않도록 snapshot RPC 에 워치독 억제 플래그(`touch_watchdog=false`/`actor="operator"`, OQ-4)를 추가한다(REQ-034 서버 부수효과 대응; 기본값=현행 유지로 하위호환). -- 전 명령에 `--json` 출력 모드를 추가한다(기계용 비대화형 — enter/exit 는 `--yes`/`--yes --live` 필수). -- CLI 에 인스턴스 라우팅 옵션을 추가해 다중 인스턴스 큐로 정확히 라우팅한다(OQ-1: `--config` canonical + `--instance` 단축, 미지정 시 `default`, 대상 인스턴스 항상 표시). -- `pause/resume` 는 기존 동작을 유지하되, 전 명령 RPC 통일 관점에서 `trex.control.req` 정합 여부를 정한다(범위 경계 참조). -- `docs/interface-contract.md`·`docs/runtime-flows.md` 에 CLI 명령·RPC 매핑·안전장치 흐름을 반영한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/runtime/cli.py` — `universe`/`slots`/`enter`/`exit` 서브커맨드 + 인스턴스 옵션 + enter/exit 가드 + RPC 클라이언트 호출 - - `trex_core/adapters/messaging/`(rabbitmq.py/messaging.py) — CLI 용 request-reply RPC 클라이언트 재사용/노출(전략 모듈이 쓰는 동일 클라이언트) - - `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler`(196~200)에 워치독 억제 모드 추가(운영자 조회 시 `last_seen_at` 미갱신, REQ-034/021) — R-4 대응, 서버측 변경 - - `trex_core/runtime/config.py` — CLI 가 RabbitMQ 접속·인스턴스 키를 읽도록 설정 활용(기존 env/TOML 재사용) -- 문서 영향: - - `docs/interface-contract.md` — CLI 명령 ↔ RPC payload 매핑(buy=portfolio_id 필수, sell=slot_id+optional price) 반영 - - `docs/runtime-flows.md` — 운영자 CLI → RPC → admission 흐름·안전장치 반영 -- 운영 영향: - - 운영자가 CLI 로 조회·매매·일시정지 수행 → 운영 편의 향상 - - enter/exit 실주문 표면 추가 → 안전장치·권한·paper/live 운영 정책 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `trex-core universe` 는 `trex.snapshot.req` RPC 로 당일 universe(자산 목록: portfolio_id/symbol 등)를 조회·출력한다. -- FR-2: `trex-core slots [--status ...]` 는 snapshot RPC 응답의 슬롯 상태(상태/심볼/수량/가격 등)를 조회·출력한다(`view=held`/holding 필터 포함). -- FR-2a: 운영자 CLI 조회(`universe`/`slots`)는 **워치독 생존 신호로 작동하지 않아야 한다** — snapshot RPC 요청에 워치독 억제 플래그(`touch_watchdog=false`/`actor="operator"`, OQ-4)를 실어, 서버 핸들러가 응답 holding 슬롯의 `last_seen_at` 갱신(REQ-034)을 건너뛴다. 기본값(플래그 미지정)은 현행 전략 폴링 갱신 유지(하위호환). CLI 조회는 항상 억제 플래그를 보낸다. -- FR-3: `trex-core enter [--capital/--qty ...]` 는 `trex.intent.req` buy 인텐트를 RPC 로 제출한다(symbol 은 서버 resolve, dcy-011). DB 직접 쓰기는 하지 않는다. -- FR-4: `trex-core exit [--price P]` 는 `trex.intent.req` sell 인텐트를 RPC 로 제출한다(가격 미지정 시 시장가, dcy-012). DB 직접 쓰기는 하지 않는다. -- FR-5: enter/exit 안전장치(OQ-2 A안) — 확인 프롬프트 기본 ON(대상 인스턴스·paper/live·portfolio_id/slot_id·qty/capital·가격 요약, 기본 No), `--yes` 로 생략(paper), **live 는 `--yes --live` 이중 ack**, `--dry-run` 으로 미제출 미리보기. paper/live 는 데몬 동일 env/`--config` 로 판별·표시한다. -- FR-6: 모든 RPC 명령은 대상 인스턴스 큐로 라우팅한다 — `--config `(canonical, REQ-048) 또는 `--instance `(단축, override), 미지정 시 `default`(OQ-1). 대상 인스턴스를 명령 출력에 항상 표시하고, RPC 응답(admitted/rejected/reason)을 운영자에게 표시한다. -- FR-7: 전 명령에 `--json` 출력 모드를 제공한다 — 사람용 표 대신 RPC 응답을 JSON 으로 출력(스크립트/`jq` 용). `--json` 은 기계용 비대화형 모드이므로 확인 프롬프트와 양립하지 않는다: enter/exit 에 `--json` 을 쓰면 `--yes`(live 는 `--yes --live`)가 필수이며, 미지정 시 프롬프트 대신 에러로 거부한다(자동 제출 금지). -- FR-8: `docs/interface-contract.md`·`docs/runtime-flows.md` 에 CLI 명령·RPC 매핑·안전장치·출력 모드를 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 단일 writer 보존 — enter/exit 는 데몬 admission 을 거쳐 슬롯 할당·portfolio_id 검증·상태 전이 불변식이 깨지지 않아야 한다. -- NFR-2: 안전성 — enter/exit 오발주를 막는 확인/가드가 기본 동작이며, live 계좌에서는 더 엄격해야 한다. -- NFR-3: 일관성 — CLI 와 전략 모듈이 동일 RPC 계약을 사용해, 표면 분기로 인한 동작 차이가 없어야 한다. -- NFR-4: 운영성 — RPC 실패(데몬 미가동·타임아웃·라우팅 오류)를 명확한 에러로 표시해 운영자가 원인을 알 수 있어야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - `universe`/`slots`/`enter`/`exit` CLI 명령(전부 데몬 RPC) + 응답 표시 - - enter/exit 안전장치(확인 프롬프트/`--yes`/live 가드) - - 운영자 조회의 워치독 억제 모드(snapshot RPC 서버 변경, REQ-034 — R-4 대응) - - 인스턴스 라우팅 옵션 - - 전 명령 `--json` 출력 모드(비대화형 — enter/exit 는 `--yes` 필수) - - interface-contract/runtime-flows 문서 갱신 -- Out of Scope - - 신규 서버측 RPC 표면/도메인 로직(기존 표면 재사용 — 단, 워치독 억제 모드는 snapshot 핸들러의 국소 서버 변경으로 In Scope) - - buy/sell 계약 자체 변경(dcy-011/dcy-012 에서 처리) - - kill_switch CLI 노출(별도 결정 — 본 주기는 enter/exit/pause/resume 까지) - - pause/resume 의 control RPC 통일 — 미채택(기존 direct-DB 유지). PauseStore 가 durable source 라 동작에 문제 없어 변경하지 않는다(OQ-3 결정). - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): enter/exit 가 실주문을 내는 표면이라, 오발주(잘못된 portfolio_id/slot_id/가격)나 live 오작동 시 금전 손실로 직결된다. - - 완화 방안: FR-5(OQ-2 A안)의 확인 프롬프트 기본 ON·`--yes`(paper)·`--yes --live` 이중 ack·`--dry-run` 을 기본 동작으로 하고, RPC 응답(rejected/reason)을 명확히 표시한다. admission 의 portfolio_id 검증(dcy-011)·slot 상태 검증이 1차 방어선이다. -- R-2 (Medium): 다중 인스턴스에서 CLI 가 잘못된 인스턴스 큐로 라우팅하면 의도와 다른 인스턴스에 매매가 들어간다. - - 완화 방안: (OQ-1) `--config`(canonical, 데몬과 동일 경로) 우선 + `--instance` 단축, 미지정 시 `default`. 모든 명령 출력·enter/exit 확인 프롬프트에 대상 인스턴스를 항상 표시해 오라우팅을 사전 차단한다. -- R-3 (Low): RPC 통일로 데몬·RabbitMQ 미가동 시 조회조차 불가하다(기존 pause 는 direct-DB 라 가능했음). - - 완화 방안: 의도된 트레이드오프임을 문서화하고, RPC 실패를 명확한 에러로 표시한다. 필요 시 읽기 전용 direct-DB 폴백은 후속 검토. -- R-4 (High): 운영자 CLI 조회(`universe`/`slots`)가 snapshot RPC 의 워치독 갱신 부수효과(REQ-034)를 일으키면, 전략이 실제로 죽어 있어도 운영자 조회가 생존 신호로 오인되어 **safety-sell(워치독 만기 강제청산)이 억제**된다 — 안전 기능 무력화. - - 완화 방안: FR-2a 로 운영자 조회는 워치독 억제 플래그(OQ-4)를 실어 서버가 `last_seen_at` 갱신을 건너뛴다. 스냅샷 RPC 계약/핸들러(REQ-034/021) 변경이 동반되며, 기본값은 현행(전략 폴링=워치독 갱신) 유지로 하위호환. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 서버측 RPC 표면(`trex.snapshot.req`/`trex.intent.req`/`trex.control.req`)과 request-reply 클라이언트가 이미 구현돼 있어, CLI 는 대부분 thin 확장으로 충분하다. 단, 워치독 억제(R-4/FR-2a)는 클라이언트만으로 불가하며 snapshot 핸들러(REQ-034)의 서버측 변경이 동반된다. -- A-2: dcy-011 구현 완료로 buy 는 portfolio_id 필수 + symbol 서버 resolve 이므로, `enter ` 만으로 매수 제출이 가능하다. -- A-3: dcy-012 구현이 완료되면 sell 인텐트에 optional 가격 필드가 있어 `exit [--price]` 가 성립한다(미구현 시 exit 은 시장가 전용으로 한정 가능). -- A-4: 인스턴스 라우팅 헬퍼(`_instance_queue`)가 존재하므로 CLI 도 동일 규칙으로 큐를 계산할 수 있다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 인스턴스 라우팅 지정 방식 — `--instance ` 플래그, `--config `(REQ-048) 재사용, env default 중 무엇을 표준으로 하는가? default 인스턴스 처리 규칙은? - - 상태: Answered (2026-06-08, 사용자 결정 — `--config` canonical + `--instance` 단축) - - 결정: **`--config .toml`(REQ-048) 를 canonical** 로 한다(데몬 기동과 동일 경로·`instance_key` 일치 보장). **`--instance ` 는 단축/override**(둘 다 주면 `--instance` 우선). 미지정 시 **literal `default` 인스턴스**. RabbitMQ 접속은 env(`TREX_RABBITMQ_*`) 재사용, 큐 네이밍은 `_instance_queue` 규칙. 오발주 방지를 위해 **모든 명령 출력·enter/exit 확인 프롬프트에 대상 인스턴스를 항상 표시**(R-2 완화, OQ-2 가드와 연결). - - 반영 위치: FR-6, R-2 -- OQ-2: enter/exit 안전장치 구체 정책 — 확인 프롬프트 기본 on + `--yes` 생략, live 계좌 가드(예: 추가 확인/환경변수)·dry-run 제공 범위는? - - 상태: Answered (2026-06-08, 사용자 결정 — A안) - - 결정: (1) **확인 프롬프트 기본 ON** — 실행 전 대상 인스턴스·broker(paper/live)·portfolio_id/slot_id·qty/capital·가격(또는 시장가) 요약을 표시하고 `y/N`(기본 No). (2) **`--yes` 로 프롬프트 생략**(paper 자동화). (3) **live 는 이중 ack** — `--yes` 만으로 부족하고 `--yes --live`(명시적 live 승인) 를 요구해 muscle-memory 오발주 차단(데몬 `TREX_KIS_ALLOW_LIVE` 배포 게이트 위에 액션 레벨 게이트 추가). (4) **`--dry-run`** — 해석된 intent + 대상 인스턴스만 출력, 미제출. paper/live 판별은 CLI 가 데몬과 동일 env(`TREX_BROKER`/`TREX_KIS_ALLOW_LIVE`)/`--config` 로 표시. - - 반영 위치: FR-5, NFR-2, R-1 -- OQ-3: 거버넌스 경로 — CLI 진입점(REQ-032)·messaging transport(REQ-022)·제어 채널(REQ-043)·**스냅샷 워치독 부수효과(REQ-034)/메시지 계약(REQ-021)**의 change-req 로 처리할지, CLI 명령을 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-032(런타임 제어 CLI: `universe`/`slots`/`enter`/`exit` 서브커맨드 + 인스턴스 라우팅 + enter/exit 안전장치), REQ-022(messaging transport: CLI request-reply RPC 클라이언트 배선), REQ-021(메시지 계약: snapshot 요청 payload 에 워치독 억제 플래그 추가), REQ-034(워치독: 핸들러가 억제 플래그 시 `last_seen_at` 미갱신), REQ-026(messaging baseline 문서 정합: interface-contract/runtime-flows CLI·RPC 매핑·플래그 반영). pause/resume 는 기존 direct-DB 유지(REQ-043 control RPC 통일 미채택). 신규 REQ 는 원칙적으로 생성하지 않으며, CLI 명령군을 독립 추적해야 한다고 판단되면 REQ 정제에서 소형 신규 REQ 가능(조건부). - - 반영 위치: 1. 거버넌스 해석(추가), R-1·R-4 완화, 8. 확인 체크 -- OQ-4: 운영자 조회의 워치독 억제 메커니즘 — snapshot RPC 요청에 `actor="operator"`/`touch_watchdog=false` 플래그를 추가해 서버가 워치독 갱신을 건너뛰는 방식인가, 아니면 워치독 갱신을 요청 `strategy_id` 의 슬롯에만 적용하도록 REQ-034 동작을 좁히는 방식인가? - - 상태: Answered (2026-06-08, 사용자 결정 — 플래그 방식) - - 결정: snapshot RPC 요청에 워치독 억제 플래그(예: `touch_watchdog=false` 또는 `actor="operator"`)를 추가하고, `make_snapshot_rpc_handler` 가 그 플래그면 `watchdog_store.update(...)` 를 건너뛴다. **기본값 = 현행(플래그 미지정 시 전략 폴링이 워치독 갱신)** 으로 기존 전략 모듈 무변경(하위호환). 운영자 CLI(`universe`/`slots`)는 항상 억제 플래그를 보낸다. strategy_id-scoped 방식은 기존 REQ-034 동작을 바꿔 미채택. - - 반영 위치: FR-2a, 4. 변경 항목/영향 범위, R-4 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: `trex-core universe`·`slots` 가 데몬 snapshot RPC 응답을 정확히 조회·출력한다(직접 DB 접근 없이). -- S-2: `trex-core enter`·`exit` 가 `trex.intent.req` RPC 로 인텐트를 제출하고 admitted/rejected 응답을 표시한다(DB 직접 쓰기 0건). -- S-3: enter/exit 가 확인 프롬프트/`--yes`·live 가드를 거치며, 미확인 시 주문이 나가지 않는다. -- S-4: 지정한 인스턴스 큐로 RPC 가 라우팅되고, 잘못된 인스턴스/데몬 미가동 시 명확한 에러를 표시한다. -- S-5: `--json` 출력이 유효한 JSON 이며, `--json` + enter/exit 에 `--yes` 미지정 시 자동 제출 없이 에러로 거부된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: stub/in-memory messaging 으로 snapshot RPC 응답 주입 후 CLI 출력 검증 / 데이터 출처: CLI 테스트, snapshot RPC 응답 fixture -- S-2 측정 방식: intent RPC mock 으로 admitted/rejected 응답 시 CLI 동작·출력 검증, DB 미접근 확인 / 데이터 출처: CLI 테스트, intent RPC 응답, 슬롯 저장소 무변경 -- S-3 측정 방식: 확인 프롬프트/`--yes`/live 가드 분기 테스트(미확인 시 RPC 미호출) / 데이터 출처: CLI 테스트, RPC 호출 여부 -- S-4 측정 방식: 인스턴스 키별 라우팅 큐 검증 + 데몬 미가동/타임아웃 에러 표시 검증 / 데이터 출처: 라우팅 계산, RPC 타임아웃 케이스 -- S-5 측정 방식: `--json` 출력 파싱(유효 JSON) + `--json` enter/exit 의 `--yes` 미지정 거부 테스트 / 데이터 출처: CLI 테스트, 출력 파서, RPC 호출 여부 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1 라우팅·OQ-2 안전장치·OQ-3 거버넌스·OQ-4 워치독 억제·pause/resume 정합(기존 유지) 모두 확정됨 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-5 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(오발주/실주문)·R-4(워치독 억제)의 change-req 대상(REQ-032/022/021/034/026)이 OQ-3 으로 확정됨. 안전장치는 OQ-2(A안)로 확정. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-4 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin Ko (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md -- 참조 문서: - - 사용자 요청(운영자 CLI 명령 추가: universe/슬롯/enter/exit/pause/resume) - - trex_core/runtime/cli.py — 기존 CLI(pause/resume/paused, direct-DB) - - trex_core/adapters/messaging/topology.py — snapshot/intent/control RPC 큐 + 인스턴스 라우팅 - - docs/discovery/dcy-011_..._buy-intent-portfolio-id-required-symbol-resolve.md — buy 계약(portfolio_id 필수, A-2) - - docs/discovery/dcy-012_..._sell-order-pricing-entry-price-bug.md — sell 계약(slot_id+optional price, A-3) - - docs/interface-contract.md — RPC payload 계약(intent/snapshot/control) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2/OQ-3/OQ-4 및 pause/resume 정합 확정으로 해소) - -> 결정 완료(2026-06-08): OQ-1 인스턴스 라우팅 = `--config`(canonical, REQ-048) + `--instance`(단축 override), 미지정 시 `default`, 대상 인스턴스 항상 표시 / OQ-2 안전장치 = 확인 프롬프트 기본 ON + `--yes`(paper) / `--yes --live`(live 이중 ack) + `--dry-run` / OQ-3 거버넌스 = REQ-032/022/021/034/026 change-req(신규 REQ 없음) / OQ-4 워치독 억제 = snapshot RPC 플래그 방식(`touch_watchdog=false`/`actor`, 기본=현행 유지) / pause-resume = 기존 direct-DB 유지(control RPC 통일 미채택). 결정 항목에서 제외됨. - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-032/022/021/034/026 change-req, pause/resume 기존 유지, 신규 REQ 원칙적 없음). - -## DATA -- 없음 - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 트랜스포트(전부 RPC)·enter/exit 안전장치·`--json` 모드 반영. 운영자 조회의 워치독 부수효과(R-4) 발견·억제 반영. 오픈 질문 OQ-1~OQ-4 모두 Answered (인스턴스 라우팅=`--config`+`--instance` / 안전장치=confirm+`--yes`/`--yes --live`+`--dry-run` / 거버넌스=REQ-032/022/021/034/026 change-req / 워치독 억제=플래그). pause/resume 기존 유지. C 유형 플레이스홀더 0개, 저장소 사실관계 일치. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 17:00 KST diff --git a/docs/discovery/dcy-017_20260608_watchdog-ownership-visibility.md b/docs/discovery/dcy-017_20260608_watchdog-ownership-visibility.md deleted file mode 100644 index 85cfc6b..0000000 --- a/docs/discovery/dcy-017_20260608_watchdog-ownership-visibility.md +++ /dev/null @@ -1,229 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-017_20260608_watchdog-ownership-visibility -- 이슈명: 전략 이탈로 슬롯이 내부 청산 루틴(워치독) 소유로 넘어간 상태를 가시화 (운영 모니터링) -- 작성 시각(KST): 2026-06-08 16:33 KST -- 마지막 갱신 시각(KST): 2026-06-08 20:00 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-034/021/037/038/039/032/042 (전부 change-req, 신규 REQ 없음 — OQ-3). REQ-034=CHG-20260608-02, REQ-021=CHG-20260608-03, REQ-032=CHG-20260608-02, REQ-037/038/039/042=CHG-20260608-01. Redis 미채택. delivery: BAT-021 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #12 (fourblocks2020/trex-core), 관련 issue #11(dcy-011, ledger silent skip), dcy-016(운영자 CLI) -- 해석: - - 슬롯이 내부 청산 루틴(safety-sell 워치독) 소유로 넘어가는 것 = 그 슬롯을 책임지던 전략 모듈이 heartbeat(스냅샷 RPC)를 멈췄다는 신호다. 그런데 이 상태가 완전히 무성(silent)이라 운영자가 "전략이 죽어 슬롯이 안전망으로 넘어갔다"는 가장 중요한 경고를 인지할 방법이 없다. 이를 가시화한다. - - 이번 Discovery에서는 **가시화 메커니즘**을 판정한다 — issue #12 는 Redis projection(TTL+pub/sub)을 제안하나, dcy-016 으로 운영자 조회가 워치독을 리셋하던 함정이 이미 해소되었으므로, **CLI `slots`(스냅샷 RPC 에 워치독 상태 노출) + safety-sell 이벤트 로깅** 으로 Redis 없이 관측을 충족한다(OQ-1 (a)). 거버넌스는 OQ-3 결정에 따라 기존 REQ(REQ-034/021/037/038/039/032/042) `change-req` 로 처리하고 신규 REQ 는 생성하지 않는다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~dcy-016) 중 워치독 소유/전략 이탈 상태의 운영 가시화를 다룬 문서는 없다. dcy-007(safety-sell)은 트리거 동작을, dcy-016(운영자 CLI)은 조회/매매 표면을 도입했으나 워치독 소유 상태 노출·알림을 정의하지 않았다. - - 변경이 워치독 상태 노출(REQ-034)·safety-sell 가시화(REQ-037) 의미를 확장하고(필요 시 Redis projection 신규 도입), 운영 모니터링 표면을 추가하므로 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/application/watchdog.py` 의 `WatchdogStore` 는 **순수 in-memory dict**(`last_seen` + `is_expired(slot_id, timeout_sec)`)다. DB 테이블도 조회용 RPC 도 없고, 데몬 재시작 시 소실된다. 만기 기준은 `watchdog_timeout_sec`(기본 120s, config.py). - - `trex_core/application/safety_sell.py` 의 `check_forced_close`/`check_prices`/`check_market_close`(143~)는 발동 슬롯 리스트(`fired`)를 반환하지만 `run_once` 가 이를 영구화·로깅하지 않고 버린다. safety_sell 로그는 WARN 3개(slot sell 실패·KIS 쿼터·market-close 실패)뿐이고 평가/발동/스킵에는 로그가 없다 → 무성. - - 스냅샷 RPC 응답은 슬롯 status/symbol/qty/price 만 운반하고 **워치독 상태(expired/last_seen)를 노출하지 않는다**(`make_snapshot_rpc_handler`, messaging_handlers.py:163~). 즉 현재는 CLI 로도 "어느 슬롯이 워치독 소유인지" 알 수 없다. - - **(중요) dcy-016 으로 "관측이 워치독을 리셋"하던 함정은 이미 해소됐다** — `make_snapshot_rpc_handler` 가 `touch_watchdog=false`/`actor="operator"` 요청이면 `last_seen` 을 갱신하지 않는다(messaging_handlers.py:201~207). 즉 issue #12 가 든 "유일한 조회 수단이 워치독을 리셋"한다는 전제는 더 이상 유효하지 않다. - - Redis 는 이미 의존성이다 — `redis>=5`, `RedisTokenCache`(best-effort, 오류 시 no-op), `TREX_KIS_REDIS_URL`. 새 인프라 도입이 아니라 기존 패턴 재사용 가능. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신이다. 워치독·safety-sell·CLI 는 runtime flow 에 속한다. -- 기대 상태 - - 운영자가 "전략 이탈 → 슬롯이 워치독 소유로 넘어감(=안전망 발동 대상)" 을 실시간으로 인지한다. - - 관측 행위가 워치독을 리셋하지 않는다(dcy-016 으로 이미 충족). - - safety-sell 의 워치독 소유 접수·발동(forced_close/stop_loss/take_profit/market_close)·전략 복귀 이탈이 로그/메트릭(또는 알림)으로 구분 가시화된다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — 가시화 메커니즘(CLI/RPC vs Redis)·거버넌스 승인 - - 운영 담당자 — 전략 이탈/안전망 발동을 실시간 모니터링·대응 - - 대시보드/알림 연동 — (Redis 채택 시) projection/pub-sub 소비 -- 제약사항 - - **Best-effort 원칙**: 가시화 부수효과(Redis/로그/메트릭)는 매매·청산을 절대 멈추면 안 된다. 워치독 판정의 source of truth 는 기존 in-memory 경로를 유지한다. - - 다중 인스턴스(dcy-009) 네임스페이스 — 키/표시에 instance 포함. - - WatchdogStore 가 in-memory 라 재시작 시 상태가 소실된다(메커니즘 선택에 영향). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략 모듈이 죽어 스냅샷 폴링이 끊긴다 → 해당 holding 슬롯의 워치독이 만기되어 safety-sell 소유로 넘어간다 → 운영자가 `trex-core slots`(또는 알림)로 "이 슬롯이 워치독 소유(전략 이탈)" 임을 즉시 인지한다. -- safety-sell 이 forced_close/stop_loss 로 슬롯을 실제 청산한다 → 발동 사실·사유·슬롯이 로그/메트릭(또는 알림)으로 남아 사후 추적된다. -- 전략이 복귀해 폴링을 재개한다 → 워치독 소유에서 이탈한 것이 가시화되어 경고가 stuck 되지 않는다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- (OQ-1 (a)) 스냅샷 RPC 응답에 holding 슬롯의 워치독 상태(expired/last_seen) 필드를 추가하고 `trex-core slots` 가 "워치독 소유(전략 이탈)" 를 표시한다(워치독 억제 읽기 = dcy-016 으로 이미 비침습). -- safety-sell 의 워치독 소유 접수·발동(forced_close/stop_loss/take_profit/market_close)·이탈을 로그/메트릭으로 가시화한다(현재 무성 → 가시화, dcy-011/#11 패턴 정합). -- baseline 문서에 워치독 가시화 흐름·스냅샷 응답 워치독 필드를 반영한다. -- (미채택) Redis projection·pub/sub 알림은 본 주기에 도입하지 않는다 — push 알림이 후일 하드 요구가 되면 후속 분리. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/safety_sell.py` — `run_once`/`check_*` 가 발동·소유·스킵을 로그/메트릭으로 가시화(현재 버려지는 `fired` 리스트 활용) - - `trex_core/application/messaging_handlers.py` — 스냅샷 응답에 워치독 expired/last_seen 필드 추가(`WatchdogStore` + `watchdog_timeout_sec` 로 슬롯별 계산) - - `trex_core/runtime/cli.py` — `slots` 가 워치독 상태(소유/전략 이탈) 컬럼 표시(dcy-016 의 `slots` 확장) -- 문서 영향: - - `docs/runtime-flows.md` — 워치독 소유·safety-sell 발동 가시화 흐름 반영 - - `docs/interface-contract.md` — 스냅샷 응답 워치독 상태 필드 계약 반영 -- 운영 영향: - - 전략 이탈/안전망 발동을 CLI 조회·로그로 인지 → 운영 대응성 향상 - - (push 알림·대시보드 연동은 본 주기 밖 — Redis projection 후속) - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: safety-sell 의 워치독 소유·발동을 phase별 차등 레벨로 가시화한다(OQ-2 A안) — WARN: 워치독 소유 진입(전략 이탈)·forced_close·stop_loss / INFO: take_profit·market_close·워치독 이탈(복귀) / 전 phase 카운터(phase·reason·instance_key 라벨) / per-tick 스킵·no-fire 는 카운터만(로그 X). 모든 로그·메트릭은 `slot_id`·`portfolio_id`·`reason`·`phase`·`instance_key` 구조화 필드를 포함한다. 소유 진입·이탈을 모두 남겨 경고가 stuck 되지 않는다. -- FR-2: 운영자가 "어느 슬롯이 워치독 소유(전략 이탈)인지" 를 관측할 수 있다. 관측 행위는 워치독을 리셋하지 않는다(dcy-016 의 `touch_watchdog=false` 억제 읽기 재사용). -- FR-3: (OQ-1 (a)) 스냅샷 RPC 응답에 holding 슬롯의 워치독 상태(expired 여부·last_seen)를 read-only 필드로 추가하고, `trex-core slots` 가 "워치독 소유(전략 이탈)" 를 표시한다. 데몬은 `WatchdogStore` + `watchdog_timeout_sec` 로 슬롯별 expired 를 계산해 응답에 싣는다. Redis projection·pub/sub 알림은 도입하지 않는다(미채택). -- FR-4: 가시화 부수효과는 best-effort 다 — Redis/로그/메트릭 오류가 매매·청산을 멈추지 않으며, 워치독 판정 source of truth 는 기존 in-memory 경로를 유지한다. -- FR-5: 다중 인스턴스 환경에서 워치독 상태 표시·로그에 instance_key 를 포함해 인스턴스 간 혼동이 없게 한다(dcy-016 의 대상 인스턴스 표시와 정합). -- FR-6: `docs/runtime-flows.md` 와 `docs/interface-contract.md`(스냅샷 응답 워치독 상태 필드)에 워치독 가시화 흐름·계약을 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 안전 격리 — 가시화는 best-effort projection 이며, 어떤 실패도 워치독 판정·safety-sell 청산을 차단하지 않아야 한다. -- NFR-2: 비침습 관측 — 운영자의 관측이 워치독 `last_seen` 을 갱신해 강제청산을 지연시키지 않아야 한다(dcy-016 억제로 충족). -- NFR-3: 가시성 — 전략 이탈/안전망 발동이라는 결과 기반 신호가 무성으로 사라지지 않고 로그/메트릭/(알림)으로 관측 가능해야 한다(issue #11/dcy-011 패턴 정합). -- NFR-4: 멀티 인스턴스 정합 — 인스턴스별 상태가 키/표시에서 혼동 없이 구분되어야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - safety-sell 워치독 소유·발동·이탈의 로그/메트릭 가시화(FR-1, cheap·필수) - - 워치독 소유 상태의 운영자 관측(FR-2) — 비침습(dcy-016) - - (OQ-1 (a)) 스냅샷 RPC 응답 워치독 상태 필드 + `trex-core slots` 표시 - - runtime-flows·interface-contract 문서 갱신 -- Out of Scope - - 워치독 판정 로직·timeout 기준 변경(가시화만, 판정은 기존 유지) - - WatchdogStore 를 in-memory 에서 영속 store 로 승격(정본 변경) — 본 주기는 관측 노출까지 - - Redis projection·pub/sub push 알림(OQ-1 (a) 로 미채택) — push 알림이 후일 하드 요구가 되면 후속 분리 - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 가시화 부수효과(특히 Redis)가 실패·지연하면 워치독 판정·safety-sell 청산을 막아 안전 기능을 해칠 수 있다. - - 완화 방안: FR-4 best-effort 격리 — `RedisTokenCache` 처럼 오류를 no-op 무시하고, source of truth 는 in-memory 유지. 가시화는 비동기·예외 격리. -- R-2 (Low, OQ-1 결정으로 해소): 메커니즘 과잉설계 위험. - - 완화 방안: OQ-1 (a)로 Redis projection 을 미채택하고 CLI/RPC 노출 + safety-sell 로깅으로 한정했다. dcy-016 으로 관측 함정이 이미 해소되어 on-demand 관측으로 issue #12 의 "무성 → 가시화" 를 충족한다. push 알림은 실제 요구 발생 시 후속. -- R-3 (Low): WatchdogStore 가 in-memory 라 재시작 시 소유 상태가 소실되어, 재시작 직후 CLI 조회가 비어 보일 수 있다(폴링 재개 시 다시 채워짐). - - 완화 방안: 가시화는 live 상태 반영임을 문서화한다. WatchdogStore 영속 승격은 Out of Scope(별도 주기). safety-sell 발동 로그(FR-1)는 재시작과 무관하게 이력으로 남는다. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-016 구현 완료로 스냅샷 RPC 에 워치독 억제 읽기(`touch_watchdog=false`/`actor="operator"`)와 `trex-core slots` 가 존재하므로, 워치독 상태 노출은 기존 표면 확장으로 충분하다(관측이 워치독을 리셋하지 않음). -- A-2: `safety_sell.check_*` 가 이미 발동 슬롯 리스트를 산출하므로, 로그/메트릭 가시화(FR-1)는 버려지던 결과를 활용하는 cheap 한 변경이다. -- A-3: Redis 가 이미 의존성이고 `RedisTokenCache` best-effort 패턴이 있어, Redis projection 채택 시 새 인프라 없이 동일 패턴을 재사용한다. -- A-4: 워치독 판정의 source of truth 는 in-memory 경로이며, 본 변경은 그 위의 관측 projection 만 추가한다(판정 동작 무변경). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 가시화 메커니즘 — (a) 스냅샷 RPC 워치독 상태 노출 + `trex-core slots` 표시(Redis 불요, pull+로그) / (b) best-effort Redis projection(TTL 연속 키 + warn/fire 이산 키·pub/sub, push 알림·대시보드) / (c) 단계적((a) 먼저, push 알림 필요 시 (b)) 중 무엇인가? - - 상태: Answered (2026-06-08, 사용자 결정 — (a) CLI/RPC 노출) - - 결정: 스냅샷 RPC 응답에 holding 슬롯의 워치독 상태(expired/last_seen) 필드를 추가하고 `trex-core slots` 가 표시한다. 관측은 dcy-016 의 `touch_watchdog=false` 억제 읽기로 비침습이다. **Redis projection·pub/sub 알림은 미채택**(이번 주기 도입 안 함). safety-sell 발동·소유·이탈 로그/메트릭(FR-1)은 메커니즘과 독립적으로 유지한다. push 알림이 후일 하드 요구가 되면 Redis projection 을 후속 분리한다. - - 반영 위치: FR-3, 범위 경계(Out of Scope: Redis projection), R-2, DATA -- OQ-2: safety-sell 가시화(FR-1)의 범위 — 어떤 phase(소유 진입/forced_close/stop_loss/take_profit/market_close/스킵/이탈)를 어떤 레벨(WARN/INFO/메트릭)로 남길지, 메트릭 키 규약은? - - 상태: Answered (2026-06-08, 사용자 결정 — A안 차등 매트릭스) - - 결정: **WARN** = 워치독 소유 진입(전략 이탈)·forced_close 발동·stop_loss 발동(주의 사건). **INFO** = take_profit 발동·market_close 발동·워치독 이탈(복귀). **메트릭(카운터)** = 전 phase(발동·소유 진입/이탈·스킵), phase/reason 라벨 + instance_key 포함. **per-tick 스킵/no-fire 평가는 로그하지 않고 카운터만**(30초 주기 로그 스팸 방지; 쿨다운 영속 거부 경고는 dcy-010/REQ-052 가 별도 처리). 모든 로그/메트릭에 `slot_id`·`portfolio_id`·`reason`·`phase`·`instance_key` 구조화 필드. dcy-011(#11 `ledger_skipped`)·dcy-013 의 WARN+카운터 패턴과 정합. - - 반영 위치: FR-1, NFR-3 -- OQ-3: 거버넌스 경로 — 워치독(REQ-034)·safety-sell(REQ-037)·설정(REQ-041)·문서(REQ-042)의 change-req 로 처리할지, (Redis projection 채택 시) 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — change-req 우선) - - 결정: 기존 Implemented REQ 본문 수정(change-req)으로 처리한다. change-req 대상 — REQ-034(per-slot 워치독: 워치독 상태 expired/last_seen 노출), REQ-021(메시지 계약: 스냅샷 응답에 워치독 상태 필드), REQ-037(forced_close 자동 매도 루프: forced_close·워치독 소유 진입/이탈 가시화), REQ-038(stop-loss·take-profit 트리거: 발동 가시화), REQ-039(장 마감 청산: market_close 가시화), REQ-032(CLI: `slots` 가 워치독 상태 표시 — dcy-016 확장), REQ-042(safety-sell baseline 문서 정합: runtime-flows/interface-contract 갱신). OQ-1 (a) 로 Redis projection 을 미채택했으므로 신규 REQ 는 생성하지 않는다(메트릭은 in-process 카운터로 기존 REQ 범위 내). - - 반영 위치: 1. 거버넌스 해석, R-1 완화, 8. 확인 체크 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 전략 폴링이 끊겨 슬롯이 워치독 소유로 넘어가면, 운영자가 (CLI 조회 또는 알림으로) 그 사실·해당 슬롯을 인지할 수 있다. -- S-2: safety-sell 의 워치독 소유 접수·발동(forced_close/stop_loss/take_profit/market_close)·이탈이 로그/메트릭으로 구분 관측된다(무성 0건). -- S-3: 운영자의 워치독 상태 관측이 `last_seen` 을 갱신하지 않아 강제청산을 지연시키지 않는다(dcy-016 억제 검증). -- S-4: 가시화 부수효과(Redis/로그/메트릭) 실패를 강제 주입해도 워치독 판정·safety-sell 청산이 정상 동작한다(best-effort 격리). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 폴링 중단 → 워치독 만기 시나리오에서 CLI 조회/알림에 소유 상태 표시 확인 / 데이터 출처: `slots` 출력 또는 projection, 워치독 상태 -- S-2 측정 방식: safety-sell 발동 시나리오에서 phase별 로그/메트릭 발생 확인 / 데이터 출처: 데몬 로그, 메트릭, `check_*` 결과 -- S-3 측정 방식: 운영자 모드(`touch_watchdog=false`) 조회 후 `last_seen` 미갱신 확인 / 데이터 출처: WatchdogStore 상태, 스냅샷 핸들러 테스트 -- S-4 측정 방식: Redis/로깅 실패 주입 후 워치독 판정·청산 정상 동작 확인 / 데이터 출처: 장애 주입 테스트, safety-sell 동작 로그 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 가시화 메커니즘(OQ-1 (a))·safety-sell 가시화 범위(OQ-2 차등 매트릭스)·거버넌스(OQ-3) 모두 확정됨 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(가시화가 안전 기능 차단)의 best-effort 격리 방식과 change-req 대상(REQ-034/021/037/038/039/032/042)이 OQ-3 으로 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-3 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin Ko (사람 승인자 지정 완료) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-017_20260608_watchdog-ownership-visibility.md -- 참조 문서: - - GitHub issue #12 (fourblocks2020/trex-core) — 워치독 소유 상태 Redis 가시화 제안 - - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md — safety-sell·워치독 도입 근거 - - docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md — `slots` CLI·워치독 억제 읽기(A-1, CLI 대안의 기반) - - docs/discovery/dcy-011_..._buy-intent-portfolio-id-required-symbol-resolve.md — 무성 가시화 패턴(#11 계열) - - trex_core/application/watchdog.py, safety_sell.py, messaging_handlers.py — 워치독 in-memory·발동 미로깅·억제 읽기 지점 - - trex_core/adapters/broker/token_cache.py — Redis best-effort 패턴(A-3) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (없음 — OQ-1/OQ-2 확정으로 해소) - -> 결정 완료(2026-06-08): OQ-1 가시화 메커니즘 = (a) 스냅샷 RPC 워치독 상태 노출 + `trex-core slots`(Redis 미채택, push 알림 후속) / OQ-2 safety-sell 가시화 = 차등 매트릭스(WARN: 소유진입·forced_close·stop_loss / INFO: take_profit·market_close·이탈 / 전 phase 카운터 / per-tick 스킵=카운터만). 결정 항목에서 제외됨. - -## CONFIRM -- 없음 — 거버넌스 경로는 OQ-3 으로 확정됨(REQ-034/021/037/038/039/032/042 change-req, 신규 REQ 없음). - -## DATA -- (참고) push 알림(전략 이탈 즉시 통지)은 본 주기 미채택(OQ-1 (a)). 후일 push 알림이 하드 요구로 확인되면 Redis projection 후속 분리. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 오픈 질문 OQ-1~OQ-3 모두 Answered (가시화=(a) 스냅샷 RPC 워치독 노출 + `trex-core slots`, Redis 미채택 / safety-sell 가시화=차등 매트릭스 / 거버넌스=REQ-034/021/037/038/039/032/042 change-req, 신규 REQ 없음). 핵심: dcy-016 으로 관측 함정이 이미 해소되어 Redis 없이 충족. C 유형 플레이스홀더 0개, 저장소 사실관계 일치. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 17:10 KST diff --git a/docs/discovery/dcy-018_20260608_ledger-response-cash-position-writeback.md b/docs/discovery/dcy-018_20260608_ledger-response-cash-position-writeback.md deleted file mode 100644 index 65d9bea..0000000 --- a/docs/discovery/dcy-018_20260608_ledger-response-cash-position-writeback.md +++ /dev/null @@ -1,233 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-018_20260608_ledger-response-cash-position-writeback -- 이슈명: ledger write 응답의 cash/quantity 를 portfolio 스냅샷에 write-back — option D 활성화 + 수량 source 통일 -- 작성 시각(KST): 2026-06-08 20:08 KST -- 마지막 갱신 시각(KST): 2026-06-08 21:45 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-017/019/029 change-req + REQ-030 Approved→Implemented (전부 change-req, 신규 REQ 없음 — OQ-3 a). REQ-017/019=CHG-20260608-02, REQ-029/030=CHG-20260608-01. delivery: BAT-023 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 운영 관측(매수 후 universe `asset_quantity` 는 1로 바뀌나 `cash_balance` 는 불변), portfolio-hub#8(ledger 응답에 cash/quantity 반환) 요청 -- 해석: - - 의도된 설계는 "ledger write → portfolio-hub 응답(cash/quantity) → trex-core SnapshotStore 갱신"(REQ-030 option D)이다. 그런데 trex-core 측에서 응답 파싱·write-back 배선이 누락되어, 현금은 일일 스냅샷 전까지 반영되지 않고 수량만 슬롯 오버레이로 살아있어 보이는 비대칭이 발생한다. - - 이번 Discovery에서는 (1) ledger write 응답의 cash/quantity 를 파싱·운반, (2) SnapshotStore write-back 배선(option D 활성화), (3) 수량 source 를 portfolio-hub 권위값으로 통일(슬롯 오버레이 정합)하는 범위를 판정한다. portfolio-hub#8 응답 계약 의존과 거버넌스도 함께 정한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - REQ-030(option D 현금/포지션 갱신)은 `Approved` 상태로 "wired but no-op until portfolio-hub#8 ships" 로 연기되어 구현되지 않았다. 본 Discovery 는 그 연기된 경로를 실제 활성화하고, ledger 응답 파싱(REQ-017)·write-back 배선(REQ-019)·수량 source 통일(REQ-029)을 함께 다룬다. - - dcy-005(snapshot read)는 option D 를 정의했으나 portfolio-hub#8 대기로 미구현 상태로 남겼다. 운영 환류로 갭이 확인되어 독립 주기로 활성화·정합하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **(1) ledger 응답 폐기**: `trex_core/adapters/ledger/portfolio_hub.py` 의 `record_trade`(portfolio_hub.py:78~94)는 2xx 응답에서 `resp.json()` 을 읽지만 **오직 `duplicate` 플래그만** 보고 `LedgerWriteResult.NEW`/`DUPLICATE_OK` 만 반환한다. portfolio-hub 가 cash/quantity 를 응답에 실어도 추출·반환하지 않는다. - - **(2) write-back 배선 부재**: `trex_core/application/ledger_outbox.py` 의 `LedgerWriter`(ledger_outbox.py:132~)는 `SnapshotStore` 참조가 없어, 응답에 값이 있어도 갱신할 경로가 없다. - - **(3) sink 만 존재, 호출자 없음**: `trex_core/application/portfolio.py` 의 `SnapshotStore.update_cash_position`(portfolio.py:148~)은 cash_balance·asset_quantity 를 쓰는 메서드로 구현돼 있으나, ledger 경로에서 호출되지 않는다(snapshot_scheduler 의 코멘트만 참조). - - **수량 비대칭(슬롯 오버레이)**: `build_snapshot_envelope`(portfolio.py:426~427)는 universe 의 `asset_quantity` 를 **로컬 슬롯 holding qty 로 override**(REQ-029)한다. 그래서 매수 직후 수량은 슬롯에서 살아있게 보이지만 portfolio-hub 권위값이 아니고, 현금은 오버레이가 없어 옛 스냅샷 값 그대로다. - - **option D 미구현**: REQ-030(option D 현금/포지션 갱신)은 `Approved`(미구현). `snapshot_scheduler.py:5~6` 주석 — "wired through `SnapshotStore.update_cash_position` but disabled at the ledger layer until portfolio-hub#8 ships". - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`)는 존재하며 최신이다. ledger 기록·스냅샷 read 는 기존 runtime flow 에 속한다. -- 기대 상태 - - ledger NEW write 후 portfolio-hub 응답의 **cash/quantity 가 SnapshotStore 에 write-back** 되어, 매수/매도 직후 `cash_balance` 와 `asset_quantity` 가 모두 portfolio-hub 권위값으로 갱신된다. - - 수량과 현금이 **동일 source(portfolio-hub)** 로 통일되어, "수량은 살아있고 현금은 stale" 한 비대칭이 사라진다. - - portfolio-hub 불가/응답 누락 시에도 best-effort 로 degrade 하며 ledger 기록·청산 동작을 막지 않는다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — ledger 응답 계약·write-back·수량 source 변경 승인 - - 운영 담당자 — 실시간 현금/포지션 정합 관측 - - portfolio-hub 팀 — ledger write 응답에 cash/quantity 반환(portfolio-hub#8) 제공 측 -- 제약사항 - - ledger write 응답의 cash/quantity 필드 계약은 portfolio-hub#8 배포·응답 스키마에 의존한다(외부 의존). - - write-back 부수효과가 ledger 기록 멱등(REQ-019)·청산 동작을 깨지 않아야 한다(best-effort 격리). - - 수량 source 변경(슬롯 오버레이 → portfolio-hub)은 매수~ledger 반영 사이의 타이밍 공백 처리가 필요하다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 `trex-core enter` 로 매수 후 `trex-core universe` 를 보면, `asset_quantity` 와 `cash_balance` 가 **모두** portfolio-hub 권위값으로 갱신되어 있다(현재는 수량만). -- 매도 후에도 동일하게 현금 증가·수량 감소가 즉시 스냅샷에 반영된다. -- portfolio-hub 가 일시 불가하면 cash/quantity 가 stale 임을 degraded 로 신호하고, ledger 기록은 outbox 로 계속 진행된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- ledger 어댑터 `record_trade` 가 portfolio-hub 응답의 cash/quantity 를 파싱·운반하도록 결과 타입을 확장한다(현재 `LedgerWriteResult` 만 → cash/quantity 동반). -- `LedgerWriter` 에 `SnapshotStore` 를 주입하고, `new`(201)·`duplicate`(200) 응답의 `cash_balance_after`/`asset_quantity_after`(절대 권위값) 로 `update_cash_position(asset_id, cash, quantity)` write-back 한다(option D 활성화, best-effort; 409/422 는 `*_after` 미정의라 skip — OQ-2 b). -- 수량/현금 source 정합(OQ-1 B): **슬롯 오버레이(REQ-029)는 유지**(lifecycle 중인 슬롯은 코어 권위 즉시값, 불변식 A), write-back 은 **현금 + 슬롯 밖(미보유) base 수량**을 portfolio-hub 권위값으로 신선화. 비대칭(수량 live·현금 stale) 제거. -- 체결~write-back 공백 동안 **현금을 추정 오버레이로 즉시 표시**(OQ-4 (i)): base 권위 현금 ± 미정산 체결 cash_delta(체결가×수량 ∓ 수수료/세금), **"추정/미정산" 라벨 필수**, write-back 권위값 도착 시 교체. -- 응답 계약·write-back·수량/현금 정합을 baseline 문서(interface-contract/runtime-flows)에 반영한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/adapters/ledger/portfolio_hub.py` — `record_trade` 응답 파싱(cash/quantity) + 결과 타입 확장 - - `trex_core/application/ledger_outbox.py` — `LedgerWriter` 에 `SnapshotStore` 주입 + NEW write 후 `update_cash_position` 호출(best-effort) - - `trex_core/application/portfolio.py` — `update_cash_position` 활용, 수량 source 정합(`build_snapshot_envelope` 의 슬롯 오버레이 처리) - - `trex_core/runtime/daemon.py` — LedgerWriter 에 SnapshotStore 배선 -- 문서 영향: - - `docs/interface-contract.md` — ledger write 응답 계약(cash/quantity) + 스냅샷 cash/quantity source 반영 - - `docs/runtime-flows.md` — ledger write → 응답 → SnapshotStore write-back 흐름 추가 -- 운영 영향: - - 매매 직후 실시간 현금/포지션 정합 → 운영 관측 정확도 향상 - - portfolio-hub#8 응답 계약 의존 — 배포 조율 필요 - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `record_trade` 는 portfolio-hub ledger write 응답 root 의 flat 필드 **`cash_balance_after`/`asset_quantity_after`**(절대 권위값)를 파싱해 운반한다(결과 타입 확장, `new`/`duplicate` 구분 유지). status별: `new`(201)·`duplicate`(200) = 권위값 있음, 409·422 = `*_after` 미정의(dcy-005 A-7 / portfolio-hub#8 확정·동작 중). -- FR-2: `LedgerWriter` 는 `new`·`duplicate` write 성공 시 `SnapshotStore.update_cash_position(asset_id, cash_balance_after, asset_quantity_after)` 로 write-back 한다(OQ-2 b — `*_after`가 delta 아닌 절대 권위값이라 덮어써도 멱등). 409/422 는 skip. 부수효과는 best-effort — 실패가 ledger 기록 멱등(REQ-019)·청산을 막지 않는다(write 성공 *이후* 분리 수행, 예외 무시). -- FR-3: 수량 source 정합(OQ-1 B) — 슬롯 오버레이(REQ-029)는 **유지**(lifecycle 중 슬롯은 코어 권위 즉시값, 불변식 A). write-back 은 **현금 + 슬롯 밖 base 수량**을 portfolio-hub 권위값으로 신선화. 슬롯 권위와 write-back 권위가 어긋나면 숨기지 않고 포지션 정합 경고(REQ-031)로 노출. -- FR-3a: 임시 현금 추정 오버레이(OQ-4 (i)) — 체결~write-back 공백 동안 `base 권위 현금 ± 미정산 체결 cash_delta`(체결가×수량 ∓ 수수료/세금)를 **"추정/미정산" 라벨과 함께** 노출하고, write-back 권위값 도착 시 교체·미정산 delta 제거. 코어 수수료(`calculate_fee`)는 근사라 추정 오차는 라벨로 커버. -- FR-4: portfolio-hub 불가·응답 누락(또는 dead-letter, dcy-015) 시 권위 현금/수량을 갱신하지 못하므로 추정·pending 유지 + degraded(REQ-028)로 신호하고 write-back 을 건너뛴다(best-effort). -- FR-5: `docs/interface-contract.md`·`docs/runtime-flows.md` 에 응답 계약(`*_after`)·write-back 흐름·수량/현금 source·추정 오버레이를 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 안전 격리 — write-back 부수효과는 best-effort 이며, 어떤 실패도 ledger 기록 멱등(REQ-019)·청산 동작을 차단하지 않아야 한다. -- NFR-2: 권위 일관성 — cash 와 quantity 가 동일 source(portfolio-hub)에서 와 비대칭(수량 live·현금 stale)이 재발하지 않아야 한다. -- NFR-3: 멱등 정합 — DUPLICATE write 응답으로 인한 cash/quantity 중복 적용이 없어야 한다(멱등 write-back). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - ledger write 응답 `cash_balance_after`/`asset_quantity_after` 파싱(`record_trade`) + write-back 배선(`LedgerWriter`→`update_cash_position`, `new`+`duplicate`) - - 수량/현금 source 정합(슬롯 오버레이 유지 + write-back 권위 신선화, OQ-1 B) - - 임시 현금 추정 오버레이(라벨, OQ-4 (i)) + write-back 권위 수렴 - - degraded/best-effort 처리 + 문서 갱신 + REQ-030 Approved→Implemented -- Out of Scope - - portfolio-hub#8 자체(외부 — 이미 배포·동작 중, 본 주기는 trex-core 소비 측) - - 일일 스냅샷 스케줄러 자체 재설계(REQ-030 의 fetch 부분은 유지) - - 슬롯 오버레이 제거(OQ-1 B 로 **유지** 확정 — 제거 안 함) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): write-back 부수효과가 실패·지연하면 ledger 기록 루프·청산을 막을 수 있다. - - 완화 방안: FR-2 best-effort 격리(`RedisTokenCache`/dcy-017 패턴) — 예외를 no-op 무시, ledger 멱등·청산 동작 불변. write-back 은 write 성공 *이후* 분리 수행. -- R-2 (해소): portfolio-hub#8 응답 스키마 미확정 위험. - - 해소: 응답 스키마 = root flat `cash_balance_after`/`asset_quantity_after`(dcy-005 A-7 확정) + portfolio-hub#8 배포·동작 중(2026-06-08). 필드 부재(409/422) 시 write-back skip(보수). 잔여 위험 없음. -- R-3 (Low, OQ-1 B + OQ-4 로 완화): 매수~write-back 공백에 현금이 잠깐 stale 할 수 있다. - - 완화 방안: 수량은 슬롯 오버레이(정확값) 유지, 현금은 추정 오버레이(OQ-4 i, "추정/미정산" 라벨)로 즉시 표시 후 권위값 수렴. 공백 구간은 outbox drain(~1s)으로 짧다. -- R-4 (Medium): DUPLICATE write 응답에 cash/quantity 가 와도 중복 적용하면 정합이 깨진다. - - 완화 방안: NEW write 에만 write-back 적용(또는 멱등 키 기준), DUPLICATE 는 스킵(NFR-3). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: portfolio-hub 가 ledger write 응답에 갱신된 cash/quantity 를 반환한다(portfolio-hub#8). 미배포 시 본 변경은 write-back 스킵(stale)으로 안전 동작하며, 배포 후 활성화된다. -- A-2: `SnapshotStore.update_cash_position` 이 이미 구현돼 있어 sink 는 재사용 가능하고, 누락된 것은 source(응답 파싱)와 배선(writer→store)뿐이다. -- A-3: ledger 기록 멱등(correlation_id, REQ-019)은 그대로 유지되며, write-back 은 그 위의 관측 갱신이다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 수량 source 통일 방식 — 슬롯 오버레이(REQ-029)를 제거하고 portfolio-hub write-back 값으로 일원화하는가, 아니면 슬롯 오버레이를 유지하고 write-back 으로 수렴하는가? - - 상태: Answered (2026-06-08, 사용자 결정 — B: 슬롯 오버레이 유지 + write-back 수렴) - - 결정: 슬롯 오버레이(REQ-029)는 **유지**한다 — lifecycle 중인 슬롯은 코어 자체 권위·즉시·외부 무관(불변식 A). portfolio-hub write-back 은 **현금과 슬롯 밖(미보유) base 수량**을 권위값으로 신선화해 비대칭(수량 live·현금 stale)을 제거한다. (A)는 매수 직후 공백에 수량을 옛값으로 후퇴시키고 portfolio-hub 불가 시 보유 수량이 0 으로 보이는 안전 리스크가 있어 미채택. - - 반영 위치: FR-3, 4. 변경 항목, 범위 경계 -- OQ-2: write-back 시점/실패 처리 — 어떤 응답에 write-back 하고, 불가·DUPLICATE·잔고부족 시 규칙은? - - 상태: Answered (2026-06-08, 사용자 결정 — b) - - 결정: `*_after` 가 delta 가 아니라 **절대 권위값**이므로 `new`(201)·`duplicate`(200) **둘 다 write-back**(덮어써도 멱등). 409·`422` 는 `*_after` 미정의라 **skip**. 시점은 LedgerWriter 가 ledger write 성공 응답을 받은 직후(별도 read round-trip 불요). write-back 부수효과는 best-effort — 예외/누락은 무시하고 ledger 멱등(REQ-019)·청산을 막지 않는다. - - 반영 위치: FR-2, FR-4, NFR-1/3 -- OQ-3: 거버넌스 경로 — change-req 로 처리할지, 신규 REQ 로 추적할지? - - 상태: Answered (2026-06-08, 사용자 결정 — a: 전부 change-req) - - 결정: 기존 Implemented/Approved REQ 본문 수정(change-req)으로 처리한다. 대상 — REQ-017(어댑터 `*_after` 응답 파싱 + 결과 타입 확장), REQ-019(`LedgerWriter` write-back 배선 + SnapshotStore 주입), REQ-029(수량 source 정합 — 슬롯 오버레이 유지 명시), **REQ-030(option D 활성화 — Approved→Implemented; REQ-030 Change Log Follow-up 의 "portfolio-hub#8 배포 후 minor-change 로 option D 구현" 예고대로)**. 신규 REQ 는 생성하지 않는다. (임시 현금 추정은 REQ-029/030 범위 내 관측 갱신으로 흡수.) - - 반영 위치: 1. 거버넌스 해석, 범위 경계 -- OQ-4: 체결~write-back 공백 동안 임시 현금 처리 — 추정값을 계산해 보여줄지(라벨), 아니면 마지막 권위값 + pending 플래그만 노출할지? - - 상태: Answered (2026-06-08, 사용자 결정 — (i) 추정 오버레이) - - 결정: 공백 동안 **추정 현금을 계산해 노출**한다 — `base 권위 현금 ± 미정산 체결 cash_delta`(체결가×수량 ∓ 수수료/세금), **"추정/미정산" 라벨 필수**. write-back 권위값(`cash_balance_after`) 도착 시 교체하고 해당 미정산 delta 를 제거한다. 코어 수수료(`calculate_fee`)는 근사라 추정 오차가 있으나 라벨로 커버하고, 추정 노출 구간은 outbox drain(~1s)으로 짧다. (수량은 슬롯 오버레이가 정확값이라 추정 불요 — OQ-1 B 와 짝.) - - 반영 위치: FR-3a, 4. 변경 항목, R-3 - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 매수/매도 후 universe 스냅샷의 `cash_balance` 와 `asset_quantity` 가 모두 portfolio-hub 응답 권위값으로 갱신된다(현재: 수량만 live, 현금 stale). -- S-2: portfolio-hub 응답에 cash/quantity 가 없거나 불가하면 write-back 을 스킵하고 ledger 기록·청산이 정상 동작한다(best-effort). -- S-3: DUPLICATE write 응답이 cash/quantity 를 중복 적용하지 않는다(멱등). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: ledger write 응답 fixture(cash/quantity 포함) 주입 후 SnapshotStore 갱신·universe 출력 확인 / 데이터 출처: `record_trade`/`LedgerWriter` 테스트, universe 응답 -- S-2 측정 방식: 응답 누락/portfolio-hub 불가 주입 시 write-back 스킵·ledger 정상 확인 / 데이터 출처: 장애 주입 테스트, outbox 상태 -- S-3 측정 방식: 동일 execution_id 중복 write 시 cash/quantity 1회만 적용 확인 / 데이터 출처: 멱등 write-back 테스트 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 수량 source(OQ-1 B)·write-back 규칙(OQ-2 b)·임시 현금(OQ-4 i)·거버넌스(OQ-3 a) 확정으로 In/Out of Scope 채워짐 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-3 의 테스트/검증 출처가 실제 실행 가능한 형태로 지정됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(write-back best-effort 격리)·R-2(응답 스키마 = `*_after` 확정, portfolio-hub#8 동작 중)의 해소 방식과 change-req 추적(OQ-3 a)이 확정됨. 해소 실무 책임자 배정은 REQ/batch planning 단계로 넘김(비차단). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~OQ-4 모두 `Answered`, `Open` 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Confirmed By = Justin (confirm-discovery 단계 지정) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-018_20260608_ledger-response-cash-position-writeback.md -- 참조 문서: - - 운영 환류(매수 후 cash_balance 미반영 관측) + portfolio-hub#8 요청 - - docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md — REQ-029(슬롯 오버레이)/REQ-030(option D) 도입 근거 - - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md — ledger 기록 경로 - - trex_core/adapters/ledger/portfolio_hub.py:78-94 — `record_trade` 응답 폐기 지점 - - trex_core/application/ledger_outbox.py:132 — `LedgerWriter`(SnapshotStore 부재) - - trex_core/application/portfolio.py:148, 426-427 — `update_cash_position`(호출자 없음)·슬롯 오버레이 - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -> 결정 완료(2026-06-08): OQ-1 수량 source = B(슬롯 오버레이 유지 + write-back 수렴) / OQ-2 write-back = b(`new`+`duplicate` 둘 다, 409·422 skip, best-effort) / OQ-3 거버넌스 = a(전부 change-req: REQ-017·019·029·030) / OQ-4 임시 현금 = (i) 추정 오버레이(라벨)+권위 수렴. DATA(portfolio-hub#8 응답 스키마)는 해소. - -## DECIDE -- (없음 — OQ-1/OQ-4 확정으로 수량/현금 source 해소) - -## CONFIRM -- (없음 — 거버넌스 경로 OQ-3 a 로 확정: REQ-017/019/029/030 change-req, 신규 REQ 없음) - -## DATA -- (해소) portfolio-hub#8 ledger write 응답 = root flat `cash_balance_after`/`asset_quantity_after`(절대 권위값), status별 `new`(201)/`duplicate`(200)=값 있음·409/422=미정의. dcy-005 A-7 에서 확정됐고 portfolio-hub#8 **배포·동작 중**(사용자 확인 2026-06-08) — "배포 대기" 조건부 해제됨. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: confirm-discovery 완료. 저장소 사실관계 일치 확인(record_trade 는 `duplicate`만 파싱·`*_after` 미파싱 / update_cash_position 정의만 있고 호출자 없음 / LedgerWriter 에 SnapshotStore 없음 / REQ-030 Approved). 오픈 질문 OQ-1~OQ-4 모두 Answered (수량 source=B 슬롯 오버레이 유지+write-back 수렴 / write-back=b `new`+`duplicate` 둘 다·409/422 skip / 거버넌스=a 전부 change-req REQ-017·019·029·030 / 임시 현금=(i) 추정 오버레이 라벨). DATA(portfolio-hub#8 응답 스키마)는 dcy-005 A-7 `cash_balance_after`/`asset_quantity_after` 확정 + portfolio-hub#8 배포·동작 중(사용자 확인)으로 해소. C 유형 플레이스홀더 0개. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-08 21:45 KST diff --git a/docs/discovery/dcy-019_20260609_slot-entry-count-and-closed-reallocation.md b/docs/discovery/dcy-019_20260609_slot-entry-count-and-closed-reallocation.md deleted file mode 100644 index 4d57351..0000000 --- a/docs/discovery/dcy-019_20260609_slot-entry-count-and-closed-reallocation.md +++ /dev/null @@ -1,213 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-019_20260609_slot-entry-count-and-closed-reallocation -- 이슈명: slot entry_count 추가 및 closed 슬롯 재할당 정책 (issue #18) -- 작성 시각(KST): 2026-06-09 13:13 KST -- 마지막 갱신 시각(KST): 2026-06-09 13:34 KST -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 신규 REQ 없음(거버넌스 전부 change-req). 변경 REQ: REQ-002/003/006/007/009 (각 CHG-20260609-01). Delivery: BAT-025 (release-candidate). - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #18(slot entry_count 추가 + closed 슬롯 재할당 정책). 기존 dcy-001/002(슬롯 도메인·preallocation), dcy-014(일자 경계 preallocation) 참조. -- 해석: - - buy admission 은 현재 `find_idle_slot`(idle 1개 임의 선택)만 사용해 closed 슬롯을 재할당 후보로 보지 않으며, 슬롯별 진입 횟수를 기록하는 필드도 없다. issue #18 은 (1) slot-scoped `entry_count` 필드, (2) idle+closed 후보 중 `entry_count` 최소 우선 선택(+tie-break), (3) closed 직접 재할당, (4) 할당 전이 시 `entry_count` 증가를 요구한다. - - 핵심 OQ(1~3) 전부 결정됨 — 데이터 모델(마이그레이션 008)·admission 흐름·슬롯 계약(+`closed→pending_buy` 신규 전이) 변경을 동반하는 신규 주기다. **OQ-1**: 신규 `entry_count` 는 daily_slot row 단독(기존 symbol-scoped `daily_symbol_entries` 와 무관·분리). **OQ-2**: 선택은 `entry_count` 오름차순 → `updated_at` 오름차순 → `slot_id`. **OQ-3**: closed 를 admission 후보에 직접 포함(`closed→pending_buy` 직접 전이), `entry_count` 는 →pending_buy 시 +1, 동시성은 `SKIP LOCKED` 보존. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 슬롯 선택 정책(현재 idle-only `find_idle_slot`)과 슬롯 데이터 계약(`EXPECTED_SLOT_FIELDS`)에 `entry_count` 가 없어, 신규 영속 필드 + 마이그레이션 + admission 흐름 변경을 요구한다. 기존 REQ 문구 미세 조정으로 흡수되지 않는다. - - 기존 `daily_symbol_entries`(symbol-scoped 재진입 카운터, REQ-002 AC-3)와 목적·키가 다른 slot-scoped 카운터를 도입하고, closed 슬롯 재할당이라는 새 도메인 정책을 정의하므로 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **slot 진입 횟수 필드 없음**: `Slot` 모델(`trex_core/domain/slot/models.py`)에 진입 횟수 필드가 없고, `EXPECTED_SLOT_FIELDS`(test_contract.py 로 강제되는 엄격 컬럼 계약)에도 없다. daily_slot 스키마(`sql/001_create_slot_core_tables.sql`)도 slot row 에 진입 카운터를 두지 않는다. - - **admission 은 idle-only 임의 선택**: buy admission(`trex_core/application/intents.py:_admit_buy`)은 `repo.find_idle_slot`(`postgresql.py:108`, `WHERE status='idle' LIMIT 1`)으로 idle 슬롯 1개를 순서 규칙 없이 잡는다. closed 슬롯은 admission 후보가 아니며, 우선순위(진입 횟수) 개념이 없다. - - **closed 재사용은 별도 복구 전이**: 상태 머신(`domain/slot/state.py`)에 `closed→idle` 복구 전이(#7)가 있고(`service.py:130`), 이때만 슬롯이 다시 idle 후보가 된다. admission 이 closed 를 직접 후보로 보지는 않는다. - - **별개의 symbol-scoped 카운터(본 주기 범위 밖)**: `daily_symbol_entries` 테이블(`sql/001`, `trading_date+symbol → entry_count, last_entry_at`)이 `closed→idle` 복구 시 +1 증가한다(`postgresql.py:160`, REQ-002 AC-3). 이름은 같은 "entry_count" 이나 **키(symbol)·목적(심볼 재진입 결정 입력)이 slot-scoped 할당 정렬과 전혀 다르다**. 게다가 현재 **production 소비자가 없다**(`get_today_entry_count` 읽기는 테스트뿐 — 재진입 제한 게이트 미배선, trex-orbit 계보 보존 불변식 REQ-002 NFR-2). 따라서 신규 slot `entry_count` 와 **개념적으로 무관**하며, 본 Discovery 는 이 테이블을 **변경하지 않는다**(분리). 제거는 별도 트랙(아래 Out of Scope 참고). - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`)는 존재하며 최신이다. 슬롯 할당·상태 전이는 기존 runtime flow 에 속한다. -- 기대 상태 - - daily_slot(슬롯 row)에 slot-scoped 진입 횟수가 영속되고, buy admission 이 idle+closed 후보 중 진입 횟수가 가장 적은 슬롯을 결정적 규칙(tie-break 포함)으로 선택한다. - - closed 슬롯이 재할당 후보로 명시적으로 취급되어 슬롯 자원이 효율적으로 재사용된다. - - 진입 횟수 증가 시점(할당 전이)이 명확히 정의되고, 신규 slot `entry_count` 는 daily_slot row 단독 source of truth 로 `daily_symbol_entries` 와 분리되어 이중 소스 모호성이 없다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 작성 - - 리뷰어/승인자 — 데이터 모델·admission 정책·카운터 정합 결정 승인 - - 운영 담당자 — 슬롯 재사용·진입 이력 관측 - - 전략 모듈(간접) — admission 결과(어느 슬롯이 잡히는지)에 의존하나 계약상 슬롯 선택은 core 책임 -- 제약사항 - - daily_slot 스키마 변경은 멱등 마이그레이션(008)으로 추가하고, `EXPECTED_SLOT_FIELDS` 계약·persistence flat slot 매핑·`data-model.md` 를 같은 변경 묶음으로 정합해야 한다(`change-req` Common Pitfall 1). - - admission 의 동시성(마지막 슬롯 경합) 보존 — 현재 `FOR UPDATE SKIP LOCKED`/재시도 규약(`intents.py`)을 깨지 않아야 한다. - - **상태 머신 불변식 변경**: 현재 "6 상태 / 8 허용 전이"(`state.py` `ALLOWED_TRANSITIONS`, REQ-001/002 NFR-2, dcy-001 FR-2)에 `closed→pending_buy` 가 없다. 본 주기는 이 전이를 추가(8→9 전이)하므로 REQ-001/002 의 전이 집합 계약·전이 카운트 서술을 change-req 로 함께 정합해야 한다. - - 신규 slot-scoped `entry_count` 와 기존 `daily_symbol_entries`(symbol-scoped, REQ-002 AC-3)는 본 주기에서 분리 유지(후자 미변경) — 회귀를 깨지 않는다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 하루 중 일부 슬롯이 진입·청산(closed)을 반복한 뒤, 새 buy 가 들어오면 admission 이 idle 뿐 아니라 closed 슬롯도 후보로 보고 **진입 횟수가 가장 적은** 슬롯을 골라 슬롯 사용을 고르게 분산한다. -- idle 슬롯이 모두 소진됐지만 closed 슬롯이 남아 있을 때, 현재는 `no_idle_slot` 으로 거부되던 buy 가 closed 재할당으로 admit 된다. -- 운영자가 슬롯별 누적 진입 횟수를 조회해 그날 슬롯 회전·집중도를 파악한다(분석 기반). - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- daily_slot/`Slot` 모델에 slot-scoped `entry_count`(기본 0) 필드 추가 + 마이그레이션 008 + `EXPECTED_SLOT_FIELDS` 계약·persistence 매핑·`data-model.md` 정합. -- buy admission 슬롯 선택 정책 변경: 후보 = idle ∪ closed, 정렬 = **`entry_count` 오름차순 → `updated_at` 오름차순(가장 오래 안 쓴 슬롯) → `slot_id`**(OQ-2), 동시성(SKIP LOCKED/재시도) 보존. `find_idle_slot` → 후보 확장 selector 로 일반화. -- closed 슬롯 재할당 경로 정의(OQ-3): admission 이 closed 를 **직접 후보**로 잡아 **`closed→pending_buy` 신규 전이**(state.py 전이 추가)로 점유 필드 초기화·설정. `entry_count` 는 할당 전이(→pending_buy) 시 +1. -- baseline 문서(`docs/runtime-flows.md` admission/슬롯 선택 흐름, `docs/data-model.md` daily_slot 필드) 갱신. (신규 `entry_count` 가 daily_slot 단독임을 명시하고, 기존 `daily_symbol_entries`(심볼 재진입 카운터)와 역할이 다름을 data-model 에 구분 기록.) - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/domain/slot/models.py` — `Slot.entry_count` + `EXPECTED_SLOT_FIELDS` - - `trex_core/adapters/persistence/sql/008_*.sql` — daily_slot `entry_count` 컬럼 추가(멱등) - - `trex_core/adapters/persistence/postgresql.py` — flat slot 매핑, 후보 selector(idle∪closed, ORDER BY entry_count, SKIP LOCKED), 증가 로직 - - `trex_core/domain/slot/repository.py` / `in_memory_repository.py` — selector 포트 + 인메모리 동등 구현 - - `trex_core/application/intents.py` — `_admit_buy` 후보 selector 사용 + closed 재할당 처리 - - `trex_core/domain/slot/service.py` / `state.py` — **`closed→pending_buy` 신규 전이 추가**(허용 전이 집합), 할당 전이(→pending_buy) 시 `entry_count` +1, closed 직접 할당 시 점유 초기화·설정 -- 문서 영향: - - `docs/data-model.md` — daily_slot `entry_count` 필드 - - `docs/runtime-flows.md` — buy admission 슬롯 선택(idle∪closed, entry_count 정렬) 흐름 -- 운영 영향: - - 마이그레이션 008 적용(additive 컬럼, 기본 0) — 배포 시 1회. 슬롯 선택 동작이 바뀌므로 회귀 관측 필요. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: daily_slot/`Slot` 에 slot-scoped `entry_count` 정수 필드(기본 0)를 추가하고 멱등 마이그레이션 008 + `EXPECTED_SLOT_FIELDS` 계약 + persistence flat 매핑 + `data-model.md` 를 정합한다. -- FR-2: buy admission 은 후보를 idle ∪ closed 로 확장하고, **`entry_count` 오름차순 → `updated_at` 오름차순(가장 오래 안 쓴 슬롯) → `slot_id`**(OQ-2) 순으로 결정적 선택한다. 마지막 슬롯 경합 동시성(`FOR UPDATE SKIP LOCKED` + 재시도)을 보존한다. -- FR-3: closed 슬롯은 admission 후보에 **직접 포함**되며, 선택 시 별도 `closed→idle` 왕복 없이 **`closed→pending_buy` 직접 전이**한다(OQ-3 — 신규 전이). 이때 점유 필드(symbol/qty/avg_price/limit_price/...)를 새 진입 값으로 초기화·설정한다(기존 `closed→idle` 리셋 의미 재사용). -- FR-4: slot `entry_count` 는 **할당 전이(idle/closed → pending_buy) 시 +1** 증가한다(OQ-3). 단일 트랜잭션 내에서 슬롯 claim·상태 전이·`entry_count` 증가를 함께 수행해 동시 buy 경합에서도 중복 카운트가 없다. 신규 `entry_count` 는 daily_slot row 단독이며 `daily_symbol_entries`(심볼 재진입 카운터, REQ-002 AC-3)와 **무관·독립**이다 — 본 주기에서 후자를 읽거나 변경하지 않는다. -- FR-5: `docs/runtime-flows.md`(admission 슬롯 선택)·`docs/data-model.md`(daily_slot) baseline 문서를 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 동시성 안전 — 슬롯 선택·`entry_count` 증가는 advisory lock/`SKIP LOCKED`/단일 트랜잭션으로 경합·중복 카운트 없이 처리한다. -- NFR-2: 결정성 — 동일 후보 집합·동일 `entry_count` 분포에서 tie-break 규칙으로 선택 결과가 결정적이고 재현 가능하다. -- NFR-3: 하위호환 — 마이그레이션은 additive(기본 0), 기존 idle-only 동작은 closed 후보가 없을 때 결과적으로 보존(회귀 안전). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - slot-scoped `entry_count` 필드 + 마이그레이션 008 + 계약/매핑/data-model 정합 - - admission 후보 확장(idle∪closed) + `entry_count` 정렬 + tie-break + closed 재할당 + 증가 시점 - - 기존 `daily_symbol_entries` 와의 관계 확정 + runtime-flows/data-model 갱신 -- Out of Scope - - 슬롯별 수익률/PnL 집계(이슈 고려사항대로 ledger/log 기반이 적절 — 본 주기 제외) - - 전략 모듈에 슬롯 선택 정책을 노출하는 계약 변경(슬롯 선택은 core 책임 유지) - - preallocation 총량/스케줄 정책 변경(dcy-014 범위 — 불변) - - **`daily_symbol_entries`(심볼 재진입 카운터) 제거/변경 — 본 주기 제외**. production 소비자 없음이 확인됐고 심볼 재진입 제한 미사용으로 결정됐으나, 보존 불변식(REQ-002 NFR-2)이라 캐주얼 삭제 불가 → **별도 REQ-002 change-req 트랙**으로 처리(후속). - - 운영자 CLI/스냅샷에 slot `entry_count` 노출 — **본 주기 제외**(이슈가 분석/노출을 후순위로 명시). 필요 시 후속 change-req 후보. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Low): 신규 slot `entry_count` 와 기존 `daily_symbol_entries.entry_count` 가 같은 이름이라 코드/문서에서 혼동될 수 있다. (OQ-1 결정으로 이중 소스 자체는 해소 — 둘은 분리·무관, 후자는 본 주기 미변경.) - - 완화 방안: data-model 에 두 카운터의 키·목적·증가 시점을 명확히 구분 기록. 신규 필드는 daily_slot 컬럼으로 테이블이 달라 물리적 충돌 없음. REQ-002 AC-3 회귀 보존(후자 미변경). -- R-2 (Medium): admission 후보에 closed 를 포함하면서 동시성(마지막 슬롯 경합)·점유 리셋이 어긋나면 잘못된 슬롯 재사용/상태 오염이 발생할 수 있다. - - 완화 방안: `FOR UPDATE SKIP LOCKED` + 단일 트랜잭션 내 상태/점유/카운트 갱신, closed 직접 할당 시 기존 `closed→idle` 리셋 로직 재사용, 경합 회귀 테스트. -- R-3 (Medium): daily_slot 컬럼 추가가 `EXPECTED_SLOT_FIELDS` 계약·flat 슬롯 read/write 순서를 깨면 광범위 회귀가 난다. - - 완화 방안: 모델·계약·persistence 매핑·data-model 을 같은 변경 묶음으로 갱신하고 `test_contract.py` green 확인. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: `entry_count` 는 trading_date 범위 내 slot row 기준(일 단위 preallocation 과 정합). 일자 경계에서 새 슬롯은 0 으로 시작한다. -- A-2: closed 슬롯은 같은 trading_date 안에서 재할당 후보다(전일 슬롯을 끌어오지 않는다 — dcy-014 trading_date 경계 유지). -- A-3: 슬롯 선택은 core admission 단독 책임이며 전략 모듈 계약에 영향을 주지 않는다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: slot-scoped `entry_count` 의 source of truth 를 어떻게 둘 것인가 — (a) daily_slot row 단독, (b) 기존 `daily_symbol_entries` 병행 유지, (c) `daily_symbol_entries` 를 slot-scoped 로 대체/재정의. - - 상태: Answered (2026-06-09, 사용자 결정) - - 결정: **(a) daily_slot row 단독.** 신규 `entry_count` 는 `daily_symbol_entries`(심볼 재진입 카운터)와 개념·키·목적이 무관 → 본 주기에서 완전 분리(후자 미변경). 심볼 재진입 제한은 사용하지 않기로 했고 `daily_symbol_entries` 는 현재 production 소비자가 없어, 그 제거는 별도 REQ-002 change-req 트랙으로 후속 처리(본 주기 Out of Scope). 반영: §3 현재 상태/기대 상태, §4, FR-4, R-1. -- OQ-2: 동률(`entry_count` 동일) 시 tie-break 규칙은 무엇인가. - - 상태: Answered (2026-06-09, 사용자 결정) - - 결정: **`entry_count` 오름차순 → `updated_at` 오름차순(가장 오래 안 쓴 슬롯) → `slot_id`**. 결정적(NFR-2) + 직관적. 반영: FR-2, §4. -- OQ-3: closed 재할당 경로와 `entry_count` 증가 시점. - - 상태: Answered (2026-06-09, 사용자 결정) - - 결정: closed 를 admission 후보에 **직접 포함**하고 별도 `closed→idle` 왕복 없이 **`closed→pending_buy` 직접 전이**(state.py 신규 전이). `entry_count` 는 **할당 전이(→pending_buy) 시 +1**. 동시성은 기존 `FOR UPDATE SKIP LOCKED` 로 보존. 반영: FR-3/FR-4, §4. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: idle 이 없고 closed 가 있는 상태에서 buy 가 closed 슬롯으로 admit 된다(이전엔 `no_idle_slot` 거부). -- S-2: 여러 후보가 있을 때 `entry_count` 최소 슬롯이 선택되고, 동률 시 tie-break 규칙대로 결정적으로 선택된다. -- S-3: 할당 전이 시 해당 slot 의 `entry_count` 가 정확히 1 증가하며, 동시 buy 경합에서도 중복 카운트가 없다. 기존 `daily_symbol_entries`(REQ-002 AC-3) 회귀가 green. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: idle=0, closed≥1 픽스처에서 admission 단위/통합 테스트 / 데이터 출처: daily_slot 상태 + admission 결과 -- S-2 측정 방식: 다중 후보(상이/동일 entry_count) 픽스처에서 선택 결과 단언 / 데이터 출처: daily_slot `entry_count` + 선택된 slot_id -- S-3 측정 방식: 단일/동시 admission 후 `entry_count` 증분 단언 + `daily_symbol_entries` 회귀 테스트 / 데이터 출처: daily_slot.entry_count, daily_symbol_entries.entry_count - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 운영자 노출은 본 주기 제외로 확정. In/Out 경계 명확. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~3 측정 픽스처/데이터 출처(daily_slot.entry_count/상태)가 테스트 가능한 형태로 정의됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(이중 소스)은 OQ-1 결정(daily_slot 단독·분리)으로 Low 로 해소됨. 잔여 High 리스크 없음. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1/2/3 전부 Answered (2026-06-09 사용자 결정) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 승인자 Justin confirm-discovery 수행 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-019_20260609_slot-entry-count-and-closed-reallocation.md -- 참조 문서: - - docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md (슬롯 도메인·daily_symbol_entries 기원, REQ-002) - - docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md (일자 경계 preallocation) - - docs/data-model.md, docs/runtime-flows.md (baseline — 갱신 대상) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 (OQ-1/2/3 사용자 결정 완료 — §6 참조. 운영자 노출은 본 주기 제외로 확정 — §5) - -## CONFIRM -- 없음 (이슈 #18 재구성 ①entry_count 필드 ②idle/closed 중 entry_count 최소 우선 ③closed 재할당 후보 ④할당 전이 시 증가 — 사용자가 OQ-1/2/3 결정으로 확인함) - -## DATA -- 없음 (외부 데이터 의존 없음 — 전부 내부 도메인/스키마 결정) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1/2/3 전부 Answered(사용자 결정 반영), C형 플레이스홀더 0, 저장소 사실 재확인 일치(slot entry_count 부재·idle-only admission·closed→pending_buy 미존재 검증). REQ Drafting 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-09 13:34 KST diff --git a/docs/discovery/dcy-020_20260610_asset-centric-slot-model.md b/docs/discovery/dcy-020_20260610_asset-centric-slot-model.md deleted file mode 100644 index 6137713..0000000 --- a/docs/discovery/dcy-020_20260610_asset-centric-slot-model.md +++ /dev/null @@ -1,289 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-020_20260610_asset-centric-slot-model -- 이슈명: 자산 중심 슬롯 모델 재설계 — portfolio-hub universe 와 1:1 슬롯 + 활성 슬롯 개수 제어 (일반 슬롯 풀 모델 대체) -- 작성 시각(KST): 2026-06-10 01:04 KST -- 마지막 갱신 시각(KST): 2026-06-10 01:04 KST (OQ-1 Answered: FCFS+무치환; FR-3/FR-4 holding/수량 정본 분리 결정; OQ-5 추가: 불일치 해소 정책; OQ-3 Answered: 영구 슬롯 + trading_date 강등 + dcy-014 폐기; OQ-2 Answered: 생성 즉시·제거 soft-retire/tombstone·degrade 보류; OQ-4 Answered: 포지션 사이징 분리·토대만; OQ-5 Answered: 불일치 해소 ground truth=브로커·표면화·운영자 게이트; A-1 CONFIRMED: 자산 중심 대전제; OQ-6 추가·Answered: M=per-instance TOML; review 정리 반영(§3 시나리오·§4·§11 현행화); OQ-7 추가·Answered: 공유 DB + instance_key 스코프(UNIQUE(instance_key,portfolio_id)) + advisory lock 인스턴스 스코프화; FR-6/7/8 추가(상태머신·intent 자산키·운영자 단일뷰), NFR-4/5 추가(전략 계약 호환·불일치 감지 주기), dcy-019 산출물 제거 명시, OQ-8 추가·Answered(over-cap=신규 차단·기존 유지); confirm-discovery 승인(2026-06-10 10:00 KST, Justin Ko) → 상태 confirmed; draft-req: REQ-056~065 생성) -- 대체됨: 없음 -- 후속 Discovery 참조: docs/discovery/dcy-021_20260610_position-sizing-target-intent.md (OQ-4 포지션 사이징 분리 주기), docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md (Out-of-Scope "중장기 전용 운영(issue #20)" 이월 주기) -- 생성된 REQ 참조: REQ-056(영구 슬롯 스키마), REQ-057(상태머신/lock), REQ-058(universe reconcile), REQ-059(capacity/admission), REQ-060(max_active_slots TOML), REQ-061(intent 자산 키), REQ-062(수량/불일치), REQ-063(slots 단일 뷰), REQ-064(cutover), REQ-065(baseline). 기존 REQ-002/003/005/006/007/048 등은 폐기/재정의 change-req 동반(예정). - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #1(포지션 사이징/선언형 target intent), issue #20(중장기 자산 중심 운영). 기존 dcy-001/002(슬롯 도메인·intent), dcy-005(스냅샷 universe read), dcy-014(일일 preallocation), dcy-019(slot entry_count + closed 재할당)와 직접 교차. -- 해석: - - 현재 모델은 **익명 슬롯 풀**(일일 N개 preallocation, 매수 신호 시 idle 슬롯을 자산에 동적 배정)이다. 운영 마찰(universe↔slots 표면 혼동 BAT-022, `no_idle_slot`/할당 경합, entry_count 재사용 정책 dcy-019)과 issue #1(포지션 사이징 자리 없음)·#20(자산 중심 다일 보유와 충돌)이 모두 이 풀 모델 전제에서 비롯된다. - - 본 Discovery 는 슬롯을 **portfolio-hub universe 자산과 1:1**로 두고("슬롯=자산 포지션"), capacity gate 를 "유휴 슬롯 수"에서 "**동시 활성(holding) 최대 M개**" 정책으로 바꾸는 모델 전환을 판정한다. issue #1·#20 을 흡수하는 상위 substrate 재설계이며, 기존 dcy-001/002/014/019 의 풀 모델 전제를 재정의한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy 들은 모두 **익명 슬롯 풀** 전제(일일 count preallocation·idle 배정·closed 재할당) 위에 쌓였다. 자산 1:1 슬롯 + 활성 개수 제어는 슬롯의 정체성·할당·capacity 개념을 **재정의**하므로 기존 문서의 미세 조정으로 흡수되지 않는다. - - issue #1(포지션 사이징)·#20(중장기) 과 교차하나, 그 둘을 따로 풀지 않고 substrate(슬롯 모델)를 바꿔 동시에 단순화하는 독립 주기다. 기존 dcy-001/002/014/019 는 입력·영향(폐기/재해석 범위)으로 참조한다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **익명 슬롯 풀 + 일일 count preallocation**: `preallocate_daily_slots(trading_date, count)`(`preallocation_scheduler.py`, dcy-014)가 매일 `TREX_TOTAL_SLOTS`(기본 10)개의 idle 슬롯을 생성한다. 슬롯은 특정 자산에 묶이지 않은 익명 capacity 다. - - **매수 신호 시 동적 배정**: buy admission(`intents.py:_admit_buy`)이 `find_allocatable_slot`(idle∪closed, dcy-019/BAT-025)로 슬롯 1개를 잡아 portfolio_id 를 채운다. capacity gate 는 `available_slots = idle_count`(`portfolio.py`/`messaging_handlers.py`). - - **universe 와 slots 가 별개 표면**: universe(자산 리스트)는 portfolio-hub `get_portfolios(daemon_id)`(`hub_read.py`, `managed_by_daemon_id` 필터)로 읽고, slots(슬롯 상태)는 별도다. 둘의 임피던스가 운영 혼동(BAT-022: `slots` 가 universe 를 슬롯으로 오인 표시)과 정합 마스킹(REQ-031: hub 수량 vs 브로커 수량 불일치 시 universe 마스킹)을 낳았다. - - **포지션 사이징 자리 없음**: 상태머신(`state.py`)이 이산적(IDLE→PENDING_BUY→HOLDING→PENDING_SELL→CLOSED)이라 `HOLDING→HOLDING`(수량 변경)·부분 매도가 없다(issue #1). - - **일일 리셋 vs 영구 자산**: 슬롯이 trading_date 로 일일 preallocation 되어 다일 보유 자산 모델(issue #20)과 충돌한다. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/data-model.md`)는 존재·최신이며 슬롯 풀·preallocation·admission 흐름을 기술한다 — 본 전환 시 대폭 갱신 대상. -- 기대 상태 - - 슬롯이 portfolio-hub universe 자산과 **1:1**로 대응되어 "슬롯=자산 포지션"이 된다. 익명 배정·할당 경합·closed 재할당 개념이 사라진다. - - capacity 제어가 "**동시 활성(holding) 슬롯 최대 M개**" 정책으로 표현되어, 활성화는 FCFS(신호 도착 순)이고 M 이 차면 새 신호를 거부한다(코어 자율 치환 없음, OQ-1). 능동적 capacity 배분은 전략 선언형 target(OQ-4)으로 분리한다. - - universe↔slots 표면이 합쳐져 운영 관측이 단순해진다. 다일 보유(#20)는 영구 슬롯으로 이번 주기에 실현되고, 포지션 사이징(#1)은 본 토대 위 **후속 주기**로 얹힌다(OQ-4 분리). -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 - - 리뷰어/승인자 — 슬롯 모델·capacity 정책·기존 작업 폐기 범위 승인 - - 운영 담당자 — 자산별 포지션·활성 수 관측 - - 전략 모듈 — 슬롯=자산이면 신호·소유 시맨틱(dcy-005 OQ-9)이 바뀜 - - portfolio-hub 팀 — universe 가 슬롯 집합의 source of truth 가 됨(자산 추가/제거 동기화) -- 제약사항 - - 검증·출시된 풀 모델 경로(REL-001~004, BAT-001~023)와 방금 출시한 dcy-019/BAT-025(entry_count·closed 재할당)를 **재정의/폐기**해야 한다 — 회귀·이행 비용이 크다. - - portfolio-hub universe 가 변동(자산 추가/제거)하므로 슬롯 생성/소멸 lifecycle 을 스냅샷 refresh 와 결합해야 한다. - - 단일 writer·동시성·불변식(advisory lock, REQ-002 NFR-1)을 새 모델에서도 보존해야 한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- portfolio-hub 가 45개 자산을 제공하면 trex-core 에 45개 자산 슬롯이 생기고, 운영자는 "동시 활성 최대 M개" 만 제어한다 — 어떤 자산을 살지는 슬롯 배정이 아니라 활성 선택으로 결정된다. -- 매수 전략이 자산 A 신호를 주면 A 의 슬롯이 활성화(holding)되고, 활성 수가 M 미만이면 즉시 활성화, M 이 차 있으면 거부된다(FCFS·코어 자율 치환 없음, OQ-1). 회전은 전략의 명시적 sell→buy 로 한다. -- (후속 주기) 보유 중 전략이 목표 수량을 바꾸면(issue #1) 같은 자산 슬롯의 수량이 조정된다(별도 슬롯 불필요) — 본 주기는 토대만, 포지션 사이징은 분리(OQ-4). -- 운영자가 `trex-core slots` 로 자산별 포지션·활성 여부를 한 표면에서 본다(universe↔slots 분리 해소). - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 슬롯 정체성 재정의: 익명 풀 → **portfolio-hub universe 자산과 1:1 바인딩**(슬롯 = 자산 포지션, portfolio_id 영구 키). -- 슬롯 lifecycle 재설계: 일일 count preallocation(dcy-014) → **universe 기반 슬롯 생성/소멸**(스냅샷 refresh 와 결합). idle 풀·`find_allocatable_slot`·closed 재할당(dcy-019) 폐기/대체. -- 슬롯 영속성 전환(OQ-3): 일일 리셋 → **영구 슬롯**(키=`portfolio_id`, `UNIQUE`, 날짜 없음). `daily_slot.trading_date` 컬럼 드롭/재의미, trading_date 는 이벤트/원장/정책의 시간 속성으로 강등. 일일 daily_slot 행 → 자산당 영구 1행 cutover + 보유 carry-over. -- capacity 제어 전환: `available_slots`(유휴 수) → "**동시 활성 holding 최대 M개**" 정책. 활성 수 == M 이면 신규 신호 거부(FCFS·무치환, OQ-1). M 값은 per-instance TOML 설정에서 읽는다(OQ-6). -- 상태머신 재해석(FR-6): 익명 슬롯의 IDLE/CLOSED → 영구 슬롯의 **단일 `inactive`** 로 합침(`closed→idle` 복구 전이 제거), 집합 = `inactive→pending_buy→holding→pending_sell→inactive`(+`error`). 활성=진입, 종료=전량 청산. **HOLDING→HOLDING 수량 변경·부분 매도 미포함**(포지션 사이징 OQ-4 분리). 수량 가변 전제는 모델에 남김. -- intent 계약 통일(FR-7): 매수·매도 모두 portfolio_id(자산) 키, `sell-by-slot_id` 폐기, 중복/무효 신호 멱등 거부(`already_active`/`not_holding`). -- 운영자 표면 단일화(FR-8): `trex-core slots` = 자산 포지션 단일 뷰(universe↔slots 이중 표면 해소, BAT-022). -- dcy-019 산출물 제거(FR-5): `find_allocatable_slot`·`closed→pending_buy` 전이·slot `entry_count` 제거(FCFS·영구 슬롯이 대체). `daily_symbol_entries` 폐기. -- baseline 문서(`docs/project-structure.md`/`docs/runtime-flows.md`/`docs/data-model.md`) 전면 갱신 + 기존 dcy-001/002/014/019 와의 대체/재해석 관계 명시. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/domain/slot/` — 상태머신(state.py)·서비스(service.py)·모델(models.py): 슬롯 정체성·전이·capacity 개념 재정의 - - `trex_core/application/preallocation_scheduler.py` — count preallocation → universe 기반 생성/소멸로 대체 - - `trex_core/application/intents.py` — `find_allocatable_slot` 배정 경로 → 자산 슬롯 활성화/선택 경로 - - `trex_core/application/snapshot_scheduler.py` / `portfolio.py` — universe fetch 와 슬롯 집합 동기화 결합, `available_slots`→활성 수 개념 - - `trex_core/adapters/persistence/postgresql.py` + 마이그레이션 — daily_slot 스키마(자산 키·활성 플래그·`retired_at`·`instance_key`) 재설계, `UNIQUE(instance_key, portfolio_id)`; slot-scoped 테이블 전반 instance 스코프화 + 모든 쿼리에 instance 필터(OQ-7) - - `trex_core/domain/slot/state.py`·`service.py` — advisory lock 인스턴스 스코프화(`(SLOT_LOCK_ID, hash(instance_key))`, NFR-1 재정의, OQ-7) - - `trex_core/runtime/config.py`(`DaemonInstanceConfig`) + `config/*.toml` — 동시 활성 최대치 M 설정 키(예: `max_active_slots`) 추가(OQ-6, REQ-048 per-instance TOML 계열) -- 문서 영향: - - `docs/data-model.md`(daily_slot 재설계: instance_key·retired_at·entry_count 제거·trading_date 강등), `docs/runtime-flows.md`(admission→활성화 흐름·reconcile lifecycle·불일치 해소), `docs/project-structure.md`(슬롯 모델) - - `trex_core/domain/slot/state.py`·`models.py` — 상태 집합 재정의(FR-6: idle/closed→inactive), `entry_count` 제거 - - `trex_core/application/intents.py`·메시지 계약 — intent 자산 키 통일·sell-by-slot_id 폐기(FR-7); `trex_core/runtime/cli.py` slots 단일 뷰(FR-8) - - (제거) `daily_symbol_entries` 테이블·`find_allocatable_slot`·`closed→pending_buy` 전이(dcy-019 산출물) -- 운영 영향: - - 슬롯 수가 universe 크기에 연동, capacity 운영이 "활성 M개" 정책으로 전환. 마이그레이션·이행 절차 필요. 기존 풀 모델 운영 가정 변경. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 슬롯은 portfolio-hub universe 의 자산과 1:1 로 대응한다(자산 portfolio_id 가 슬롯의 **영구 키**, 날짜 없음 — OQ-3). **다중 인스턴스는 DB 를 공유하고 슬롯은 `instance_key` 로 스코프**하므로 슬롯 유일성은 **`UNIQUE(instance_key, portfolio_id)`** 이다(OQ-7). `daily_slot` 에 `instance_key` 컬럼 추가, slot-scoped 테이블 전반(slot_transition_log/intent_log/ledger_outbox/snapshot_pause/killswitch_exclusion 등)도 instance 스코프화·쿼리 필터. `daemon_id` 는 universe 파티션(get_portfolios)·감사용으로 (선택) 병행 저장하되 스코프 키는 아니며, instance_key↔daemon_id 1:1 정합을 가드한다(OQ-7). 슬롯은 **영구**이며 날 경계로 리셋되지 않고, universe 동기화(reconcile, OQ-2) 시: 신규 자산 → 슬롯 즉시 생성(비활성·멱등); 제거 자산 → **비활성이면 soft-retire(행 보존, `retired_at`/`universe_detached` 마킹, hard delete 금지)·보유 중/pending 이면 tombstone(safety-sell 유지·신규 매수 제외, 닫힌 뒤 은퇴)**. reconcile 은 스냅샷 refresh 경로(부팅+매일 07:30 KST, +운영자 on-demand)에서 단일 writer(advisory lock)로 수행. `retired_at`/`universe_detached` 는 status 와 직교한 마커. 슬롯 행의 기존 `trading_date` 컬럼은 드롭/재의미하고, trading_date 는 이벤트/원장/정책의 시간 속성으로 강등(OQ-3). 일일 preallocation(dcy-014)은 폐기하고 슬롯 생성을 universe reconcile 로 대체. -- FR-2: capacity 는 "동시 활성(holding) 슬롯 최대 M개"로 제어한다. 활성화는 **FCFS(신호 도착 순)**이며 활성 수 == M 이면 새 신호를 **거부(`capacity_full`)**한다 — 코어는 자리를 만들려고 기존 holding 을 자율 청산(치환)하지 않는다(OQ-1). 도착 순서는 단일 writer 직렬화로 결정적이고, 동시 후보 tie-break 은 `updated_at` asc → `portfolio_id`(alpha 비사용). capacity 를 최고 기회에 배분하는 능동 선택이 필요하면 전략의 선언형 target(OQ-4/issue #1)으로 처리한다. **M 값은 per-instance TOML 설정(`config/*.toml`, 예: `max_active_slots`)에서 읽는다**(REQ-048 per-instance TOML 계열, OQ-6) — 인스턴스별 독립 운영. -- FR-3: 매수/매도 신호는 익명 슬롯 배정이 아니라 **해당 자산 슬롯의 활성화/비활성화**로 처리한다. 동시성·단일 writer·불변식(advisory lock)을 보존한다. **holding(포지션 열림 여부)은 코어 슬롯의 로컬 정본**으로, 체결 시 동기 set·매도 체결 시 동기 clear 한다 — portfolio-hub `asset_quantity` 로 판단하지 않는다. 근거: (1) 자율 안전 매도(forced_close/stop_loss/take_profit/market_close/watchdog/kill-switch)는 portfolio-hub 불가 시에도 독립 동작해야 하므로(REQ-024) 보유 사실을 코어 durable 상태에서 알아야 한다; (2) universe 에서 자산이 빠지면 `get_portfolios` 에서 사라져 `asset_quantity` 신호가 증발하지만 로컬 holding 은 생존(OQ-2 고아 포지션 방지); (3) hub 수량은 ledger outbox 비동기 전파(dcy-015)라 지연되므로 안전 판단의 primary 신호로 부적합; (4) capacity(M개, FR-2) 카운트의 원자성은 단일 writer 슬롯 상태라야 보장. (사용자 결정 2026-06-10) -- FR-4: 보유 **수량(asset_quantity)의 정본은 portfolio-hub**이며, 슬롯은 수량을 정본 소유하지 않고 **로컬 best-effort 미러**(option D write-back `*_after`·추정 오버레이, BAT-023)로만 둔다. 실제 청산 시 매도 수량은 **브로커 잔고(`BrokerPort.get_balance().hldg_qty`)를 정본**으로 조회한다 → 로컬 미러가 stale 해도 청산 정확. 브로커 잔고는 포지션 존재·수량 불일치의 **reconcile ground truth** 이기도 하다(OQ-5). 즉 "포지션 열림(boolean)=코어 로컬, 수량=hub 정본+로컬 미러"로 분리. (issue #1 연계) 활성 슬롯의 보유 **수량 변경(포지션 사이징)** 은 본 주기에서 **분리(OQ-4 Out of Scope)** — 단 수량 가변 전제는 모델에 남긴다(토대 가드). 슬롯이 `qty`/`avg_price` 를 정본 소유하던 풀 모델(reconcile_positions/REQ-031 warning, option D write-back 의 drift 원인)은 본 분리로 정리한다. (사용자 결정 2026-06-10) -- FR-5: `docs/data-model.md`·`docs/runtime-flows.md`·`docs/project-structure.md` 를 새 모델로 갱신하고, dcy-001/002/014/019 의 폐기/재해석 범위를 명시한다. **dcy-019 산출물 구체 처분**: `find_allocatable_slot` selector·`closed→pending_buy` 전이·slot `entry_count` 필드는 FCFS·영구 슬롯 전환으로 **제거**(슬롯 회전 분석이 필요하면 `slot_transition_log` 로 도출, 슬롯 컬럼 아님). `daily_symbol_entries`(symbol-scoped 재진입 카운터)도 자산 중심 모델에서 무의미 → 폐기(별도 트랙과 합류). -- FR-6: **슬롯 상태머신을 자산 영구 슬롯에 맞게 재정의**한다. 기존 `idle`(매수 전)·`closed`(매도 후)는 영구 슬롯에서 **단일 `inactive`(포지션 없음)** 로 합쳐지고(dcy-001 `closed→idle` 복구 전이 제거), 상태 집합 = `inactive → pending_buy → holding → pending_sell → inactive`(+ 모든 비-error → `error`, `error → {inactive, holding}` 운영자 resolve). `retired_at`/`universe_detached`(OQ-2)·`instance_key`(OQ-7)는 status 와 **직교한 속성**으로 둔다. 활성=진입, 종료=전량 청산(HOLDING→HOLDING·부분 매도 미포함, OQ-4). -- FR-7: **intent 계약을 슬롯 native 키(`slot_id`)로 통일**한다(2026-06-10 재정의 — 아래 주). 매수·매도 모두 `slot_id` 로 식별하고 스냅샷 `slots[]` 가 계약 1차 단위; portfolio_id 는 표시/자산 정체성으로 잔존(계약 키 아님). 매수 = 그 슬롯 활성화(`inactive→pending_buy`, symbol 은 슬롯 portfolio_id 로 resolve), 매도 = 비활성화(`holding→pending_sell`). **중복/무효 규칙**: 이미 활성(`pending_buy`/`holding`/`pending_sell`)인 슬롯에 매수 → `already_active`; 보유(holding) 아닌 슬롯에 매도 → `not_holding`. intent_id 멱등 보존. - - **[재정의 2026-06-10, 사용자 결정 / REQ-061 CHG-20260610-03]** 최초 FR-7 은 `portfolio_id` 키 통일(+sell-by-slot_id 폐기)였으나, trex-core 가 슬롯 기준 관리이고 스냅샷이 `slots[]` 를 단위로 주므로 **코어 native 키 `slot_id` 로 통일**하는 것이 일관적이고 portfolio-hub 식별 체계의 계약 누수를 막는다. 자산 영구 슬롯이라 매수도 slot_id 로 지정 가능(슬롯 선존재). 매도는 이미 slot_id(불변); 매수만 portfolio_id→slot_id 이동(레거시 portfolio_id dual-accept, 폐기 BAT-δ). slot_id 는 계약 키이자 내부 PK 로 잔존(제거 아님). -- FR-8: **운영자 표면 단일화** — `trex-core slots` 가 자산 포지션(슬롯=자산, status·holding·수량 미러·`universe_detached`/`retired`)을 **단일 뷰**로 보여준다. universe↔slots 이중 표면(BAT-022 혼동 원인) 해소. universe(자산 리스트)와 slots(자산 포지션)가 같은 자산 집합 위 view 가 된다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 동시성·일관성 — 슬롯 활성화/수량 변경은 advisory lock/단일 트랜잭션으로 경합 없이 처리(REQ-002 NFR-1 계승). **공유 DB 다중 인스턴스에서 단일 writer 불변식은 인스턴스 단위로 재정의**한다: advisory lock 을 인스턴스 스코프화(예: `pg_advisory_xact_lock(SLOT_LOCK_ID, hash(instance_key))`)해 인스턴스 간 교차 경합(한 인스턴스 전이가 타 인스턴스를 막음)을 제거한다(OQ-7). -- NFR-2: 이행 안전 — 기존 풀 모델 데이터/운영에서 새 모델로의 마이그레이션이 검증된 매수/매도 경로(REL-001~004)를 회귀시키지 않거나, 명확한 cutover 절차를 갖는다. -- NFR-3: universe 결합 견고성 — portfolio-hub 불가/degraded 시 슬롯 생성·은퇴를 **전부 보류**하고 마지막 집합을 유지한다(`SnapshotStore` degraded 신호 재사용). fetch 성공이라도 universe 가 N→0/급격 대량 감소면 글리치로 의심해 **mass-retire 보류 + 경고**(정상 단건 제거와 구분). 보유 중 자산은 universe 누락에도 tombstone 으로 보호되어 포지션을 잃지 않는다(OQ-2). 코어 holding↔브로커/hub 의 **비정상 불일치**는 브로커를 ground truth 로 감지(REQ-031 reconcile 확장)하되 조용한 자동 교정 없이 warning/`error`+운영자로 표면화하고, 실주문 발생은 운영자 게이트로 둔다(OQ-5). -- NFR-4: 전략 계약 호환 — intent 계약 변경(FR-7: 자산 키 통일, `sell-by-slot_id` 폐기)·snapshot envelope 의미 변경(FR-8)이 외부 전략 모듈을 깨뜨리므로, **계약 버전/cutover 절차**를 REQ/배치에서 명시한다(전략 모듈 동시 전환 또는 한시적 하위호환). 슬롯 선택은 여전히 core 책임(전략은 자산 신호만, A-3 계승). -- NFR-5: 불일치 감지 주기 — 코어 holding↔브로커 비정상 불일치(OQ-5 Case B, 미보호 고아 포지션)는 안전 민감이므로, 감지 reconcile 은 일일 universe refresh 와 **분리해 더 자주** 수행 가능해야 한다(브로커 잔고+슬롯 상태만 필요, 전체 universe fetch 불필요). 구체 주기는 설계 tuning. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 슬롯=자산 1:1 영구 모델 + 활성 개수 제어(M, FCFS+무치환) 정책 정의 - - 슬롯 lifecycle 의 universe 동기화(생성/soft-retire/tombstone) 설계 - - 기존 풀 모델(일일 preallocation/idle 배정/closed 재할당) 폐기·재해석 + 일일행→영구행 cutover 범위 산정 - - holding=코어 로컬·수량=hub 정본+미러 분리(FR-3/FR-4) - - baseline 문서 재설계 요구 - - 토대 가드: 수량 가변 전제 유지(고정 가정 금지) + "활성=진입/종료=전량 청산" 상태 경계 고정(후속 사이징 얹힘 대비) -- Out of Scope - - **포지션 사이징(활성 슬롯 보유 수량 변경)·선언형 target intent(issue #1) — 분리(OQ-4), 별도 후속 Discovery.** 본 주기는 HOLDING→HOLDING 수량 변경·부분 매도를 구현하지 않는다. - - 중장기 전용 운영(issue #20)의 전체 — 본 모델은 토대만 제공(다일 보유는 영구 슬롯으로 실현) - - 코어 holding↔broker/hub 비정상 불일치 해소 정책 — OQ-5(잔여 Open) - - 전략 모듈 내부 구현(외부 repo) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 검증·출시된 풀 모델(REL-001~004, 특히 방금 출시한 dcy-019/BAT-025)을 폐기/재정의 — 광범위 회귀·재작업·이행 위험. OQ-3(영구 슬롯) 확정으로 **일일 daily_slot 행 → 자산당 영구 1행 cutover + 현재 보유 포지션 carry-over** 가 본 주기 최대 작업으로 구체화됨. 일일 preallocation(dcy-014) 폐기, `daily_slot.trading_date` 스키마 변경 동반. - - 완화 방안: 영향 분석으로 폐기 범위를 명시하고, cutover/마이그레이션(일일행→영구행, 보유 carry-over, **기존 데이터 instance_key 태깅 + slot-scoped 테이블 instance 스코프화**, OQ-7) 절차와 회귀 테스트를 REQ/배치 단계에서 우선 설계. trading_date KST 계산(REQ-005)은 일일 회계 경계용으로 존속(부분 재해석). 단계적 전환(읽기 정합 → 모델 전환) 검토. -- R-2 (완화): universe 변동에 따른 슬롯 생성/소멸이 보유 포지션·진행 중 주문과 충돌(예: 보유 중 자산이 universe 에서 빠짐). - - 완화 방안: OQ-2 확정 — 보유 중/pending 슬롯은 **tombstone**(은퇴 금지, safety-sell 유지, 닫힌 뒤 은퇴), 비활성만 soft-retire(행 보존), degrade·대량소멸 시 보류(NFR-3). 잔여는 비정상 divergence(OQ-5)로 분리. -- R-3 (해소): "활성 M개" 정책의 선택/치환 규칙이 모호하면 비결정적·불공정 활성화. - - 완화 방안: OQ-1 을 FCFS+무치환(단일 writer 직렬화로 도착 순서 결정적, tie-break `updated_at`→`portfolio_id`)으로 확정 — 치환 분기 자체를 제거해 모호성 해소. 재현성은 테스트로 보장. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 제품 방향이 "알려진 universe 자산의 포지션 관리 + 동시 보유 수 제어"(자산 중심)이며, 익명 capacity 풀(기회주의적 자본 배분)이 아니다. — **CONFIRMED (2026-06-10, 사용자 확정)**. 모델 전환의 대전제 성립 → OQ-1~5 결정의 기반. -- A-2: portfolio-hub universe(`get_portfolios`, `managed_by_daemon_id` 필터)가 슬롯 집합의 source of truth 로 적절하다. -- A-3: 단일 writer·advisory lock 불변식은 새 모델에서도 유지 가능하다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 활성 후보가 "최대 M개"를 초과할 때 활성 자산 선택/치환 규칙은 무엇인가(우선순위·tie-break·기존 보유 우선 등). - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **FCFS(신호 도착 순) + 가득 차면 거부, 코어 자율 치환(eviction) 없음.** 활성 수 < M 이면 신호 온 자산 즉시 활성화, 활성 수 == M 이면 새 신호 거부(`capacity_full`) — 자리를 만들려고 기존 holding 을 코어가 임의 청산하지 않는다. 도착 순서는 단일 writer + advisory lock 직렬화(intent_log 시퀀스)로 이미 결정적이며, 동시 후보가 실제로 생기는 경로(재기동·정합)의 tie-break 은 alpha 가 아닌 내재적 결정 키(`updated_at` asc → `portfolio_id`)로 한다. - - 근거: (1) "코어는 집행, 전략은 alpha" 분리 보존 — 어떤 자산이 더 좋은지(살지/뺄지)는 전략 책임이며, 코어가 score 로 후보를 줄 세우면 alpha 판단을 흡수한다(dcy-019 A-3 계승). (2) 치환=자율 청산이라 사용자 채택 "코어 자율 청산 안 함" 원칙(issue #15)과 충돌 — 회전이 필요하면 전략이 명시적 sell→buy 로 수행(감사 로그에 의도 잔존). (3) 결정성(NFR-1/R-3)이 직렬화로 공짜로 따라온다. - - 트레이드오프 & 연동: FCFS 는 확신 낮은 이른 신호가 슬롯을 점유해 더 좋은 후속 신호를 막을 수 있다. "M개 capacity 를 최고 기회에 배분"이 필요하면 그것은 **전략이 'M개 목표 포트폴리오'를 선언**하고 코어가 reconcile 하는 모델(OQ-4 / issue #1)로 처리한다 — 그 경우 선택 규칙은 전략 선언으로 이동하고 OQ-1 은 "target 이 M 초과면 선언 검증 단계에서 거부"가 된다. 본 결정은 OQ-4 분리(토대만) 전제에서의 단순 규칙이며, 선언형 target 채택 시 자연 확장된다. - - 반영: FR-2, R-3, §3 기대 상태, §10 DECIDE. -- OQ-2: universe 변동 시 슬롯 lifecycle — 신규 자산 슬롯 생성 시점, 제거 자산(특히 보유 중) 슬롯의 소멸/청산/보류 규칙, 스냅샷 refresh 와의 결합 방식. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정 (영구 슬롯 OQ-3·holding 로컬 FR-3 전제): - - **생성**: universe 신규 자산 → 영구 슬롯 즉시 생성(비활성). `get_portfolios` reconcile 경로에서 수행, `UNIQUE(portfolio_id)` 멱등(있으면 no-op). fetch 성공 시에만. - - **제거 — 비활성**(holding 아님 + pending 주문 없음): **soft-retire**(행 보존, `retired_at`/`universe_detached` 마킹). **hard delete 금지** — `slot_transition_log` 이력 보존 + 같은 자산 재추가 시 동일 슬롯 재활성화(영구 portfolio_id 키). - - **제거 — 보유 중/ pending 진행 중**: **은퇴 금지, tombstone**. 슬롯 유지 + safety-sell/watchdog 계속 동작(FR-3), universe-detached 로 마킹해 **신규 매수 후보 제외**하되 기존 포지션은 보호·매도 가능. 슬롯이 닫힌(매도 체결) **후 은퇴**. 코어 자동 청산 안 함(issue #15). - - **cadence**: 기존 스냅샷 refresh(`run_once()` 의 `store.swap` 직후, 부팅 + 매일 07:30 KST)에 reconcile 을 얹는다 — 별도 빠른 루프 불필요(영구 슬롯이라 제거 지연이 tombstone 으로 안전). 즉시성 필요 시 운영자 on-demand reconcile(CLI) 허용. - - **degrade + 대량소멸 가드**(NFR-3): portfolio-hub 불가/degraded → 생성·은퇴 전부 보류, 마지막 집합 유지. fetch 성공이라도 universe 가 N→0/급격 대량 감소면 글리치 의심 → mass-retire 보류 + 경고(정상 단건 제거와 구분). - - **단일 writer**: 생성/은퇴 쓰기도 전이와 같은 advisory lock 경로(스냅샷 스레드가 repo 통해). - - **새 마커**: `retired_at`(또는 `universe_detached` boolean) — 포지션 lifecycle(status)과 **직교**(status enum 에 'retired' 넣지 않음). "보유 중 + universe-detached" 조합 표현용. - - **capacity 상호작용**: tombstone-holding 슬롯은 실포지션이므로 M 카운트 포함(닫힐 때까지), 단 재매수 불가. - - 반영: FR-1, NFR-3, §4, R-2, §10 DECIDE. OQ-5(비정상 divergence)와 경계 구분 — universe-detached holding 은 예상된 정상 케이스(tombstone), 코어-holding↔broker-0 같은 비정상은 OQ-5(error/operator). -- OQ-3: trading_date 의미 재정의 — 자산 슬롯은 영구적인데 현재는 일일 리셋. 일/다일 경계, 일일 회계와의 관계(issue #20 직결). - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **영구 슬롯.** 슬롯 키 = `portfolio_id`(날짜 없음; 다중 인스턴스 공유 DB 라 실제 유일성 제약은 OQ-7 에서 `UNIQUE(instance_key, portfolio_id)` 복합키로 정련), 슬롯 행은 날 경계로 리셋되지 않고 자산이 universe 에 있는 동안 영속(holding 이 날 경계를 그대로 넘어감 — issue #20). **trading_date 는 슬롯의 정체성에서 제거되고, 이벤트/원장/정책의 시간 속성으로 강등**된다: 일일 활동·회계는 슬롯이 아니라 `slot_transition_log`(ts)·ledger·일일 집계를 날짜로 필터해서 본다. 슬롯 행의 기존 `trading_date NOT NULL` 컬럼은 드롭하거나 의미 변경(`opened_at`/`last_activity_date` 등)한다(스키마 변경). 일일 preallocation(dcy-014 `preallocate_daily_slots`/00:00 트리거)은 **폐기**하고 슬롯 생성/소멸을 universe reconcile(OQ-2)로 대체한다 — 단 trading_date 의 KST 계산 자체(dcy-014/REQ-005)는 일일 회계 경계용으로 **일부 존속(재해석)**, 완전 폐기 아님. - - 근거: 슬롯=자산 포지션은 본질적으로 다일 보유이므로 일일 리셋과 충돌. 영구 슬롯은 풀 모델 부산물(일일 preallocation·`no_idle_slot`·closed 재할당 dcy-019·00:00 경계 미생성 issue #16)을 개념적으로 소멸시키고, lifecycle 을 날짜 축에서 떼어 universe 멤버십에만 반응하게 해 OQ-2 와 FR-3(holding 로컬 영속)에 정합한다. - - 반영: FR-1, §3 현재/기대 상태, §4, §영향 범위, R-1, §10 DECIDE. dcy-014 폐기/재해석 범위 명시(FR-5). -- OQ-4: 포지션 사이징(issue #1, 활성 슬롯 수량 변경)을 본 주기에 포함할지, 토대만 깔고 별도 주기로 분리할지. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **분리 (Out of Scope). 본 주기는 토대만.** 포지션 사이징(활성 슬롯 보유 수량 변경)과 선언형 target intent(issue #1)는 **별도 후속 Discovery** 로 둔다. - - 근거: dcy-020 은 이미 substrate 재설계 + 일일행→영구행 cutover(R-1 High) + 스키마 변경 + dcy-014/019 폐기로 거대하다. 신규 거래 능력(수량 변경)을 얹으면 모델 마이그레이션과 뒤엉켜 회귀 분리·cutover 검증이 불가능해진다. 게다가 우리가 정한 토대(영구 자산 슬롯·holding 로컬·수량 hub 정본+미러·청산 수량 브로커 조회, FR-3/FR-4)가 사이징을 깔끔히 받쳐주므로 미뤄도 손해 없이 후속이 더 쉬워진다. 선언형 target 은 intent 계약 변경(자체 OQ 묶음: 델타 계산·부분체결·오버슈트·rebalancing·M 캡 상호작용)이라 독립 주기가 맞다. issue #20(다일 보유)의 핵심 가치는 영구 슬롯(OQ-3)으로 이미 실현. - - **토대 가드(이번 주기 필수, 재작업 방지)**: (1) 수량 고정 가정을 모델/스키마/제약에 박지 않는다(수량=hub 정본+미러라 자연히 가변). (2) 상태 의미 경계 고정 — "활성"=포지션 진입(크기는 기존처럼 allocated_capital/qty 로 1회 결정)·종료 시 전량 청산. **HOLDING→HOLDING 수량 변경·부분 매도는 본 주기에서 구현하지 않는다.** - - 반영: §범위 경계 In/Out Scope, §4 상태머신 항목, FR-4, §10 DECIDE. -- OQ-5: 코어 로컬 holding(FR-3) 과 portfolio-hub `asset_quantity`/브로커 잔고가 **실제로 어긋날 때** 해소 정책 — 누가 이기고 무슨 행동을 하는가(예: 코어는 holding 인데 브로커 0, 또는 그 반대). 수량 정본 분리(FR-3/FR-4)에서 파생된 잔여 결정. - - 상태: Answered (2026-06-10, 사용자 결정) - - 권위 모델: ① 즉시 안전 행동 구동 = **코어 로컬 holding**(FR-3, offline); ② 포지션 존재·수량의 reconcile **ground truth = 브로커 잔고**(`get_balance().hldg_qty`, 실계좌); ③ hub `asset_quantity` = 회계(코어 ledger write 의 downstream, 지연 정상 → 대부분 warning, 코어 상태 오류 아님). - - 케이스별 해소: - - **A. 코어 holding / 브로커 0**(유령 포지션): 조용한 자동 flat 금지 → 슬롯 **`error`(REQ-004 reconcile_mismatch) + WARN + 운영자 resolve**. error 인 동안 safety-sell 대상 제외(유령 청산 시도 중단). - - **B. 코어 flat / 브로커 보유**(관리 안 되는 고아 포지션, **최위험**): 큰 **WARN/alert + quarantine/error 격리 + 운영자 결정**(관리 편입 vs 청산 vs 무시). **자동 청산 금지(issue #15)**, **임계값 조작 자동 편입 금지**(cost basis·stop/take 부재 — 편입해도 시간기반 forced/market-close·kill-switch 만 가능). - - **C. 둘 다 보유, 수량만 불일치**(holding boolean 일치): 수량은 코어 정본 아님(FR-4) → **브로커 기준 미러 갱신**, hub 지속 불일치는 warning(REQ-031). **상태 변경 없음.** - - **D. 코어+브로커 일치, hub 만 다름**(hub 회계 지연): 기존 ledger outbox write-back/재시도(dcy-015/018) + warning. **코어 상태 문제 아님.** - - 원칙: (1) **조용한 자동 교정 금지·표면화** — 자동은 비파괴(미러 갱신/warning/error·quarantine 격리)만, 실주문 발생·취소(청산 등)는 **운영자 게이트**(issue #15 자율청산 금지 일관). (2) **감지 = `reconcile_positions`(REQ-031) 확장** — 브로커 잔고 + hub 수량 + **코어 슬롯 holding** 3자 비교. cadence 는 Case B 안전 민감성 때문에 일일 universe refresh 보다 자주 가능(브로커 잔고+슬롯 상태만 필요, 전체 universe fetch 불필요) — 빈도는 tuning. - - 경계: **universe-detached 인데 holding = 정상(tombstone, OQ-2)** → OQ-5 아님. OQ-5 는 세 출처의 **비정상 충돌**(A/B/C/D)만. - - 기존 자산 재사용(green-field 아님): 감지=REQ-031, 격리/해소=`error`+운영자 resolve(REQ-004), 가시화=warning 오버레이 + safety-sell 카운터/구조화 로그(dcy-017). - - 반영: OQ-5 본문(상기), FR-4(브로커 reconcile ground truth), NFR-3, §10 DECIDE. -- OQ-6: 동시 활성 최대치 M 의 값·설정 출처는 무엇인가(env / per-instance TOML / 기본값). 기존 `TREX_TOTAL_SLOTS`(풀 크기)는 영구 슬롯 전환으로 폐기되므로 신규 설정 노브가 필요. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **per-instance TOML 설정(`config/*.toml`)에 둔다** — 예: `max_active_slots`(`DaemonInstanceConfig`, REQ-048 per-instance TOML 계열). 인스턴스별 독립 운영(다중 인스턴스 토폴로지 dcy-009 와 정합), gitignore 되는 인스턴스 파일로 운영자가 인스턴스별 동시 보유 한도를 제어. env(`TREX_*`)·하드코딩 기본값은 비채택(per-instance 일관성·기존 toml 로더 재사용). - - 반영: FR-2, §4 capacity 항목, §영향 범위(config), §10 DECIDE. -- OQ-7: 다중 인스턴스의 슬롯 영속 스코프 — 인스턴스마다 별도 DB 인가, 공유 DB + 인스턴스 스코프 컬럼인가. 영구 슬롯(FR-1)의 UNIQUE 형태·advisory lock(NFR-1)을 좌우. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **공유 DB + `instance_key` 스코프.** (1) `daily_slot` 유일성 = **`UNIQUE(instance_key, portfolio_id)`** (+ `instance_key` 컬럼), slot-scoped 테이블 전반(slot_transition_log/intent_log/ledger_outbox/snapshot_pause/killswitch_exclusion 등) instance 스코프화 + 쿼리 필터. (2) advisory lock 을 **인스턴스 스코프화**(`(SLOT_LOCK_ID, hash(instance_key))`)해 인스턴스 간 교차 경합 제거(NFR-1 재정의). (3) `daemon_id` 는 **universe 파티션(get_portfolios) + 감사용**으로만(선택 병행 저장), **스코프 키 아님**; reconcile 시 instance_key↔daemon_id **1:1 정합 가드**(불일치 표면화, 쓰기는 daemon_id 비의존). (4) cutover: 기존 단일 인스턴스 데이터에 instance_key 태깅(R-1). - - 근거: 인스턴스별 DB 는 마이그레이션/백업/관측 N배로 비합리적. 메시징이 이미 "브로커 공유 + instance_key 큐 격리"(dcy-009)라 DB 도 동일 패턴이 대칭적. **핵심: 스코프 키를 `instance_key` 로 두어 영속 스토어·락이 portfolio-hub 가용성(whoami→daemon_id)에 의존하지 않음** → 자율 안전 매도 offline 보장(REQ-024)·부팅 즉시 가용·토큰 회전에 안정. daemon_id 의 권위적 소유 이점은 1:1 가드로 흡수. - - 반영: FR-1, NFR-1, §영향 범위, R-1, §10 DECIDE. (OQ-3 의 "키=portfolio_id 단독"은 본 OQ-7 로 복합키 `(instance_key, portfolio_id)`로 정련.) -- OQ-8: 운영 중 M(`max_active_slots`)을 현재 보유 수보다 작게 변경하면(over-cap) 어떻게 처리하는가. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **초과분은 신규 활성화만 차단, 기존 보유는 자연 종료까지 유지.** M 을 낮춰 현재 holding 수 > M 이 되어도 코어는 기존 보유를 강제 청산하지 않고(자율청산 금지, issue #15), holding 수가 M 이하로 내려갈 때까지 **새 매수 활성화만 거부**(`capacity_full`)한다. 전략의 명시적 매도로 자연 수렴. - - 근거: 강제 청산은 자율청산 원칙·single-writer 안정성과 충돌. FCFS+무치환(OQ-1)과 일관 — capacity 는 "신규 진입 게이트"이지 "보유 강제 축소 트리거"가 아니다. - - 반영: FR-2(또는 인접), §10 DECIDE. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 슬롯 집합이 portfolio-hub universe 자산과 1:1 로 일치한다(universe N개 → 자산 슬롯 N개, portfolio_id 키 일치). -- S-2: 동시 활성(holding) 슬롯 수가 항상 ≤ M 이며, 후보 초과 시 OQ-1 규칙대로 결정적으로 활성 자산이 선택된다. -- S-3: 매수/매도 신호가 익명 배정 없이 해당 자산 슬롯의 활성화/비활성화로 처리되고, 단일 writer·불변식 회귀가 없다. universe↔slots 표면이 합쳐져 운영 관측이 단일화된다. -- S-4: 재정의 상태머신(FR-6: inactive↔pending_buy↔holding↔pending_sell, idle/closed 합침)·intent 자산 키 통일(FR-7: portfolio_id, 중복=`already_active`/무효=`not_holding` 거부)·`trex-core slots` 단일 뷰(FR-8)가 동작하고, over-cap 시 신규만 거부·기존 보유 유지(OQ-8)된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: universe fetch 결과 vs 슬롯 집합 대조(통합 테스트) / 데이터 출처: portfolio-hub `get_portfolios`, daily_slot -- S-2 측정 방식: 후보>M 시나리오에서 활성 수·선택 결과 단언 / 데이터 출처: 슬롯 활성 상태 + 선택 규칙 -- S-3 측정 방식: 신호→활성화/비활성화 경로 단위·통합 + 동시성 경합 테스트 / 데이터 출처: 슬롯 상태 전이, 전이 로그 -- S-4 측정 방식: 상태 전이 단위테스트(허용/거부 집합) + intent 멱등/거부(`already_active`/`not_holding`) 단위 + `slots` 단일 뷰 출력 단언 + over-cap(M 축소) 시나리오 / 데이터 출처: 상태머신 전이표, intent_log, slots RPC 응답 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 포지션 사이징(OQ-4 분리) + 풀 모델 폐기 범위(dcy-001/002/014/019 산출물 명시) 합의 완료(§5 범위 경계). -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~4 측정 시나리오·데이터 출처가 테스트 가능한 형태로 합의(§7). -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 (비차단으로 이관) - - 기준: R-1(풀 모델 폐기·cutover)·R-2(완화됨) 해소 방향은 확정. **해소 책임자(owner) 지정은 사람 책임 배정이라 REQ/batch planning 단계 담당자 할당으로 이관** — Discovery confirm 비차단(confirm-discovery rule 3-4). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~8 전부 Answered, A-1 Confirmed. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 승인자 Justin Ko, confirm-discovery 수행(2026-06-10). - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-020_20260610_asset-centric-slot-model.md -- 참조 문서: - - docs/discovery/dcy-001_20260603_slot-pure-domain-migration.md (슬롯 도메인·상태머신 — 재해석 대상) - - docs/discovery/dcy-014_20260608_trading-date-configurable-timezone-kst.md (일일 preallocation — 폐기/재설계 대상) - - docs/discovery/dcy-019_20260609_slot-entry-count-and-closed-reallocation.md (풀 모델 슬롯 재사용 — 폐기 대상) - - docs/data-model.md, docs/runtime-flows.md, docs/project-structure.md (baseline — 전면 갱신 대상) - - GitHub issue #1(포지션 사이징), #20(중장기 자산 중심) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- [Decided 2026-06-10] 활성 후보 > M 시 규칙 (OQ-1): **FCFS + 가득 차면 거부, 코어 자율 치환 없음** (tie-break `updated_at`→`portfolio_id`). 능동 배분은 OQ-4 선언형 target 으로 분리. -- [Decided 2026-06-10] holding/수량 정본 분리 (FR-3/FR-4): **holding(포지션 열림)=코어 슬롯 로컬 정본(동기 set/clear), 수량(asset_quantity)=portfolio-hub 정본 + 로컬 best-effort 미러, 청산 수량=브로커 잔고 조회.** holding 을 hub `asset_quantity` 로 판단하지 않음(offline 안전·universe 누락 생존·hub 지연·capacity 원자성). 슬롯의 `qty`/`avg_price` 정본 소유(드리프트 원인) 정리. -- [Decided 2026-06-10] universe 변동 시 슬롯 lifecycle (OQ-2): **생성=refresh 때 즉시·멱등 / 제거=비활성 soft-retire(행 보존)·보유·pending 은 tombstone 후 닫힐 때 은퇴 / cadence=부팅+일1회(+운영자 on-demand) / degrade·대량소멸 보류 / 단일 writer / 직교 `retired_at` 마커.** -- [Decided 2026-06-10] trading_date 시맨틱 (OQ-3): **영구 슬롯(키=`portfolio_id`, `UNIQUE`, 날짜 없음)**, trading_date 는 이벤트/원장/정책의 시간 속성으로 강등, 슬롯 `trading_date` 컬럼 드롭/재의미, 일일 preallocation(dcy-014) 폐기·재해석(KST 회계 경계는 존속). cutover(일일행→영구행+보유 carry-over)는 R-1. -- [Decided 2026-06-10] 포지션 사이징 범위 (OQ-4): **분리(Out of Scope) — 본 주기 토대만.** 포지션 사이징·선언형 target intent(issue #1)는 별도 후속 Discovery. 토대 가드: 수량 가변 전제 유지 + "활성=진입/종료=전량 청산" 상태 경계 고정(HOLDING→HOLDING·부분 매도 미구현). -- [Decided 2026-06-10] holding↔hub/broker 불일치 해소 (OQ-5): **ground truth=브로커, 코어 holding=즉시 안전 구동, hub 차이=warning.** A(코어 holding/브로커 0)=error+운영자, B(코어 flat/브로커 보유, 최위험)=alert+격리+운영자(자동청산·자동편입 금지), C(수량만)=미러 갱신+warning, D(hub 지연)=기존 write-back. 감지=REQ-031 reconcile 확장, 자동은 비파괴만·실주문은 운영자 게이트. universe-detached holding(정상)=tombstone(OQ-2)과 구분. -- [Decided 2026-06-10] 동시 활성 최대치 M 설정 출처 (OQ-6): **per-instance TOML(`config/*.toml`, 예: `max_active_slots`, `DaemonInstanceConfig`/REQ-048 계열).** 인스턴스별 독립 운영, env·하드코딩 기본값 비채택. 기존 `TREX_TOTAL_SLOTS`(풀 크기)는 폐기. -- [Decided 2026-06-10] 슬롯 영속 인스턴스 스코프 (OQ-7): **공유 DB + `instance_key` 스코프.** `UNIQUE(instance_key, portfolio_id)` + slot-scoped 테이블 전반 instance 스코프화, advisory lock 인스턴스 스코프화(NFR-1 재정의), `daemon_id` 는 universe 파티션·감사용(스코프 키 아님)·instance_key↔daemon_id 1:1 가드. 인스턴스별 별도 DB 비채택. 핵심: 스코프 키를 instance_key 로 두어 영속·락이 portfolio-hub 가용성 비의존(offline 안전). -- [Decided 2026-06-10] over-cap(M 축소) 처리 (OQ-8): **초과분은 신규 활성화만 차단, 기존 보유는 자연 종료까지 유지** — 강제 청산 없음(issue #15), holding ≤ M 될 때까지 새 매수 `capacity_full` 거부. - -## CONFIRM -- [Confirmed 2026-06-10] 제품 방향이 "universe 자산 중심 포지션 관리 + 동시 보유 수 제어"(익명 capacity 풀 아님)임을 확정 (A-1) — 모델 전환의 대전제. 사용자 확정. - -## DATA -- 없음 (외부 데이터 의존 없음 — 내부 도메인/모델 결정) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~8 전부 Answered + 대전제 A-1 Confirmed + FR-1~8/NFR-1~5 확정 + holding/수량 정본 분리(FR-3/FR-4)·인스턴스 스코프(OQ-7)·over-cap(OQ-8) 결정. C-type placeholder 0개, Open OQ 0개, §10 DECIDE/CONFIRM 전부 해소. R-1(풀 모델 폐기·일일행→영구행 cutover)·R-2 해소 **책임자 지정은 REQ/batch planning 단계 담당자 할당으로 이관(비차단, rule 3-4)**. 승인자 Justin Ko 확정 → REQ Drafting 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-10 10:00 KST diff --git a/docs/discovery/dcy-021_20260610_position-sizing-target-intent.md b/docs/discovery/dcy-021_20260610_position-sizing-target-intent.md deleted file mode 100644 index 6f50a9a..0000000 --- a/docs/discovery/dcy-021_20260610_position-sizing-target-intent.md +++ /dev/null @@ -1,221 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-021_20260610_position-sizing-target-intent -- 이슈명: 포지션 사이징 — 활성 자산 슬롯 보유 수량 변경(HOLDING→HOLDING)·부분 매도·선언형 target intent (issue #1, dcy-020 OQ-4 후속) -- 작성 시각(KST): 2026-06-10 17:06 KST -- 마지막 갱신 시각(KST): 2026-07-29 19:43:47 KST (후속 자본배분 정책 Discovery 참조 연결) -- 대체됨: 없음 -- 후속 Discovery 참조: `docs/discovery/dcy-042_20260729_shared-account-capital-allocation-policy.md` -- 생성된 REQ 참조: REQ-066, REQ-067 (+ change-req REQ-057/061/062) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #1(포지션 사이징/선언형 target intent). dcy-020(자산 중심 영구 슬롯 — 본 주기의 직접 토대, OQ-4 에서 분리), dcy-013(부분 체결 후 외부 취소 — 부분 수량/잔량 처리 선례), REQ-062(청산 수량 브로커 정본·수량 hub 정본+미러), REQ-057(상태머신). -- 해석: - - dcy-020 이 자산 중심 영구 슬롯 substrate 를 완성하면서 "수량 가변 전제는 모델에 남김 / 활성=진입·종료=전량 청산 경계는 고정"으로 OQ-4(포지션 사이징)를 명시 분리했다. 본 Discovery 는 그 토대 위에 **보유 중 수량 조정(증액·부분 감액)과 선언형 target intent** 를 얹는 후속 주기다. - - 핵심 판단: HOLDING 상태에서 같은 자산 슬롯의 수량을 바꾸는 상태머신·집행·intent 계약을 정의한다. **계약 형태는 명령형으로 확정**(OQ-1, 2026-06-10): 전략이 스냅샷의 cash+quantity 로 사이징을 계산해 qty+price 를 실은 매수(증액)/부분 매도 intent 를 보내고, core 는 실행 substrate(단일 writer·멱등·브로커 세션·슬롯 상태)에 집중한다. core 는 "의도 수량"을 저장/추적하지 않으며, 드리프트 재수렴은 전략이 스냅샷으로 수행한다. 남은 판단은 단위(OQ-2)·avg_price 가중평균 세부(OQ-3)다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-020 은 OQ-4/issue #1 을 "별도 후속 Discovery"로 명시 분리했고 본 주기와 문제 정의(보유 수량 가변)·성공 기준이 다르다 — dcy-020 갱신이 아니라 신규 주기다. - - 본 변경은 dcy-020 이 고정한 상태머신 경계("종료=전량 청산", HOLDING→HOLDING 미포함)를 해제하므로 dcy-020 의 설계 전제를 확장하는 독립 주기로 추적해야 책임 경계가 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/domain/slot/state.py` 의 상태머신은 이산적이다: `inactive → pending_buy → holding → pending_sell → inactive`(+error). **HOLDING→HOLDING(수량 변경) 전이가 없고**, 매도는 전량 청산(pending_sell→inactive)만 의도적으로 표현된다. 부분 수량 잔량 처리는 `execution.py` 의 외부 취소 경로(dcy-013, `_on_partial_cancel`: pending_sell→holding + qty 차감)에만 존재하며 이는 반응적 처리이지 의도적 부분 매도 intent 가 아니다. - - `application/intents.py` 의 `BuyIntent` 는 inactive 슬롯을 활성화(qty 지정)할 뿐, 이미 holding 인 슬롯의 수량을 늘리는(증액) 경로가 없다. `SellIntent` 는 전량 청산만 한다(부분 수량 인자 없음). - - 수량 정본은 브로커 잔고(REQ-062, `BrokerPort.get_balance().hldg_qty`)이고 슬롯 `qty` 는 미러다. avg_price 는 매수 체결 시 1회 설정되고 증액 시 가중평균 재계산 경로가 없다. - - dcy-020 가 "수량 가변 전제"를 모델에 남겨 둔 토대(영구 슬롯·자산 1:1)는 이미 존재한다 — substrate 재작성은 불필요하다. -- 기대 상태 - - 보유 중인 자산 슬롯이 `holding` 을 유지한 채 목표 수량으로 **증액(추가 매수)·부분 감액(부분 매도)** 될 수 있다. 전량 청산만 inactive 로 종료한다. - - 전략 모듈이 자산의 **목표 포지션을 선언(target)** 하거나 증감을 지시하면, core 가 현재 보유(브로커 정본)와 비교해 필요한 매수/매도 수량을 도출·집행한다. avg_price 는 증액 시 가중평균으로 재계산되고 부분 매도 시 cost basis 가 유지된다. -- 이해관계자 - - 전략 모듈(trex-orb) 개발자 — target/사이징 intent 를 발행하는 주체 - - trex-core 유지보수자 — 상태머신·집행·intent 계약 확장 구현 - - 운영자 — 슬롯의 수량 변화·부분 매도를 `trex-core slots`/이벤트로 관측 -- 제약사항 - - 집행 매도 수량은 브로커 잔고 정본(REQ-062)을 유지해야 한다 — 부분 매도도 과매도/이중 주문이 없어야 한다. - - dcy-020 의 자산 1:1 영구 슬롯·M(동시 활성 자산 수) 캡·instance 스코프 불변식을 깨지 않는 additive 확장이어야 한다. - - 단일 writer(SlotService advisory lock)·intent 멱등(intent_log) 불변식 유지. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략이 자산 A 를 100주 보유 중인데 신호 강화로 목표를 150주로 올린다 → 같은 슬롯이 holding 을 유지하며 50주 추가 매수, avg_price 가중평균 재계산. -- 전략이 자산 A 목표를 100→40주로 낮춘다 → 같은 슬롯이 60주 부분 매도 후 holding(잔량 40) 유지, cost basis 유지. 목표 0 이면 전량 청산 → inactive. -- 운영자가 `trex-core slots --symbol …` 로 부분 매도 진행/잔량을 관측하고, 부분 체결 이벤트를 전략이 수신해 목표 대비 현재를 재조정. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 보유 자산 슬롯의 **수량 조정 상태 전이** 도입: 증액(HOLDING→PENDING_BUY→HOLDING, qty 누적·avg_price 가중평균)과 부분 감액(HOLDING→PENDING_SELL→HOLDING 잔량>0 / →INACTIVE 전량)을 의도적 intent 경로로 표현한다. -- **포지션 사이징 intent 계약**(명령형, OQ-1 확정): 전략이 cash+quantity(스냅샷)로 계산한 qty+price 를 실은 매수(증액)/부분 매도 intent 를 보내면 core 가 단일 writer·멱등(intent_log) 하에 실행한다. core 는 target/의도 수량을 저장·추적하지 않는다(미체결 잔량 무추적). 집행 매도 수량은 `min(intent qty, 브로커 잔고)`(과매도 방지), intent 가 잔고 이상이면 전량 매도. -- baseline 문서 갱신: `docs/data-model.md`(슬롯 수량 가변·avg_price 재계산 규칙), `docs/runtime-flows.md`(사이징 intent→reconcile→집행 흐름, 상태머신 전이 확장), `docs/interface-contract.md`(사이징 intent payload·이벤트), `docs/project-structure.md`(영향 모듈) 를 실제 구현과 일치시킨다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/domain/slot/state.py`: HOLDING→PENDING_BUY(증액) 전이 추가, 부분 매도 후 HOLDING 복귀(기존 pending_sell→holding 재사용) 의미 정립. - - `trex_core/domain/slot/service.py`: 증액 시 qty 누적 + avg_price 가중평균 재계산, 부분 매도 시 잔량 계산·cost basis 유지 필드 규칙. - - `trex_core/application/intents.py`: 사이징/target intent(증액·부분 감액) admission 경로 + 멱등. - - `trex_core/application/execution.py`: 증액 매수 dispatch, 부분 매도 수량(브로커 정본 기반) dispatch — dcy-013 `_on_partial_cancel`/REQ-062 와 정합. - - `trex_core/application/messaging_handlers.py`: 사이징 intent payload 파싱 + 이벤트. -- 문서 영향: - - `docs/data-model.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/project-structure.md` 갱신. -- 운영 영향: - - 전략 모듈(trex-orb)이 새 사이징 intent 를 발행하도록 계약 전환 필요(점진/하위호환 검토). - - 부분 매도·증액 주문이 늘어 KIS 주문 빈도·체결 모니터링 영향. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 보유(holding) 자산 슬롯의 **수량 증액**을 지원한다. 전략이 qty+price 를 실은 매수 intent 를 보내면 같은 슬롯이 HOLDING→PENDING_BUY→HOLDING 로 순환하며 **실제 체결분만큼** 보유 수량이 누적되고, avg_price 는 (기존수량×기존평단 + 체결수량×체결가)/총수량 가중평균으로 재계산된다. 부분 체결 시 실제 체결분만 반영·ledger 기록하고 미체결 잔량은 추적하지 않는다(재수렴은 전략). 활성 자산 수(M 캡 대상)는 증가하지 않는다. -- FR-2: 보유 자산 슬롯의 **부분 감액(부분 매도)**을 지원한다. 전략이 qty+price 를 실은 (부분)매도 intent 를 보내면 같은 슬롯이 HOLDING→PENDING_SELL 로 전이한다. 집행 매도 수량 = **`min(intent qty, 브로커 hldg_qty)`**(REQ-062 과매도 방지); intent 가 잔고 **이상**이면 전량 매도. 체결 후 슬롯은 브로커 정본 수량을 반영해 잔량>0 이면 HOLDING(cost basis 유지), 0이면 INACTIVE 로 종료한다. **부분 체결 시 실제 체결분만 ledger 기록(REQ-018/019)**, 미체결 잔량은 core 가 추적·재시도하지 않고, 더 줄이려면 전략이 다음 스냅샷 기준으로 재발행한다(dcy-013 부분 체결 처리 재사용). -- FR-3: **포지션 사이징 intent 계약 = 명령형**(OQ-1 확정). 전략이 스냅샷의 cash·asset_quantity(REQ-028/029, option D)로 사이징을 계산해 **qty+price 를 실은** 매수(증액)/부분 매도 intent 를 발행하고, core 는 단일 writer·멱등(intent_log) 하에 실행만 한다. **wire 계약 단위 = 정수 주식 수(qty) + 가격(price)**(OQ-2 확정); 비중(weight)/금액(capital) 사이징의 주식 수 환산과 반올림·lot 처리는 **전략 책임**(core 는 정수 qty 를 그대로 집행, 현재가·cash 환산 로직 없음). core 는 target/의도 수량을 **저장·diff·추적하지 않으며**(reconcile-on-intent 없음), 실현손익은 ledger/portfolio-hub 가 소유한다(core 는 실제 체결만 원장 기록). 드리프트·유실 복구는 전략이 스냅샷(현재 수량 = 브로커 정본 표면화)으로 재수렴한다. 기존 buy/sell intent 와 병존하는 additive 확장이다. -- FR-4: baseline 문서(`docs/data-model.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/project-structure.md`)를 수량 가변 슬롯·사이징 흐름·상태머신 전이 확장·intent/이벤트 계약에 맞게 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 부분 매도·증액은 dcy-020/REQ-062 의 "수량=브로커 정본, 슬롯=미러" 불변식과 단일 writer·집행 멱등을 유지한다 — 과매도·이중 주문·avg_price drift 가 없어야 한다. -- NFR-2: 상태머신·intent·집행 변경은 dcy-020 자산 중심 모델에 대한 **additive 확장**이어야 한다(기존 활성/청산/admission 경로 무회귀). 기존 전 단위 회귀 green 유지. -- NFR-3: 사이징 intent 는 멱등(intent_id) + 이벤트로 부분 체결/증액 결과를 전략에 last-wins 로 전달해, 전략이 목표 대비 현재를 재수렴할 수 있어야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 보유 자산 슬롯의 증액·부분 감액 상태 전이·집행·avg_price 재계산 - - 포지션 사이징/target intent 계약(형태는 OQ-1 확정) + 멱등 + 이벤트 - - 관련 baseline 4문서 갱신 -- Out of Scope - - 능동 capacity 배분(M 슬롯을 어떤 자산에 줄지 core 가 최적 선택) — dcy-020 FR-2 에서 분리된 별개 주제, 본 주기는 "이미 보유한 자산의 수량 조정"에 한정(2026-06-10 확정) - - 자동 리밸런싱 스케줄러(주기적 목표 재산정) — 전략 모듈 책임 - - 비중(weight)/금액(capital)→주식 수 환산·반올림·lot 처리 (OQ-2 확정: **전략 책임**, core 범위 밖 — wire 는 정수 주식 수+가격) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 집행 핫패스 변경(부분 매도·증액 주문)으로 과매도·이중 주문·avg_price 오류 위험. 라이브 주문 동작. - - 완화 방안: 매도 수량 브로커 정본(REQ-062) 재사용, 멱등(intent_log)+dispatch 가드, dcy-013 부분 체결 처리와 정합, 단위+paper 리허설+run-the-app 검증. -- R-2 (High): dcy-020 이 고정한 상태머신 경계("종료=전량 청산", HOLDING→HOLDING 미포함) 해제로 기존 불변식·테스트가 흔들릴 위험. - - 완화 방안: 전이는 additive 추가(기존 전이 보존), 5→확장 상태머신 회귀 가드, ALLOWED_TRANSITIONS 명세 테스트 갱신. -- R-3 (Medium): 전략 모듈(trex-orb) 동시 계약 변경 필요 — 미전환 전략과의 호환. - - 완화 방안: 기존 전량 매수/매도 intent 와 병존(additive), 사이징 intent 는 신규 경로로 도입해 점진 전환. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-020 자산 중심 영구 슬롯(자산 1:1·수량 가변 전제 유지)이 토대이므로 substrate 재작성 없이 additive 확장으로 충분하다. -- A-2: 보유 수량 정본은 브로커 잔고(REQ-062)이고 슬롯 `qty` 는 미러다 — 사이징도 이 정본/미러 분리를 그대로 따른다. -- A-3: M 캡은 동시 활성 자산 수 제어이므로, 이미 활성인 자산의 증액은 active 수를 늘리지 않아 M 캡과 무관하다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 사이징 intent 계약 형태 — 선언형 target vs 명령형 delta vs 혼합. - - 상태: **Answered (2026-06-10, 사용자 결정)** - - 결정: **명령형** — 전략이 스냅샷의 cash+quantity 로 사이징(qty+price)을 계산해 매수(증액)/부분 매도 intent 를 보내고, core 는 실행 substrate(target/의도 수량 미저장·미추적). 근거: ① 멱등성은 intent_log dedup 이 이미 수량 레벨로 보장(REQ-007/023), ② 주문에 가격(지정가)이 따라가야 함(dcy-012), ③ cash 정본은 portfolio-hub 라 사이징 계산은 전략 책임(관심사 분리), ④ 기존 명령형 buy/sell 계약의 최소 additive 확장. 부수 결정: 과매도→`min(intent,잔고)`·이상이면 전량(FR-2), 부분 체결→실제 체결만 ledger·미추적·전략 재수렴(FR-1/FR-2). 반영: FR-1/FR-2/FR-3, §4. - - 종료 조건: 충족(FR-3 계약 형태 확정). -- OQ-2: 사이징 단위 — 절대 수량 vs 비중 vs 금액 + 환산 책임. - - 상태: **Answered (2026-06-10, 사용자 결정)** - - 결정: **wire 단위 = 정수 주식 수(qty) + 가격(price)**. 비중/금액→주식 수 환산·반올림·lot 처리는 **전략 책임**(core 는 정수 qty 를 그대로 집행). 근거: ① OQ-1 명령형·기존 buy/sell·KIS 주문이 모두 주식 수+가격이라 새 단위 불필요(최소 additive), ② 환산에 필요한 cash(정본=portfolio-hub)·주문 가격을 전략이 이미 쥐고 있어 환산-주문 가격 일관, ③ 시장 미시구조(lot/호가) 규칙을 core 에서 배제. 주의: 스냅샷 cash 는 미정산 시 추정값(`cash_estimated`, dcy-018)일 수 있어 금액/비중 사이징 오차창은 전략이 감안. 반영: FR-3, §5 Out of Scope, §10 DATA. - - 종료 조건: 충족(정본 단위·환산 책임 확정). -- OQ-3: avg_price 재계산·cost basis 정책. - - 상태: **Answered (2026-06-10, 사용자 결정)** - - 결정: 증액 시 avg_price = **`(기존수량×기존평단 + 체결수량×체결가)/총수량`** 가중평균(FR-1). 부분 체결 시 **실제 체결분만** 누적(미체결 무시)이라 자연 정합. 부분 매도 후 잔량 avg_price 는 **변경 없음(cost basis 유지, dcy-013 일관)**. 집행 매도 수량 = `min(intent,잔고)`(과매도 방지), 실현손익은 **ledger/portfolio-hub 소유**(core 미계산, 실제 체결만 원장 기록). 반영: FR-1/FR-2/FR-3. - - 종료 조건: 충족(avg_price·cost basis·실현손익 규칙 확정). -- OQ-4: 사이징 reconcile 주기·트리거. - - 상태: **Answered (2026-06-10, 사용자 결정)** - - 결정: **intent 수신 시 1회 집행**. OQ-1 명령형 확정으로 core 는 target 을 저장하거나 주기적으로 reconcile 하지 않는다(reconcile-on-intent 없음). 드리프트·유실 재수렴은 전략이 스냅샷(현재 수량=브로커 정본)으로 수행하고, **자동 리밸런싱 스케줄러는 Out of Scope(전략 책임)**. 반영: FR-3, §5 Out of Scope. - - 종료 조건: 충족(트리거 모델 In/Out of Scope 확정). - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 보유 자산의 목표 상향 시 같은 슬롯이 holding 을 유지하며 추가 매수로 수량이 증가하고 avg_price 가 가중평균으로 재계산된다. -- S-2: 목표 하향 시 같은 슬롯이 부분 매도 후 holding(잔량>0, cost basis 유지) 또는 전량 시 inactive 로 종료되며, 집행 매도 수량이 브로커 잔고 정본과 일치한다(과매도 0). -- S-3: 사이징 변경 전·후 dcy-020 기존 경로(활성/청산/admission/M 캡/instance 스코프) 회귀가 없고, baseline 4문서가 구현과 일치한다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 단위 테스트(증액 전이·avg_price 가중평균) + run-the-app(증액 intent→holding qty 증가) / 데이터 출처: `tests/`, daily_slot, slot_transition_log -- S-2 측정 방식: 단위+PG 테스트(부분 매도 잔량/전량 분기) + paper 집행 매도 수량==브로커 잔고 검증 / 데이터 출처: `tests/`, BrokerPort.get_balance, ledger_outbox -- S-3 측정 방식: 전 단위 회귀 green + ruff + baseline 문서 diff 리뷰 / 데이터 출처: `pytest`, `docs/*.md` - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 — capacity 배분 Out of Scope·단위(OQ-2) 확정으로 In/Out 갈림 없음 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 — S-1~S-3 모두 기존 pytest·PG·run-the-app 경로로 측정 가능(§7) -- [x] High 영향 리스크 검증 방식 확정(R-1 집행=브로커 정본+멱등+리허설, R-2 상태머신=additive+회귀 가드, §6) — 해소 **책임자 지정은 REQ/batch planning 단계로 위임(비차단)** -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 — OQ-1~4 전부 Answered (2026-06-10) - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 -- [x] Freeze 확정 담당자(Confirmed By) 지정 — Justin Ko (§11) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-021_20260610_position-sizing-target-intent.md -- 참조 문서: - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (토대·OQ-4 분리 출처) - - docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md (부분 체결/잔량 처리 선례) - - stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md (baseline 갱신 논리 템플릿 참조) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (해소) 사이징 단위 = 정수 주식 수+가격, 환산은 전략 책임 — OQ-2 확정, FR-3/§5 반영. -- (해소) 능동 capacity 배분(M 슬롯 자산 선택)은 **Out of Scope** — dcy-020 FR-2 에서 분리된 별개 주제, 본 주기는 "이미 보유한 자산의 수량 조정"에 한정. §5 Out of Scope 반영. -- (해소) 사이징 reconcile 트리거 = intent 당 1회(core 스케줄 없음) — OQ-4 확정, FR-3/§5 반영. - -## CONFIRM -- (해소) avg_price 정책 = 증액 가중평균 + 부분 매도 cost basis 유지 + 실현손익 ledger/hub 소유 — OQ-3 확정, FR-1/FR-2/FR-3 반영. -- (해소) trex-orb 사이징 intent 전환 = 기존 buy/sell 와 **병존(additive·점진 전환)** — OQ-1 확정, FR-3/R-3 반영. - -## DATA -- (해소) 전략 신호 표현 방식(목표/증감/비중)은 OQ-1/2 확정으로 **core 범위 밖** — wire 는 항상 정수 주식 수+가격이라 core 는 전략 내부 단위를 알 필요 없음. trex-orb 측 환산 구현은 trex-orb Discovery 에서 다룸. - -> 참고: 미체결 pending 고착 처분·운영자 강제 취소(issue #21)는 본 주기와 다른 문제(PENDING 처분/안전 통제)로 **별도 Discovery 후보**다 — dcy-021 범위 밖. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1(명령형 계약)·OQ-2(정수 주식 수+가격, 환산 전략)·OQ-3(avg_price 가중평균·cost basis·실현손익 hub)·OQ-4(intent 당 1회, 스케줄 없음) 전부 Answered 로 핵심 결정 확정. capacity 배분 경계(Out of Scope)·단위·avg_price·트리거 전부 확정. confirm-discovery 승인 완료(Justin Ko). -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-10 17:39 KST diff --git a/docs/discovery/dcy-022_20260610_stuck-pending-order-recovery.md b/docs/discovery/dcy-022_20260610_stuck-pending-order-recovery.md deleted file mode 100644 index 937fdf2..0000000 --- a/docs/discovery/dcy-022_20260610_stuck-pending-order-recovery.md +++ /dev/null @@ -1,220 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-022_20260610_stuck-pending-order-recovery -- 이슈명: 전략 발신 intent 실행 정책 — 주문 timeout(TIF)/슬리피지 허용 + 미체결 고착 백스톱 (issue #21, dcy-020/021 모델 전제) -- 작성 시각(KST): 2026-06-10 17:43 KST -- 마지막 갱신 시각(KST): 2026-06-10 19:30 KST (confirm-discovery 승인 — OQ-1~4 Answered, 저장소 사실 재확인(cancel_order 미배선·payload 무 timeout/slippage), C형 0 → 상태 confirmed) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 없음 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #21(pending 고착). dcy-020(자산 중심 영구 슬롯·slot_id 정본·상태머신 — 토대, BAT-027~030 delivery), dcy-021(포지션 사이징·부분 매도 — BAT-031), dcy-013(stale 워치독 Alert-only + 외부취소 cncl_yn 롤백 선례). -- 해석: - - 본래 issue #21 은 "미체결 고착을 운영자/코어가 사후 정리"하는 반응적 문제였다. 그러나 더 나은 근원 해법은 **전략이 intent 제출 시 실행 정책(주문 timeout, 슬리피지 허용)을 함께 실어** core 가 이를 집행하는 것이다 — 미체결 주문은 전략이 지정한 timeout 에 자동 취소되어 애초에 고착되지 않는다. - - 따라서 본 Discovery 의 **주축**은 intent 실행 정책(timeout=TIF + slippage)이고, 운영자 강제 취소·코어 fallback 은 **백스톱**으로 둔다. 가격(`entry_price`)·청산시간(`forced_close_at`)·수량(`qty`)은 이미 계약에 있으므로 **신규는 timeout 과 slippage 두 가지**다. 브로커 `cancel_order` 는 어댑터에 이미 존재(미배선)라 timeout 집행 배선만 필요하다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-013(REQ-014)은 stale pending 을 **Alert-only**(상태 변경 없음)로 의도 고정했다. 본 주기는 전략이 명시한 실행 정책(timeout)을 core 가 집행하므로 "코어 임의 취소" 우려가 해소되며, 능동적 취소·실행 정책을 정의하는 신규 주기다. - - intent 에 실행 정책(timeout/slippage)을 추가하는 것은 매수/매도 계약(REQ-007/021/061)의 확장이며, 기존 dcy 어디에도 정의되지 않은 신규 능력이다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **주문에 실행 정책이 없다**: buy/sell intent payload(`interface-contract.md`)는 `entry_price`(지정가)·`qty`·`forced_close_at`(청산시간)·`stop_loss_price`/`take_profit_price` 는 있으나, **주문 유효시간(TIF/timeout)·슬리피지 허용**을 지정할 수단이 없다. 지정가 미체결 시 무기한 대기. - - **미체결 시 무기한 pending**: 집행 루프(`execution.py`)는 poll 결과가 "접수/부분(취소 아님)"이면 상태를 두고 재polling 한다 — 체결·외부취소·거부가 없으면 자산 슬롯이 pending_buy/pending_sell 에 영구 고착(issue #21). - - **stale 워치독은 Alert-only**: `_check_stale_pending`(dcy-013/REQ-014)은 임계 초과 시 WARN+카운터만 — 자동 조치 없음. 의도는 "코어가 전략 의도를 몰라 임의 취소 불가"였다. - - **브로커 취소 송신 미배선**: 어댑터에 `cancel_order(...)`가 이미 존재(`paper.py`/`kis.py`)하나 어떤 자동/운영자 흐름에도 호출되지 않는다. - - **운영자 처분 경로 부재**: `trex-core exit` 는 holding 대상이라 pending 에 적용 불가. - - **(dcy-021 후) 부분 매도 고착**: 포지션 사이징 부분 매도 주문도 pending_sell 로 나가 미체결 시 같은 고착 + 보유 슬롯 조정 잠금. - - baseline(`docs/interface-contract.md` payload, `docs/runtime-flows.md` 집행) 갱신 대상. -- 기대 상태 - - 전략이 intent 에 `timeout_sec`(예 60)·`slippage_pct`(예 0.1%)를 실으면, core 가 이를 집행한다 — timeout 초과 미체결 주문은 자동 취소·롤백(고착 예방), slippage 로 보호 지정가를 산출해 과도한 불리 체결을 막는다. - - timeout 이 없는(또는 timeout 취소 자체가 실패한) 잔여 고착은 운영자 강제 취소 + (선택) 코어 fallback 으로 처리한다(백스톱). - - 가격·청산시간·수량과 함께 실행 정책이 하나의 주문 명세로 통합되어, 전략이 "어떻게 체결할지"를 선언한다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 - - 리뷰어/승인자 — 실행 정책 계약·취소 집행·안전성 승인 - - 전략 모듈(orb-v1 등) — intent 에 timeout/slippage 발신(외부 repo 발신 측 변경) - - 운영 담당자 — 잔여 고착 백스톱 처분·관측 -- 제약사항 - - 취소·롤백은 단일 writer·멱등·불변식(advisory lock, instance 스코프 dcy-020)을 보존하고, 이미 체결된 부분 수량을 잃지 않아야 한다(dcy-013 부분 취소 회계 계승). - - 취소 요청 송신과 실제 취소 확인(cncl_yn/poll)을 분리 — 취소-체결 race 처리. - - 슬리피지·timeout 미지정 시 기존 동작(무기한 지정가/시장가)과 호환(하위호환·옵션 필드). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- orb-v1 이 매수 신호를 줄 때 `entry_price`·`qty`·`forced_close_at` 과 함께 `timeout_sec=60`·`slippage_pct=0.1` 을 실어 보낸다. 60초 내 미체결이면 core 가 주문을 취소하고 슬롯을 inactive 로 롤백한다(고착 없음). -- 시장가 매수에 `slippage_pct=0.1` 을 주면 core 가 현재가×(1+0.1%) 보호 지정가로 제출해 과도한 불리 체결을 막는다. 밴드 밖이면 미체결 → timeout 취소로 수렴. -- 부분 매도(dcy-021)에 timeout 을 주면 미체결 시 자동 취소되어 보유로 되돌아가고 부분 체결분은 보존된다. -- (백스톱) timeout 없는 옛 주문이 고착되면 운영자가 `trex-core cancel ` 로 취소·롤백한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- **intent 실행 정책 필드 추가(주축)**: buy/sell payload 에 optional `timeout_sec`(주문 유효시간, TIF)·`slippage_pct`(슬리피지 허용) 추가. 계약(REQ-007/021/061) 확장. 운영자 config 기본 timeout(per-instance TOML `default_order_timeout_sec`, opt-in) + per-intent override(OQ-4). deprecate 없음. -- **timeout 집행**: 집행 루프가 dispatched-pending 주문의 경과시간을 추적해 `timeout_sec` 초과 시 브로커 `cancel_order` 송신 → 취소 확인 후 롤백(pending_buy→inactive / pending_sell→holding, 부분 체결 보존). 기존 어댑터 `cancel_order` 배선. -- **슬리피지 집행(OQ-2/3)**: `slippage_pct` 는 `entry_price` 기준 밴드 — 실효 지정가 = 매수 `entry_price*(1+pct)`/매도 `entry_price*(1-pct)`(호가단위 반올림). `entry_price` 없으면 무시(순수 시장가). 즉 `entry_price`=기준/목표, `slippage_pct`=허용폭, 실효 limit=둘의 곱(`entry_price` 단독이면 하드 지정가=현행). -- **고착 백스톱**: timeout 미지정/취소 실패 잔여 고착에 운영자 강제 취소(`cancel`) + (선택) 코어 fallback. dcy-013 Alert-only 는 기본 유지. -- baseline 문서(`docs/interface-contract.md` payload, `docs/runtime-flows.md` 집행/취소 흐름) 반영. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/intents.py` / `messaging_handlers.py` — payload `timeout_sec`/`slippage_pct` 파싱·운반, 슬롯/주문에 전달 - - `trex_core/application/execution.py` — dispatched-pending 경과시간 추적 + timeout 취소 송신, slippage→limit_price 산출, 취소 확인 후 롤백 - - `trex_core/adapters/broker/*` — 기존 `cancel_order` 배선(신규 메서드 아님), 보호 지정가 주문 - - `trex_core/domain/slot/service.py` / `models.py` — (필요 시) 주문 정책/제출시각 보유 + 취소 롤백 전이(부분 보존) - - `trex_core/runtime/cli.py` / control RPC — 운영자 `cancel` 백스톱 - - `trex_core/runtime/config.py`(`DaemonInstanceConfig`) + `config/*.toml` — `default_order_timeout_sec` opt-in 기본값(REQ-048 per-instance TOML 계열, OQ-4) -- 문서 영향: - - `docs/interface-contract.md`(buy/sell payload 에 timeout/slippage), `docs/runtime-flows.md`(집행·timeout 취소·slippage·백스톱) -- 운영 영향: - - 미체결 고착 예방으로 활성 capacity 회복. 전략 모듈(외부) 발신 계약 변경 조율 필요. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: buy/sell intent payload 에 optional `timeout_sec`(주문 유효시간)·`slippage_pct`(슬리피지 허용)를 추가하고 core 가 파싱·운반한다(REQ-007/021/061 확장). core 는 수신 측 먼저 배포 가능(필드 누락 시 무시 — orb-v1 발신과 lockstep 불필요, 하위호환). `timeout_sec` 미지정 시 운영자 config 기본값(per-instance TOML `default_order_timeout_sec`, 미설정=OFF=무기한)을 적용하고 per-intent 값이 override 한다(OQ-4). `slippage_pct` 는 기본값 없음(미지정=보호밴드 없음). -- FR-2: 집행 루프는 dispatched-pending 주문이 `timeout_sec` 를 초과하면 브로커 `cancel_order` 를 송신하고, 취소 확인 후 슬롯을 롤백한다(pending_buy→inactive / pending_sell→holding). 이미 체결된 부분 수량은 보존한다. -- FR-3: `slippage_pct` 는 **기준가(`entry_price`)가 있을 때만** 적용한다(OQ-2) — 실효 지정가 = 매수 `entry_price*(1+pct)` / 매도 `entry_price*(1-pct)`(KRX 호가단위로 반올림: 체결 보장 측 — 매수 올림/매도 내림). `entry_price` 가 없으면(순수 시장가) `slippage_pct` 는 **무시**(무제한 시장가). 밴드 밖 미체결은 timeout 취소(FR-2)로 수렴한다. -- FR-4: 취소 요청 송신과 실제 취소 확인(cncl_yn/poll)을 분리한다 — 취소가 거부(이미 체결)되면 정상 체결 경로로 수렴하고 슬롯을 임의 변경하지 않는다(취소-체결 race). -- FR-5: (백스톱) 운영자가 자산 슬롯의 고착 pending 을 강제 취소·롤백할 수 있다(timeout 미지정/취소 실패 대비). dcy-013 Alert-only 기본 동작은 회귀 없이 유지한다. -- FR-6: `docs/interface-contract.md`·`docs/runtime-flows.md` 에 실행 정책·timeout 취소·slippage·백스톱을 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 안전·멱등 — 취소·롤백은 단일 writer·advisory lock(instance 스코프)·멱등을 보존하고, 중복 취소/경합에서 부분 체결분을 잃지 않는다. -- NFR-2: 비차단 — timeout 취소 송신 실패·지연이 집행 루프·다른 슬롯 처리를 막지 않는다(best-effort, 요청/확인 분리). -- NFR-3: 하위호환 — `timeout_sec`/`slippage_pct` 미지정 시 기존 매수/매도 경로와 byte 동등(회귀 없음). -- NFR-4: 관측성 — timeout 취소·slippage 적용·백스톱 처분이 로그+카운터로 가시화된다(stale 카운터 계승). - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - intent `timeout_sec`/`slippage_pct` 계약 + core 집행(timeout 취소·보호 지정가) - - 브로커 `cancel_order` 배선 + 취소-체결 race 처리(부분 보존) - - 운영자 강제 취소 백스톱 + runtime-flows/interface-contract 정합 -- Out of Scope - - 브로커 `cancel_order` 신규 구현(이미 존재 — 배선만) - - 전략 모듈(orb-v1) 내부 신호/사이징 로직(외부 repo, 발신 계약만 합의) - - IOC/FOK 등 네이티브 TIF 분류 확장 — **후속 주기**(본 주기는 코어-타이밍 `timeout_sec` 만, OQ-1 Answered) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): 취소 요청과 실제 체결의 경쟁(race) — timeout 취소 송신 직전/직후 체결되면 슬롯과 브로커 실제가 어긋날 수 있다. - - 완화 방안: 취소 송신 후 즉시 롤백하지 말고 cncl_yn/poll 확인 후 전이(FR-4). 이미 체결 시 정상 경로로 수렴. -- R-2 (Medium): 슬리피지 보호 지정가 산출 오류로 의도와 다른 체결/미체결. - - 완화 방안: 방향별 부호·산출식을 명확히 정의(OQ-2), 단위 테스트. 밴드 밖은 timeout 취소로 안전 수렴. -- R-3 (Medium): 부분 체결분 처리 오류로 회계/포지션 불일치. - - 완화 방안: dcy-013 부분 취소 회계 선례 재사용 + 브로커 정본 대조(REQ-062) + 테스트. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-020(자산 중심 영구 슬롯·slot_id 정본)·dcy-021(부분 매도)이 구현 완료된 상태다(BAT-027~031). -- A-2: 브로커 어댑터 `cancel_order` 가 동작하며 cncl_yn 으로 취소 결과를 관측 가능하다(dcy-013). -- A-3: 전략 모듈이 intent 에 실행 정책 필드를 발신하도록 합의·변경 가능하다(외부 repo). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: timeout 시맨틱 범위 — 단순 `timeout_sec`(미체결 시 취소)만 우선 도입하고 IOC/FOK 등 TIF 분류는 후속으로 둘지, 본 주기에 함께 정의할지. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: **timeout 만 도입** — `timeout_sec`(N초 미체결 시 코어가 `cancel_order` 송신·롤백, GTD/timeout 성격, 브로커 무관). **IOC/FOK 등 네이티브 TIF 는 후속 주기**(브로커 네이티브 플래그·부분 체결 규칙 별도). 반영: §4, FR-1/2, §5 범위 경계. -- OQ-2: 슬리피지 산출 규칙. - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: `slippage_pct` 는 **기준가 `entry_price` 가 있을 때만** 적용 — 실효 지정가 = 매수 `entry_price*(1+pct)` / 매도 `entry_price*(1-pct)`(KRX 호가단위 반올림, 체결 보장 측). `entry_price` 없으면(순수 시장가) **무시**(무제한). 시장가엔 곱할 기준이 없다는 점에서 "기준가 없으면 슬리피지 없음". 반영: FR-3, §4. -- OQ-3: 필드 상호작용/우선순위. - - 상태: Answered (2026-06-10, OQ-2 동반 결정) - - 결정: `entry_price`=기준/목표, `slippage_pct`=허용폭 → 실효 limit = `entry_price*(1±pct)`. `entry_price` 단독(slippage 없음)=하드 지정가(현행). `timeout_sec`=주문 유효시간(미체결 취소), `forced_close_at`=포지션 만기 청산(별개 축, REQ-066/037). 네 필드 역할 분리 명확. 반영: FR-3, §4, payload 계약(REQ 단계 interface-contract). -- OQ-4: 계약/발신 위치 — `timeout_sec`/`slippage_pct` 를 buy/sell payload optional 로 추가(core 수신)하고, 전략 모듈(orb-v1) 발신 변경을 어떻게 조율할지(기본값·하위호환·deprecate 정책). - - 상태: Answered (2026-06-10, 사용자 결정) - - 결정: (1) buy/sell payload 에 **optional** 필드 추가 — core 수신 측 먼저 배포(누락 시 무시), orb-v1 발신은 별도 일정(lockstep 불필요·하위호환). (2) `timeout_sec` 기본값 = **운영자 config opt-in**(per-instance TOML `default_order_timeout_sec`, 미설정=OFF=현행), per-intent override. `slippage_pct` 기본값 없음. (3) **deprecate 없음**(전부 신규 optional 필드, `entry_price` 등 기존 필드 유지). 반영: FR-1, §4, 영향 범위(config). - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: `timeout_sec=N` 을 실은 intent 가 N초 내 미체결이면 core 가 주문을 취소하고 슬롯을 롤백하며 부분 체결분을 보존한다(고착 없음). -- S-2: `slippage_pct` 가 주어진 주문이 방향별 보호 지정가로 제출되고, 밴드 밖이면 미체결 → timeout 취소로 수렴한다. 미지정 주문은 기존 동작과 동일(회귀 없음). -- S-3: 취소-체결 race 에서 이미 체결된 경우 정상 체결 경로로 수렴(임의 롤백 없음). 백스톱 운영자 `cancel` 로 timeout 없는 고착도 해소된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: timeout 경과 픽스처 → 취소·롤백·부분 보존 단위/통합 / 데이터 출처: 슬롯 전이, 브로커 cancel_order/poll -- S-2 측정 방식: 시장가/지정가 + slippage 산출 단언 + 미지정 회귀 / 데이터 출처: OrderRequest limit_price, 슬롯 -- S-3 측정 방식: 취소-체결 동시 시나리오 수렴 + 운영자 cancel 백스톱 / 데이터 출처: poll_fill/cncl_yn, control RPC - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: TIF 범위(OQ-1 timeout만)·슬리피지 규칙(OQ-2 entry_price 밴드) 합의 → In/Out 경계 명확 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~3 측정 시나리오·데이터 출처가 테스트 가능한 형태로 정의됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(취소-체결 race)은 FR-4(취소 송신·확인 분리)로 해소 방향 확정. 해소 책임은 구현 batch. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~4 전부 Answered (2026-06-10 사용자 결정) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 승인자 Justin Ko confirm-discovery 수행 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 갱신 (주축 재작성) -- 생성 경로: docs/discovery/dcy-022_20260610_stuck-pending-order-recovery.md -- 참조 문서: - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (자산 중심 영구 슬롯·slot_id 정본·상태머신 — 토대) - - docs/discovery/dcy-021_20260610_position-sizing-target-intent.md (부분 매도 — 실행 정책 적용 대상) - - docs/discovery/dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md (stale Alert-only + cncl_yn 롤백·부분 취소 회계 선례) - - docs/interface-contract.md(buy/sell payload), docs/runtime-flows.md (baseline — 갱신 대상) - - GitHub issue #21(pending 고착) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 (OQ-1~4 전부 Answered — §6 참조) - -## CONFIRM -- 없음 (전제 dcy-020/021 구현은 BAT-027~031 로 충족 — A-1) - -## DATA -- 없음 (외부 데이터 의존 없음 — 내부 도메인/브로커 계약 + 전략 발신 합의) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~4 전부 Answered, C형 플레이스홀더 0, 저장소 사실 재확인 일치(cancel_order 어댑터 존재·미배선, payload 무 timeout/slippage, stale Alert-only). REQ Drafting 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-10 19:30 KST diff --git a/docs/discovery/dcy-023_20260610_allocated-capital-dead-field-removal.md b/docs/discovery/dcy-023_20260610_allocated-capital-dead-field-removal.md deleted file mode 100644 index 92ecdc0..0000000 --- a/docs/discovery/dcy-023_20260610_allocated-capital-dead-field-removal.md +++ /dev/null @@ -1,208 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-023_20260610_allocated-capital-dead-field-removal -- 이슈명: allocated_capital 죽은 필드 제거 — `enter --capital` 옵션 + slot/intent/snapshot 계약 정리 (issue #22) -- 작성 시각(KST): 2026-06-10 21:27 -- 마지막 갱신 시각(KST): 2026-06-10 21:42 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 신규 REQ 없음 — change-req 5건: REQ-002(CHG-20260610-02)·REQ-006(CHG-20260610-02)·REQ-007(CHG-20260610-02)·REQ-028(CHG-20260610-01)·REQ-032(CHG-20260610-07) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #22 (`trex-core enter --capital 옵션 및 slot allocated_capital 필드 제거`) -- 해석: - - issue #22 가 지적한 대로 `allocated_capital` 은 주문 사이징·회계·전략 결정 어디에도 쓰이지 않는 죽은 메타데이터다 — 설정(CLI)·운반(intent)·영속(daily_slot)·표시(snapshot) 경로만 존재한다. - - dcy-021(포지션 사이징)에서 "전략이 cash+quantity 로 qty 를 계산해 명령형 intent 로 제출, core 는 사이징하지 않음" 계약이 확정되어 capital 기반 자동 사이징의 미래 용도도 사라졌으므로, 필드 제거의 전제(OQ-3)가 사실상 해소된 상태에서 제거 범위·마이그레이션 정책만 결정하면 된다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery 22건 중 `allocated_capital` 제거를 다루는 문서 없음. 가장 가까운 dcy-021(포지션 사이징)은 qty 사이징 계약 확립이 주제이고, 본 건은 그 결정의 귀결인 죽은 필드 정리로 문제 정의가 다르다. - - 슬롯 데이터 계약·영속(컬럼 drop)·intent payload·CLI·snapshot 노출에 걸치는 다중 REQ change-req 묶음이라 독립 주기로 추적하는 편이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `allocated_capital` 소비처는 전수 추적 결과 설정·운반·영속·표시뿐이다: `trex_core/runtime/cli.py`(`--capital`→payload, slot detail 표시), `trex_core/application/intents.py`(BuyIntent 필드 + admission 전달), `trex_core/application/messaging_handlers.py`(payload 파싱), `trex_core/domain/slot/models.py`(Slot 필드 + `EXPECTED_SLOT_FIELDS`), `trex_core/domain/slot/service.py`(buy/전이 시 설정·클리어), `trex_core/adapters/persistence/postgresql.py`(daily_slot 컬럼 SELECT/UPDATE/복원), `trex_core/application/portfolio.py`(snapshot `slots[]` 노출). 의사결정/회계/사이징 소비자는 없다. - - 집행 수량은 `qty = int(slot.qty or 0)` 로 `slot.qty` 에서만 온다. issue #22 가 지적한 footgun(`--capital` 만 주고 `--qty` 누락 → 0주 주문)의 CLI 측면은 BAT-033(REQ-032 AC-7, `--qty` 필수 검증)으로 이미 차단됐으나, 필드 자체는 슬롯 계약·영속·스냅샷에 잔존해 유지 비용과 "capital 이 사이징한다"는 오해 여지가 남아 있다. - - 마이그레이션은 012 까지 존재하며, BAT-030 의 011(`drop_pool_model_columns`)이 비가역 컬럼 drop + 사전 백업 운영 절차의 선례를 남겼다. - - baseline 문서 `docs/project-structure.md`, `docs/runtime-flows.md` 존재 — 확인 결과 두 문서 모두 `allocated_capital` 을 직접 기술하지 않으므로 FR-5(baseline 정합)는 제거 후 재확인 수준의 경량 항목이다. -- 기대 상태 - - `trex-core enter` 에서 `--capital` 옵션이 제거되고, buy intent payload·Slot 모델·daily_slot 컬럼·snapshot `slots[]` 에서 `allocated_capital` 이 사라진다. - - 슬롯 데이터 계약이 실제 소비되는 필드만 남아 계약·영속·스냅샷·테스트의 유지 비용이 줄고, capital 기반 사이징 오해가 원천 제거된다. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/batch 산출물 작성 - - 운영자(Justin Ko) — CLI 인터페이스 변경(`--capital` 제거)과 마이그레이션(컬럼 drop) 승인 - - 전략 모듈 발신자 — buy intent payload 계약 변경의 영향 대상(현재 `allocated_capital` 송신 여부 확인 필요) -- 제약사항 - - daily_slot 컬럼 drop 은 비가역 — BAT-030 선례처럼 사전 백업 + 롤백 절차 명시 필수. - - 운영 라이브 DB 에 대한 파괴적 작업 금지 원칙 — 마이그레이션 검증은 테스트 DSN(trex_core_test)에서 수행. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 `trex-core enter --qty 10 --yes` 로 진입한다 — `--capital` 은 더 이상 받지 않으며, 잘못 입력하면 argparse 가 즉시 거부해 "capital 로 사이징된다"는 오해가 생길 여지가 없다. -- 전략 모듈이 buy intent 를 제출할 때 payload 에 `allocated_capital` 을 넣지 않는다(또는 deprecated 기간 동안 넣어도 무시된다) — 계약 문서와 실제 동작이 일치한다. -- 개발자가 Slot 모델/persistence/snapshot 코드를 읽을 때 죽은 필드가 없어 실제 소비되는 계약만 추적하면 된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- `trex-core enter` 의 `--capital` 옵션 제거 + buy intent payload 의 `allocated_capital` 필드 폐기(즉시 제거 vs deprecated 수신-무시 중 OQ-1 결정). -- Slot 도메인 모델·`EXPECTED_SLOT_FIELDS`·persistence flat 매핑·daily_slot 컬럼(마이그레이션 013, drop)·snapshot `slots[]` 노출에서 `allocated_capital` 제거. -- 데이터 계약 변경에 따른 baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 슬롯 필드 기술 정합 갱신(해당 기술이 있는 경우). - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/runtime/cli.py`(`--capital` 인자·summary·payload·slot detail 표시), `trex_core/application/intents.py`(BuyIntent 필드·admission), `trex_core/application/messaging_handlers.py`(payload 파싱), `trex_core/domain/slot/models.py`(+`EXPECTED_SLOT_FIELDS`), `trex_core/domain/slot/service.py`(설정/클리어 경로), `trex_core/adapters/persistence/postgresql.py`(컬럼 매핑), `trex_core/application/portfolio.py`(snapshot 노출) - - 신규 마이그레이션 `013_drop_allocated_capital.sql`(ALTER TABLE daily_slot DROP COLUMN IF EXISTS, 멱등) + 관련 단위 테스트 정리 -- 문서 영향: - - REQ-002(슬롯 도메인)·REQ-006(persistence/마이그레이션)·REQ-007(buy intent payload)·REQ-028(snapshot slots[])·REQ-032(운영자 CLI) change-req - - baseline `docs/project-structure.md`/`docs/runtime-flows.md` 슬롯 필드 기술 정합(존재 시) -- 운영 영향: - - 마이그레이션 013 적용(비가역 drop — 사전 백업 절차), `enter --capital` 사용 스크립트/습관 변경 공지. 데몬 재기동 1회. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `trex-core enter` 는 `--capital` 옵션을 받지 않는다. 지정 시 argparse 수준에서 unknown argument 로 거부된다. -- FR-2: buy intent payload 계약에서 `allocated_capital` 이 즉시 제거된다 — messaging handler 의 파싱 제거로 외부 발신자가 키를 계속 보내도 unknown key 로 무시된다(OQ-1 결정: 즉시 제거). -- FR-3: Slot 모델·`EXPECTED_SLOT_FIELDS`·persistence 매핑·daily_slot 컬럼에서 `allocated_capital` 이 제거된다(마이그레이션 013, `DROP COLUMN IF EXISTS` 멱등). -- FR-4: snapshot `slots[]` 응답과 CLI `slots` detail 표시에서 `allocated_capital` 필드가 사라진다. -- FR-5: baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)에 슬롯 필드/intent 계약 기술이 있으면 제거 결과와 정합하도록 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 마이그레이션 013 은 멱등(`IF EXISTS`)이며, 구 코드와의 공존이 불가능한 비가역 drop 이므로 적용 전 백업·롤백 절차를 release 문서에 명시한다(BAT-030 011 선례 준용). -- NFR-2: 전체 단위 테스트 suite 회귀 없음(제거 대상 필드를 참조하던 테스트는 함께 정리). -- NFR-3: 변경 후에도 buy intent admission·집행 경로의 런타임 동작(qty 기반 사이징)은 불변이다 — 죽은 필드 제거가 거래 동작을 바꾸지 않는다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - CLI `--capital` 제거, buy intent `allocated_capital` 폐기, Slot/persistence/snapshot 필드 제거, 마이그레이션 013, 관련 REQ change-req 5건, baseline 정합 -- Out of Scope - - capital 기반 자동 사이징(capital→qty) 도입 — dcy-021 에서 "전략이 사이징, core 미관여" 로 확정되어 채택하지 않음 - - `entry_price`/`limit_price` 등 다른 슬롯 필드 정리 — 본 건과 무관 - - trex-orb-v1 등 외부 전략 모듈 저장소의 발신 코드 수정 — 사용자 확인 결과 무시(범위 제외). RabbitMQ payload 는 관용적 계약이라 송신 잔존이 무해함. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Low): 외부 발신자(trex-orb-v1, `intent_builder.py:56`)가 `allocated_capital` 을 계속 송신함 — 파싱 제거 후에는 unknown key 로 무시되어 동작 영향 없음(사용자 확인: 외부 발신자 무시). - - 완화 방안: messaging handler 파싱 제거만으로 충분. 외부 저장소 수정은 범위 제외. -- R-2 (Medium): daily_slot 컬럼 drop 은 비가역 — 롤백 시 구 코드는 해당 컬럼을 UPDATE 절에 포함하므로 컬럼 부재 시 구 리비전과 공존 불가. - - 완화 방안: BAT-030 011 선례 준용 — 적용 전 daily_slot 백업, release 문서에 롤백 절차(컬럼 재추가 + 백업 복원) 명시, 테스트 DSN 에서 round-trip 검증 후 적용. -- R-3 (Low): 제거 대상 필드를 참조하는 테스트·스냅샷 fixture 누락으로 suite 가 깨질 수 있음. - - 완화 방안: `allocated_capital` 전수 grep(소스+테스트) 후 일괄 정리, 전체 suite green 을 DoD 로 설정. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: capital 기반 자동 사이징은 향후에도 core 책임이 아니다 — dcy-021 확정 계약(전략이 cash+quantity 로 qty 계산, 명령형 intent)이 유지된다. -- A-2: 운영 daily_slot 의 기존 `allocated_capital` 값은 보존 가치가 없다(의사결정·회계 소비자가 없었으므로 이력 데이터로도 무의미). 필요 시 drop 전 백업으로 충분하다. -- A-3: BAT-033 의 `--qty` 필수 검증이 release 되면 `--capital` 단독 입력 footgun 은 이미 차단된 상태에서 본 건은 순수 계약 정리다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: buy intent payload 의 `allocated_capital` 을 즉시 제거(수신 시 무시)할지, deprecated 명시 후 유예 기간을 둘지. - - 상태: Answered — **즉시 제거** (2026-06-10, 운영자 결정) - - 근거: RabbitMQ payload 는 관용적 계약이라 파싱 제거 = 수신-무시이며 유예가 주는 추가 안전이 없음. trex-orb-v1 이 실제 송신 중이나(intent_builder.py:56) 사용자 확인으로 무시(범위 제외). FR-2 에 반영 완료. - - 종료 조건: 충족 (FR-2 본문 반영). -- OQ-2: 마이그레이션 013 의 적용 시점 — 코드 배포와 동시 적용 vs 코드 먼저(컬럼 미사용화) 배포 후 별도 시점 drop. - - 상태: Answered — **동시 적용** (2026-06-10, 운영자 결정) - - 근거: 단일 인스턴스 운영 + BAT-030 마이그레이션 011(pool-model 컬럼 drop) 동시 적용 선례. 롤백은 "백업 복원 + 구 리비전"으로 동일하게 커버. `apply_migrations` 가 데몬 기동 시 자동 적용하므로 별도 절차 불필요. - - 종료 조건: 충족 (REQ-006 change-req 에 동시 적용 + 사전 백업 절차로 반영 예정). - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 소스 트리(`trex_core/`)에서 `allocated_capital` 참조가 0건이 된다(deprecated 유예를 택한 경우 messaging handler 의 무시 주석 1건만 예외). -- S-2: 마이그레이션 013 적용 후 daily_slot 에 `allocated_capital` 컬럼이 없고, `EXPECTED_SLOT_FIELDS` ↔ information_schema 계약 가드가 green 이다. -- S-3: 전체 단위 테스트 suite green(회귀 0) + `trex-core enter --capital ...` 이 argparse 오류로 거부된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: `grep -rn allocated_capital trex_core/ tests/` 결과 0건(또는 예외 1건) / 데이터 출처: 저장소 소스 트리 -- S-2 측정 방식: 테스트 DSN 에서 013 적용 후 information_schema 조회 + 컬럼 계약 가드 테스트 / 데이터 출처: trex_core_test PostgreSQL -- S-3 측정 방식: `pytest -q` 전체 green + CLI 수동 스모크(`enter --capital` 거부 확인) / 데이터 출처: 테스트 실행 로그 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 외부 전략 모듈 저장소 수정 포함 여부 확인 완료 — 운영자 확인으로 범위 제외 확정 (2026-06-10) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-3 측정 방식에 대한 사용자 이견 없음 — grep 전수·계약 가드 테스트·suite green 모두 저장소 근거로 측정 가능 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-2(비가역 drop) 완화 절차(백업·롤백, BAT-030 011 선례 준용)를 운영자가 OQ-2 동시 적용 결정과 함께 승인 (2026-06-10) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1(즉시 제거)·OQ-2(동시 적용) 모두 Answered (2026-06-10) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 지정 (2026-06-10, 사용자 명시) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-023_20260610_allocated-capital-dead-field-removal.md -- 참조 문서: - - GitHub issue #22 (trex-core enter --capital 옵션 및 slot allocated_capital 필드 제거) - - docs/discovery/dcy-021_20260610_position-sizing-target-intent.md (사이징 계약 확정 — capital 사이징 불채택 근거) - - trex_core/adapters/persistence/sql/011_drop_pool_model_columns.sql (비가역 컬럼 drop 선례) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~OQ-1 — buy intent `allocated_capital` 즉시 제거 vs deprecated 유예~~ → **즉시 제거 확정** (2026-06-10, 운영자) -- ~~OQ-2 — 마이그레이션 013 동시 적용 vs 2단계 분리~~ → **동시 적용 확정** (2026-06-10, 운영자) - -## CONFIRM -- ~~외부 전략 모듈(trex-orb-v1 등)의 송신 코드 정리 범위 포함 여부~~ → **무시(범위 제외) 확정** (2026-06-10, 운영자) - -## DATA -- ~~trex-orb-v1 의 `allocated_capital` 송신 여부~~ → **송신함** (`trex_orb_v1/runtime/intent_builder.py:56`). 단 사이징 입력은 자체 계산(`daemon.py:83`)이며 trex-core snapshot 의존 아님. 운영자 결정으로 무시. - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1(즉시 제거)·OQ-2(동시 적용) 모두 운영자 결정으로 Answered, 외부 발신자 확인 완료(무시·범위 제외). C 유형 플레이스홀더 0건, baseline 문서 모순 없음(allocated_capital 직접 기술 없음 확인). REQ change-req 5건(REQ-002·006·007·028·032) 인계 준비 완료. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-10 21:42 diff --git a/docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md b/docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md deleted file mode 100644 index 81ff906..0000000 --- a/docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md +++ /dev/null @@ -1,194 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-024_20260611_safety-sell-disable-mode -- 이슈명: safety-sell 자율 청산 비활성화 모드 (issue #15) -- 작성 시각(KST): 2026-06-11 -- 마지막 갱신 시각(KST): 2026-06-11 13:50 KST (change-req 진행: REQ-048 CHG-20260611-01(safety_sell_enabled TOML) + REQ-005 CHG-20260611-01(조건부 워커·기동 로그·runtime-flows), 둘 다 Implemented→Approved. 신규 REQ 없음(OQ-1).) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 없음 (change-req only, dcy-024 OQ-1). Delivery: REQ-048(CHG-20260611-01)·REQ-005(CHG-20260611-01) change-req → 차기 batch. - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 점수 파일: 없음 - - 관련 TODO/메모: GitHub issue #15 (자율 청산 비활성화 모드 설계 전문 포함) -- 해석: - - REL-006 피드백 환류 Discovery 후보로 식별됨. 자율 청산(forced_close / SL·TP / market_close) 워커를 완전히 끄고 전략·운영자 지시 매도만 허용하는 운영 모드 요구. - - REQ-048(per-instance TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다 — OQ-1·OQ-2 확정. SafetySellConfig(env) 변경 없음. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-007(core-autonomous-safety-sell)은 자율 매도 기능을 구현한 Discovery로 REL-003에서 released됨. 비활성화 모드는 그 기능의 확장이 아닌 운영 정책 플래그 추가 — 독립 후속 주기. - - 현재 SafetySellConfig에 `enabled` 플래그가 없고, `daemon.start()`의 workers 리스트에 `trex-safety-sell` 스레드가 조건 없이 포함됨. 코드 갭이 실존하는 신규 요구. - -# 3. 문제점의 요약 - -- 현재 상태 - - `trex_core/runtime/daemon.py:335`: `("trex-safety-sell", self._safety_sell_loop)` 이 workers 리스트에 항상 포함되어 데몬 기동 시 매번 자율 청산 워커가 시작됨. - - `SafetySellConfig`(config.py:113)에는 `enabled` 플래그가 없고, per-trigger 비활성화 수단도 없음. 관찰 전용 인스턴스·신규 전략 검증 환경에서 자율 청산을 막을 방법이 없음. -- 기대 상태 - - `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 추가(OQ-2 — env 제외, `max_active_slots`/`default_order_timeout_sec` 선례)로, `false` 시 `trex-safety-sell` 스레드를 데몬 기동부에서 아예 시작하지 않음. - - 기동 로그에 `safety_sell=disabled` 표시로 운영자가 안전망 없는 인스턴스임을 즉시 인지 가능(스냅샷 응답 미포함 — OQ-3). -- 이해관계자 - - 운영자/시스템 관리자 (Justin Ko): 관찰 전용 인스턴스·신규 전략 검증 시 자율 청산 OFF 필요 - - 전략 모듈(trex-orb-v1): 전략 지시 매도는 항상 유지되어야 함 - - 리뷰어/승인자: 안전망 없음을 명시한 운영 가이드 확인 -- 제약사항 - - `ExecutionService`(집행 루프), 운영자 CLI, kill-switch는 이 플래그에 영향받지 않아야 함 — 자율 청산(safety-sell 워커)만 OFF. - - Redis·RabbitMQ·PostgreSQL 의존성 변경 없음. config.py + daemon.py 한정 수정. - -## 주요 사용자 시나리오 - -- 관찰 전용 인스턴스: per-instance TOML `safety_sell_enabled = false` 설정 후 데몬 기동 → 기동 로그에 `safety_sell=disabled` 확인 → 포지션 유지 중 자동 청산 미발생, 전략 sell intent는 정상 집행. -- 신규 전략 검증(paper 브로커): safety-sell 없이 전략 모듈 매도 타이밍만 테스트 — 의도치 않은 장마감 강제청산 없이 전략 e2e 검증. -- 기본 운영(enabled=true): 기존 동작과 완전 동일. 하위 호환 변경 없음. - -# 4. 이번에 정의할 변경 - -- `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 필드 추가(OQ-2 — env 제외, SafetySellConfig 변경 없음). -- `daemon.start()` workers 리스트 구성부에서 `safety_sell_enabled=false` 시 `trex-safety-sell` 스레드 제외 — run_once 내부 if-가드보다 스레드 미기동이 물리적으로 자율 청산을 차단. -- 기동 로그(`RUNNING`)에 `safety_sell_enabled` 상태 노출(스냅샷 응답 미포함 — OQ-3) — 운영자가 인지 없이 안전망 없는 인스턴스를 운영하는 사고 방지. - -## 영향 범위 - -- 코드 영향: - - `trex_core/runtime/config.py`: `DaemonInstanceConfig`에 `safety_sell_enabled: bool = True` 필드 추가 + `from_toml()` 파싱 (SafetySellConfig 변경 없음) - - `trex_core/runtime/daemon.py`: `start()` workers 조건부 구성 — `config.instance.safety_sell_enabled` 참조 + 기동 로그 `safety_sell=disabled/enabled` -- 문서 영향: - - REQ-048(per-instance TOML 설정) change-req: `safety_sell_enabled` 필드 추가 - - REQ-005(daemon bootstrap) change-req: 워커 조건부 기동 명시 - - `docs/runtime-flows.md`: safety-sell 비활성 모드 플로우 추가 -- 운영 영향: - - 기본값 `true` — 기존 운영 인스턴스 동작 무변. 기동 전 `.env` 또는 TOML에 `TREX_SAFETY_SELL_ENABLED=false` 추가 시에만 비활성 모드로 전환. - - 비활성 모드 인스턴스는 손절/강제만기/장마감 안전망 없음 — 운영 가이드·기동 로그 명시 필수. - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: `DaemonInstanceConfig`(per-instance TOML)에 `safety_sell_enabled: bool = True` 필드를 추가하고, TOML `safety_sell_enabled = false`로 인스턴스별 비활성화를 제어한다. 기본값 `true`(미지정 시 기존 동작 유지). env var 경로 미노출 — 인스턴스별 운영 모드 차이(`max_active_slots` 선례). -- FR-2: `daemon.start()` 시 `DaemonInstanceConfig.safety_sell_enabled == False` 이면 `trex-safety-sell` 워커 스레드를 workers 리스트(`daemon.py:335`)에 포함하지 않는다. `True`(기본)이면 기존과 동일하게 기동한다. -- FR-3: 기동 시 RUNNING 로그에 `safety_sell_enabled=` 필드를 포함한다. 비활성 모드(`false`)이면 추가로 `no autonomous liquidation` 경고를 로그에 남긴다. -- FR-4: `safety_sell_enabled` 모드는 **기동 로그(FR-3)로만** 노출하고 **스냅샷/slots 응답에는 포함하지 않는다**(OQ-3). 근거: 인스턴스 config 이지 per-slot 데이터가 아니며, dcy-025 가 read 표면을 "응답=상태(slots[])+health"로 최소화하므로 config 를 응답에 싣지 않는 패턴과 정합한다. 런타임 노출이 필요해지면 per-slot 이 아니라 dcy-025 envelope-level(또는 별도 instance-status 표면)에서 조율한다. - -## 비기능 요구사항 - -- NFR-1: `enabled=false` 시 `ExecutionService`(집행 루프)·운영자 CLI(`enter`/`exit`/`cancel`)·kill-switch·messaging handler에는 영향 없음. 자율 청산(safety-sell 워커)만 미기동. -- NFR-2: 기본값 `true` — 기존 운영 인스턴스 재배포 시 config 변경 없이 기존 동작 보존(완전 하위 호환). -- NFR-3: 비활성 모드는 테스트에서 `config.safety_sell.enabled=False`를 주입하는 것만으로 재현 가능. 추가 mocking 불필요. - -## 범위 경계 - -- In Scope - - SafetySellConfig `enabled` 필드 + env + TOML 파싱 - - daemon.start() workers 조건부 구성 - - 기동 로그에 모드 노출(스냅샷 응답 미포함 — OQ-3) - - REQ-048(per-instance TOML) change-req + REQ-005(daemon bootstrap) change-req + runtime-flows 갱신 -- Out of Scope - - per-trigger 개별 플래그(forced_close / SL·TP / market_close 각각 ON/OFF) — 마스터 플래그 이후 별도 후속 이슈로 분리 - - kill-switch 포함 여부 변경 — 운영자 지시 경로이므로 유지(혼용 금지) - - 비활성 모드용 별도 watchdog 가시화 — 워커 미기동 시 로깅 없어도 무방(과설계 회피) - - trex-orb-v1 전략 모듈 변경 — 전략 sell intent는 messaging handler 경로이며 영향 없음 - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (Medium): 비활성 모드 인스턴스에서 전략 모듈이 죽어 폴링이 끊겨도 안전망 없이 포지션이 계속 유지됨 — 의도적 모드이지만 운영자 인지 없이 설정하면 무제한 손실 리스크. - - 완화 방안: 기동 로그 `no autonomous liquidation` 경고로 운영자가 즉시 인지(스냅샷 응답 미포함 — OQ-3). 운영 가이드에 위험 명시. -- R-2 (Low): TOML `safety_sell_enabled` 오타/잘못된 타입이 의도와 다르게 해석될 경우 기대와 반대로 자율 청산이 활성화됨. - - 완화 방안: TOML bool 파싱 규약을 따르되(미지정/비-false → `true`), 기동 로그에 파싱된 값을 항상 출력해 운영자가 확인 가능하게 함. -- R-3 (Low): 기존 SafetySellConfig 단위 테스트가 `enabled` 필드 추가 후 drift할 수 있음. - - 완화 방안: `EXPECTED_SLOT_FIELDS` drift test 선례처럼 config 계약 가드 테스트 추가 또는 기존 테스트 보완. - -## 가정 - -- A-1: 단일 마스터 플래그(`enabled`) 구현으로 시작하고, per-trigger 세분화는 후속 이슈로 분리한다. -- A-2: `enabled=false` 시 kill-switch, 운영자 CLI exit, 전략 sell intent는 그대로 동작한다. -- A-3: 신규 REQ 없이 REQ-048(per-instance TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다 — OQ-1·OQ-2 Justin Ko 확정. SafetySellConfig(env) 변경 없음. - -## 오픈 질문 - -- OQ-1: 신규 REQ 없이 change-req 경로(REQ-041·005)만으로 진행할지, 별도 Configuration REQ를 신규 발행할지? - - 상태: Answered - - 처리 방안: Justin Ko 확정 — **change-req 우선(신규 REQ 미발행)**. REQ-048(DaemonInstanceConfig TOML) + REQ-005(daemon bootstrap) change-req 2건으로 닫는다. - - 종료 조건: 완료 -- OQ-2: per-instance TOML `[safety_sell] enabled = false` 지원 포함 여부(env만 지원할지 TOML도 포함할지)? - - 상태: Answered - - 처리 방안: Justin Ko 확정 — **TOML 포함(env 제외)**. 당일 매매 인스턴스는 enabled=true, 중장기 보유 인스턴스는 enabled=false — 인스턴스마다 다른 운영 모드이므로 `DaemonInstanceConfig`(TOML)에 배치. `max_active_slots`·`default_order_timeout_sec` 선례 동일. REQ-048 change-req 추가. - - 종료 조건: 완료 -- OQ-3: 스냅샷 응답(`slots[]` envelope)에 `safety_sell_enabled` 필드 추가 포함 여부(FR-4)? - - 상태: Answered (2026-06-11, 사용자 결정) - - 결정: **기동 로그만, 스냅샷/slots 응답 미포함.** 근거: `safety_sell_enabled`는 인스턴스 config(per-slot 데이터 아님)이고, dcy-025 가 read 표면을 "응답=상태(slots[])+health"로 최소화하므로 config 를 응답에 싣지 않는 패턴과 정합. **per-slot 행에 넣지 않는다.** 런타임 노출이 필요해지면 per-slot 이 아니라 dcy-025 envelope-level(또는 별도 instance-status 표면)에서 조율(후속). 반영: FR-4. - -# 7. 초기 성공 기준 - -- S-1: per-instance TOML `safety_sell_enabled = false`로 데몬 기동 시 `trex-safety-sell` 스레드가 시작되지 않는다. 기동 로그에 `safety_sell_enabled=false`와 `no autonomous liquidation` 경고가 포함된다. -- S-2: 비활성 모드에서 forced_close_at 만기·SL·TP·장마감 시각이 지나도 슬롯이 자동 청산되지 않는다. 전략 모듈 sell intent + 운영자 CLI exit는 정상 집행된다. -- S-3: `enabled=true`(기본값) 시 기존 동작과 동일하며 기존 단위·통합 테스트 회귀가 없다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: 단위 테스트 — `enabled=False` config 주입 + `daemon.start()` 후 thread 이름 목록에 `trex-safety-sell` 부재 확인 / 데이터 출처: `tests/runtime/test_daemon.py` 또는 신규 테스트 -- S-2 측정 방식: run-the-app 스모크 — 비활성 모드 데몬 기동 후 holding 슬롯 유지 중 장마감 시각 경과 → 슬롯 상태 `holding` 유지 + 전략 sell intent 처리 확인 / 데이터 출처: 데몬 기동 로그(`safety_sell_enabled=false`) + 슬롯 상태(`trex-core slots`로 holding 유지 확인 — slots 출력 자체엔 safety_sell_enabled 미포함, OQ-3) -- S-3 측정 방식: `pytest` 전체 실행 회귀 없음, `ruff check` clean / 데이터 출처: CI 또는 로컬 테스트 결과 - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: per-trigger 플래그 Out of Scope + TOML 지원(OQ-2 TOML·env 제외) 확정 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1 단위(스레드 부재)·S-2 run-the-app 스모크·S-3 회귀로 정의됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(비활성 모드 손실)은 기동 로그 경고 + 운영 가이드로 해소, 가이드 작성 담당 = 구현 batch/Justin Ko(아래) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1/2/3 전부 Answered -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 승인 후 `confirm-discovery` 수행 -- [ ] (delivery 인계) 비활성 모드 운영 가이드(손실 리스크 경고) 작성 — 구현 batch 단계 산출물, 담당 project maintainers/Justin Ko (Discovery confirm 비차단) - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md -- 참조 문서: - - GitHub issue #15 — 자율 청산 비활성화 모드 설계 전문 (영향 범위·구현 방안·OQ 상세) - - docs/discovery/dcy-007_20260606_core-autonomous-safety-sell.md — 자율 매도 구현 Discovery (선행 참조) - - trex_core/runtime/config.py:113 — SafetySellConfig (변경 대상) - - trex_core/runtime/daemon.py:335 — workers 리스트 (`trex-safety-sell` 항목, 변경 대상) - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- 없음 (OQ-1·OQ-2 확정 완료) - -## CONFIRM - -- 없음. (1) 스냅샷 응답 노출 = OQ-3 Answered(기동 로그만). (2) kill-switch·운영자 CLI exit가 `safety_sell_enabled=false`와 무관하게 유지됨은 설계로 확정(A-2/NFR-1) — 저장소상 `KillSwitchService`(daemon.py:166)·운영자 CLI exit 는 `trex-safety-sell` 워커와 **독립 경로**라 워커 미기동이 그 둘에 영향 없음(확인 완료). - -## DATA - -- 없음. (운영 가이드(손실 리스크 경고) 작성은 Discovery 정책 결정이 아니라 **delivery 단계 산출물**이므로 §8 인계 체크로 이동 — 담당은 구현 batch/project maintainers.) - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1/2/3 전부 Answered(change-req 경로·TOML·기동 로그만). C형 플레이스홀더 0(CONFIRM/DATA 해소). 저장소 사실 재확인 일치. 운영 가이드 작성만 delivery 단계 비차단 인계. REQ Drafting 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-11 13:29 KST diff --git a/docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md b/docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md deleted file mode 100644 index 9c6e989..0000000 --- a/docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md +++ /dev/null @@ -1,228 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-025_20260611_strategy-read-surface-slots-req -- 이슈명: 전략 read 표면 재설계 — `trex.snapshot.req`→`trex.slots.req` + 슬롯 상태 필터 + 슬롯 단일 행(assets[]/available_slots/watchdog[] 흡수·제거) -- 작성 시각(KST): 2026-06-11 12:38 KST -- 마지막 갱신 시각(KST): 2026-06-11 14:05 KST (draft-req 완료: REQ-073/074/075 신규 + REQ-021/034/063 change-req(Approved 강등) + REQ-028 폐기 예정 forward-link) -- 대체됨: 없음 -- 후속 Discovery 참조: 전략↔자산 할당 모델(portfolio-hub 주도 다중 전략 설정) — 미생성, 후속 예정(본 표면에 strategy 필드 additive 추가 시점) -- 생성된 REQ 참조: REQ-073(slots.req RPC 계약), REQ-074(슬롯 단일 행 스키마), REQ-075(baseline 정합). 변경 REQ: REQ-021/034/063(각 CHG-20260611-01, Implemented→Approved), REQ-028(CHG-20260611-01, keep-current — delivery 배치에서 Deprecated 전환) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: dcy-020(자산 중심 영구 슬롯·REQ-063 slots 단일 뷰 — 직접 토대), BAT-022(universe↔slots 이중 표면 분리 선례), dcy-005(스냅샷 universe read 기원), REQ-028(snapshot 메시지 계약), REQ-021(messaging 토폴로지). -- 해석: - - dcy-020 이 "슬롯=자산 1:1 영구 슬롯"으로 모델을 바꾸면서, 전략이 universe 를 받던 스냅샷 표면(`trex.snapshot.req`, view=all/buyable/held + `assets[]`/`slots[]`/`available_slots`/`watchdog[]`)이 과도하게 분리·중복된 상태가 됐다. 자산 중심 모델에선 **슬롯 테이블 하나**가 곧 universe 이므로 `assets[]`·`watchdog[]` 같은 병렬 배열을 슬롯 행에 흡수할 수 있다. - - 본 Discovery 는 전략 read 표면을 **`trex.slots.req`(슬롯 단일 테이블 + 상태 필터)**로 재설계한다 — `assets[]`/`available_slots`/`watchdog[]` 제거, watchdog·자산 데이터(symbol/portfolio_id/cash_balance/asset_quantity)를 슬롯 행에 흡수, `spy`(워치독 무터치) 옵션, `degraded/reason/message` 유지. BAT-022·dcy-020(REQ-063)가 향하던 통합의 종착점이다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 큐 표면 교체(`trex.snapshot.req`→`trex.slots.req`)·응답 구조 재설계(assets/available_slots/watchdog 제거 + 슬롯 행 흡수)는 REQ-028(snapshot 계약)·REQ-021(토폴로지)·REQ-063(slots 뷰)을 동시에 재정의하는 인터페이스 변경이라 단일 REQ 미세 조정으로 흡수되지 않는다. - - dcy-020 이 자산 중심 모델을 confirmed·delivery(BAT-027~031)했으나 전략 read 표면의 통합은 별도 주제로 남겨졌다. 본 주기는 그 read 계약을 독립적으로 재설계한다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **요청 표면 `trex.snapshot.req`**: payload `{view: all|buyable|held, strategy_key?, trading_date?, actor?/touch_watchdog?}`(`messaging_handlers.py` snapshot 핸들러). view 는 파생 집합(`View` enum, `portfolio.py:65`): ALL=universe∪holding, BUYABLE=universe−occupied−masked, HELD=holding. - - **응답 envelope**(`build_snapshot_envelope`): `assets[]`(view 필터된 자산: symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params) + `slots[]`(인스턴스 전체 슬롯 테이블, view 무관 — BAT-022) + `available_slots`(M−active capacity) + `degraded`/`reason`/`message` + `watchdog[]`(별도 배열, `_attach_watchdog`). - - **이중 표면·병렬 배열**: `assets[]`(universe view)와 `slots[]`(슬롯 테이블)가 공존하고, `watchdog[]`는 슬롯과 조인해야 하는 별도 배열이다. 자산 중심 모델(슬롯=자산 1:1)에선 `assets[]`가 사실상 `slots[]`의 부분집합이라 중복이다. - - **워치독 터치 시맨틱**: 전략 폴링은 holding 슬롯 워치독을 갱신(생존 신호, REQ-034)하고, 운영자 조회는 `actor="operator"`/`touch_watchdog=false`로 억제(REQ-021 R-4) — 두 갈래가 ad-hoc 플래그로 표현됨. - - **strategy 필터**: `strategy_key`로 소유 자산 필터, `assets[]`에 `active_strategy_id`/`strategy_params` 노출. - - legacy stub 경로(`{view, strategy_id, slots[], capacity{}}`)도 잔존(구버전·테스트 호환). - - baseline(`docs/interface-contract.md` snapshot 섹션)이 이 표면을 기술 — 재설계 시 갱신 대상. -- 기대 상태 - - 전략이 **`trex.slots.req`** 하나로 슬롯 테이블을 받는다. 응답 = `slots[]` + `degraded`/`reason`/`message` 만. - - `slots[]` 각 행이 슬롯 상태(slot_id/status/qty/avg_price/limit_price/forced_close_at…)와 자산 데이터(symbol/portfolio_id/cash_balance/asset_quantity)와 **워치독 상태(expired/last_seen)**를 함께 운반한다 — 병렬 배열·조인 제거. - - `filter`로 슬롯 상태별(all/inactive/holding/pending/paused…) 조회. `spy=true`면 워치독 무터치(생존 신호로 오인 안 함). - - `assets[]`·`available_slots`·`watchdog[]` 제거. 매수 가능 목록 = `filter=inactive` 결과, capacity 는 admission(`capacity_full`)이 게이트. -- 이해관계자 - - 문서 작성자(AI agent) — Discovery/REQ/Batch 산출물 - - 리뷰어/승인자 — read 계약·토폴로지 재설계 승인 - - 전략 모듈(orb-v1 등) — 요청 큐·응답 구조 변경(외부 repo 소비 측) - - 운영 담당자 — `spy` 조회·슬롯 단일 뷰 관측 -- 제약사항 - - 큐 교체는 토폴로지(REQ-021/050 동적 큐명·per-instance)·전략 모듈 소비 측을 함께 바꿔야 한다 — cutover/과도기 필요. - - 슬롯 행에 브로커 정본(qty/avg)과 hub 정본(cash_balance/asset_quantity)이 공존하며 divergence(REQ-031/062) 가능 — 필드별 source 명시 필요. - - 단일 writer·워치독(REQ-034)·degraded 신호(REQ-028)의 의미를 새 표면에서 보존. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략이 `trex.slots.req {filter: "inactive"}` 로 매수 가능(잔고 0·미보유) 슬롯만 받아 신호를 낸다 — `assets[]` 없이 `slots[]` 한 배열로. -- 전략이 `{filter: "holding"}` 로 보유 슬롯의 수량·avg·워치독 상태를 한 행에서 본다(별도 watchdog[] 조인 불필요). -- 운영자가 `{filter: "all", spy: true}` 로 전 슬롯을 조회하되 워치독을 건드리지 않아 죽은 전략의 safety-sell 을 억제하지 않는다. -- 전략이 `{filter: "pending"}` 로 진행 중 주문 슬롯을 관측한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- **요청 표면 교체**: `trex.snapshot.req` 파이프라인 제거, `trex.slots.req` 추가(토폴로지·핸들러·전략 소비 측 cutover, OQ-1). -- **슬롯 상태 필터**: `filter` = status 단일 값 `{all, inactive, pending(buy∪sell), holding, error}`(상호배타, OQ-2). `inactive` 판정=슬롯 status(잔고 아님). overlay(`paused`/`warned`)는 행 플래그(직교 축), 서버측 overlay 필터는 후속. 파생 view(ALL/BUYABLE/HELD) 폐기. -- **`spy` 옵션**: `spy=true` 면 워치독 무터치(현 `actor=operator`/`touch_watchdog=false` 일반화). 기본 false(터치=생존 신호). -- **슬롯 단일 행 흡수**(OQ-3 (a)): `slots[]` 각 행에 자산 데이터(symbol/portfolio_id/cash_balance/asset_quantity) + 워치독(`watchdog:{expired,last_seen}` nested) + overlay(`paused`/`warned`) 포함. 수량은 `asset_quantity`(hub 정본)+`qty`(미러) **둘 다** 노출(REQ-062), 어긋나면 `warned`. 현금은 `cash_balance`+`cash_estimated`. 필드별 정본 라벨 명시. -- **응답 정리**: `assets[]`·`available_slots`·`watchdog[]` **제거**, `degraded`/`reason`/`message` **유지**. -- **strategy 필드 제외(이번 범위)**: `strategy_id`/`strategy_key`/`strategy_params`/`active_strategy_id` 제외(OQ-4 — 인스턴스=전략 스코프 전제). -- baseline(`docs/interface-contract.md`/`docs/runtime-flows.md`) snapshot→slots 표면 갱신. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/adapters/messaging/topology.py` 등 — `trex.snapshot.req`→`trex.slots.req` 큐명/바인딩(per-instance, REQ-021/050) - - `trex_core/application/messaging_handlers.py` — snapshot 핸들러 → slots 핸들러(filter/spy 파싱, 응답 구조), `_attach_watchdog` 제거(슬롯 행 흡수), legacy stub 경로 정리 - - `trex_core/application/portfolio.py` — `View` enum·`build_snapshot_envelope`(assets/available_slots/watchdog 제거, 슬롯 행에 자산·워치독 흡수), `slot_to_dict` 확장 - - `trex_core/runtime/cli.py` — 운영자 `slots`/`universe` 명령이 새 표면 사용(`spy`) - - (전략 모듈 orb-v1 — 외부 repo 소비 측 cutover) -- 문서 영향: - - `docs/interface-contract.md`(snapshot→slots 계약), `docs/runtime-flows.md`(read 표면) -- 운영 영향: - - 전략 모듈 동시 cutover 필요(큐 교체). 운영자 조회는 `spy`로 일원화. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: `trex.snapshot.req` 를 제거하고 `trex.slots.req`(per-instance 큐명) RPC 를 추가한다 — 요청 `{filter?, spy?}`, 응답 `{slots[], degraded, reason, message}`. **즉시 교체**(과도기 없음, OQ-1): 두 큐를 한 배포에 교체하고 전략 모듈을 lockstep 전환한다. -- FR-2: `filter` 는 **status 단일 값**(상호배타) `{all, inactive, pending(=pending_buy∪pending_sell), holding, error}` 로 조회한다(OQ-2). 미지정 시 `all`. `inactive` 판정 키는 슬롯 **status**(hub `asset_quantity` 아님 — 정합 정본은 상태머신). overlay(`paused`/`warned`)는 필터 값이 아니라 각 슬롯 행의 플래그로 노출하며(FR-3), 서버측 overlay 필터는 후속(MVP는 행 플래그 + client 필터). -- FR-3: `slots[]` 각 행은 아래를 **필드별 정본 라벨**과 함께 운반한다(OQ-3 (a)): - - 슬롯/코어 정본: `slot_id`, `status`, `portfolio_id`, `symbol`, `avg_price`, `limit_price`, `forced_close_at` - - 수량(REQ-062 정합, **둘 다 노출**): `asset_quantity`(**hub 정본** — 전략 reasoning primary) + `qty`(**슬롯 미러** — 코어 confirmed local). 정상 시 일치, 어긋나면 `warned=true`. 라이브 브로커 잔고는 행에 미포함(청산/reconcile 시점 정본). - - 현금: `cash_balance`(hub 정본) + `cash_estimated`(bool, dcy-018 추정 현금 오버레이) - - 워치독: `watchdog: {expired: bool, last_seen: ISO}` (nested object) - - overlay 플래그(직교 축, OQ-2): `paused`, `warned` - - 계약 문서(interface-contract)에 각 필드 정본 출처(hub/슬롯·코어/브로커/watchdog/overlay)를 명시한다. -- FR-4: `spy=true` 면 워치독을 갱신하지 않는다(생존 신호 미발생). 기본 false(holding 슬롯 워치독 터치, REQ-034 계승). -- FR-5: 응답에서 `assets[]`·`available_slots`·`watchdog[]` 를 제거하고 `degraded`/`reason`/`message` 는 유지한다. 매수 가능 목록은 `filter=inactive`, capacity 는 admission(`capacity_full`) 게이트로 대체한다. -- FR-6: `strategy_id`/`strategy_key`/`strategy_params`/`active_strategy_id` 는 본 표면에서 제외한다(OQ-4). -- FR-7: `docs/interface-contract.md`·`docs/runtime-flows.md` 를 새 read 표면으로 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 워치독 일관성 — 전략 폴링(기본)은 생존 신호로 터치, `spy` 는 무터치(REQ-034 R-4 의미 보존). -- NFR-2: 단일 표면 — 한 응답이 슬롯 테이블 하나로 자산·워치독을 모두 담아 소비자 조인이 불필요하다. -- NFR-3: cutover 안전 — 즉시 교체(과도기 없음, OQ-1)이므로 전략 모듈(orb-v1)을 같은 배포에 lockstep 전환한다. dcy-020 자산 중심 cutover(BAT-030)와 묶어 단일 전환점으로 관리한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - `trex.slots.req` 표면(요청 filter/spy, 응답 slots[]+degraded) + 슬롯 행 흡수(자산·워치독) - - `assets[]`/`available_slots`/`watchdog[]` 제거, `trex.snapshot.req` 폐기 - - interface-contract/runtime-flows 정합 -- Out of Scope - - strategy_id/key/params/active(이번 제외 — 인스턴스=다중 전략이나 할당 모델 portfolio-hub 미정의, 후속 discovery. loose JSON 미채택, 추후 명시 필드 additive 추가) - - intent admission/capacity 정책 자체(dcy-020 — read 표면만 변경) - - 포지션 사이징·실행 정책(dcy-021/022 — 별개) - - 전략 모듈(orb-v1) 내부 구현(외부 repo, 소비 계약만 합의) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High): **즉시 교체**(OQ-1, 과도기 없음)라 전략 모듈(외부 repo)이 같은 배포에 전환되지 않으면 전략이 스냅샷을 즉시 못 받는다. - - 완화 방안: 전략 모듈 lockstep cutover를 dcy-020 자산 중심 cutover(BAT-030, 이미 전략 계약 변경 동반)와 **단일 전환점으로 묶어** 조율. 배포 전 전략 모듈 `trex.slots.req` 대응 준비 확인. -- R-2 (Medium): 슬롯 행에 브로커 정본(qty/avg)과 hub 정본(cash_balance/asset_quantity)이 공존해 divergence(REQ-031/062) 시 소비자가 어느 값을 믿을지 모호. - - 완화 방안: 필드별 정본 출처·라벨을 OQ-3 에서 확정하고 계약 문서에 명시. -- R-3 (Medium): `available_slots` 제거로 전략이 capacity 사전 확인 불가 → 거부(`capacity_full`) 후 재시도 부담. - - 완화 방안: admission 게이트로 충분함을 합의(가정 A-3), 필요 시 별도 capacity 조회는 후속. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: dcy-020 자산 중심 영구 슬롯(슬롯=자산 1:1)이 구현 상태라 `assets[]`가 `slots[]`에 흡수 가능하다. -- A-2: (정정) 한 인스턴스 = **다중 전략**이다. 전략↔자산 할당은 **portfolio-hub 가 내려주는 미정의 설정**으로 별도 후속 discovery 에서 정의한다. 따라서 이번 read 표면은 전략 정보를 받지 않고(전략-무관), 전략 모델 확정 시 strategy 필드를 additive(비파괴)로 추가한다. -- A-3: capacity 사전 확인 없이 admission(`capacity_full`) 거부로 충분하다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: 큐 교체 방식 — `trex.slots.req` 신규 + `trex.snapshot.req` 즉시 폐기 vs 한시적 dual-listen/dual-publish 과도기. - - 상태: Answered (2026-06-11, 사용자 결정) - - 결정: **즉시 교체** — `trex.snapshot.req` 파이프라인 제거 + `trex.slots.req` 추가를 같은 배포에. dual-listen/dual-publish 과도기 **없음**. 전략 모듈(orb-v1)은 **lockstep cutover**(동시 전환)로 조율하며, dcy-020 자산 중심 cutover(BAT-030, 이미 전략 계약 변경 동반)와 함께 묶는 것을 권장. 반영: FR-1, NFR-3, R-1. -- OQ-2: `filter` 분류·조합 — status + overlay 혼합/단일, `inactive` 정의, `pending` buy/sell 합 여부. - - 상태: Answered (2026-06-11, 사용자 결정) - - 결정: `filter` = **status 단일 값**(상호배타) `{all, inactive, pending(=pending_buy∪pending_sell), holding, error}`. **status와 overlay는 직교 축** — overlay(`paused`/`warned`)는 필터 값이 아니라 **각 슬롯 행의 플래그**로 항상 노출, 서버측 overlay 필터는 후속(MVP는 행 플래그 + client 필터, "paused holding" 조합도 client에서). `inactive` 판정 키는 슬롯 **status**(hub `asset_quantity` 아님 — 상태머신 정본, 행에 asset_quantity 동반 노출). 임의 다중 status 조합 미지원(`pending` 묶음이 흡수). 반영: FR-2/3, §4. -- OQ-3: 병합 슬롯 행 필드·정본 — 수량 정본/미러 노출, divergence 표기, 워치독 형태. - - 상태: Answered (2026-06-11, 사용자 결정 (a)) - - 결정: **(a) 둘 다 노출** — `asset_quantity`(hub 정본, REQ-062)와 `qty`(슬롯 미러)를 행에 함께, 어긋나면 `warned`. `cash_balance`(hub)+`cash_estimated`(dcy-018). 워치독 = **nested `{expired,last_seen}`**. overlay `paused`/`warned` 플래그. 라이브 브로커 잔고는 행 미포함(청산/reconcile 시점 정본). interface-contract 에 필드별 정본 출처 라벨 명시. 반영: FR-3, §4. -- OQ-4: strategy 필드 제외의 전제·처리. - - 상태: Answered (2026-06-11, 사용자 결정) - - 결정: 인스턴스는 **다중 전략**이나 전략↔자산 할당 모델이 **portfolio-hub 주도로 미정의** → **후속 discovery 로 연기**. 이번 표면은 전략 정보(strategy_id/key/params/active)를 **제외**(받지 않음). **loose JSON 필드는 미채택**(undefined blob은 OQ-3 정본 라벨 원칙에 반하고, 나중에 명시 필드로 additive 추가가 비파괴). 한계: 미래 모델 전까지 `trex.slots.req` 는 전략-무관(인스턴스 전체 슬롯, per-strategy 스코핑 없음). 반영: A-2, FR-6, §5 Out of Scope, 후속 Discovery 참조. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 전략이 `trex.slots.req {filter}` 로 슬롯 테이블을 받고, 응답에 `assets[]`/`available_slots`/`watchdog[]` 가 없으며 `slots[]`+`degraded`/`reason`/`message` 만 있다. -- S-2: `slots[]` 각 행이 슬롯 상태 + 자산(symbol/portfolio_id/cash_balance/asset_quantity) + 워치독(expired/last_seen)을 함께 담아, 별도 배열 조인 없이 소비된다. -- S-3: `spy=true` 조회는 워치독을 갱신하지 않고(생존 신호 미발생), 기본 조회는 holding 워치독을 터치한다. `filter=inactive` 가 매수 가능 목록을 반환한다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: `trex.slots.req` 응답 키 집합 단언(assets/available_slots/watchdog 부재) 단위/통합 / 데이터 출처: RPC 응답 payload -- S-2 측정 방식: 슬롯 행 필드 단언(자산·워치독 포함) / 데이터 출처: slot_to_dict 확장 결과 -- S-3 측정 방식: spy on/off 워치독 갱신 여부 + filter=inactive 결과 단언 / 데이터 출처: WatchdogStore, 슬롯 상태 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 필터 분류(OQ-2)·strategy 제외 전제(OQ-4) 합의 완료 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~3 측정 시나리오·데이터 출처가 테스트 가능한 형태로 정의됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(큐 cutover)은 즉시교체(OQ-1) lockstep + dcy-020 cutover(BAT-030) 묶음으로 해소 방향 확정. 책임 = 구현 batch + 전략팀 조율. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~4 전부 Answered (2026-06-11 사용자 결정) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 승인자 Justin confirm-discovery 수행 - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md -- 참조 문서: - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (자산 중심 영구 슬롯·REQ-063 slots 단일 뷰 — 토대) - - docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md (스냅샷 universe read 기원) - - docs/interface-contract.md(snapshot 계약), docs/runtime-flows.md (baseline — 갱신 대상) - - 코드: trex_core/application/portfolio.py(View/build_snapshot_envelope), messaging_handlers.py(snapshot 핸들러/_attach_watchdog) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- 없음 (OQ-1/2/3 Answered — §6 참조) - -## CONFIRM -- 없음 (OQ-4 Answered — 인스턴스=다중 전략, 할당 모델은 후속 discovery, 이번 표면 전략 정보 제외) - -## DATA -- 없음 (외부 데이터 의존 없음 — 내부 계약/토폴로지 + 전략 발신 합의) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~4 전부 Answered(즉시교체·filter=status 단일·행 정본(a)·전략 정보 제외). C형 플레이스홀더 0, 저장소 사실 재확인 일치(trex.snapshot.req·View enum·_attach_watchdog 존재). REQ Drafting 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin -- Confirmed At (KST): 2026-06-11 13:32 KST diff --git a/docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md b/docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md deleted file mode 100644 index f0d9151..0000000 --- a/docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md +++ /dev/null @@ -1,246 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-026_20260612_sell-transaction-tax-and-fee-policy -- 이슈명: 매도 증권거래세 누락 + 수수료 정책 정정 (issue #24) -- 작성 시각(KST): 2026-06-12 -- 마지막 갱신 시각(KST): 2026-06-12 (confirm-discovery: Justin Ko 승인 — 상태 confirmed 전환, §8 체크 완료, Freeze 플래그 갱신) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 없음 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 점수 파일: 없음 - - 관련 TODO/메모: GitHub issue #24 (`bug` 라벨, 2026-06-11 trex-admin-daily-trading 4건 체결 증거 포함) -- 해석: - - 매도 증권거래세가 전 체결에서 `tax=0.0`으로 저장되어 실현 손익·일일 수익률이 과대 계산되는 운영 버그. 코드상 `dcy-004 OQ-3`로 미뤄둔 "정밀 세금 모델"이 운영에서 표면화된 사안. - - 단순 핫픽스가 아니라 수수료율/세율 정책(매수·매도 구분, bp 단위, 세금 산식)을 계약으로 확정해야 하므로 신규 Discovery로 정식화한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - 기존 ledger 계열 Discovery(dcy-004 연동, dcy-015 outbox 백오프, dcy-018 cash write-back)는 fee/tax **정책 자체**를 다루지 않음. fee/tax 정확성은 미해결 영역. - - `trex_core/application/broker_port.py:51` 주석이 "tax defaults to 0 (precise tax model is a follow-up Discovery, dcy-004 OQ-3)"로 후속 Discovery 필요를 명시 — 그 후속을 본 Discovery로 개설. - -# 3. 문제점의 요약 - -- 현재 상태 - - `trex_core/application/broker_port.py:118-119`: `_FEE_BPS_BUY = _FEE_BPS_SELL = 1.5`(0.015%) — 수수료만 계산. `calculate_fee(side, amount)` 단일 함수. - - `trex_core/adapters/broker/paper.py:90`·`kis.py:213·230`: 체결 시 `tax=0.0` **하드코딩** — 매도 증권거래세가 전혀 반영되지 않음. 한국 주식 매도세(증권거래세+농특세 약 0.18~0.20%)는 수수료(0.015%)보다 10배 이상 큰 비용인데 누락. - - 결과(issue #24 증거): 2026-06-11 4건 체결의 저장 `fee+tax` 합계 177.9원 vs 실제 기대 1,357원 — 차액 대부분이 누락된 매도세. -- 기대 상태 - - 매도 체결 시 증권거래세를 정책 기준으로 계산해 `FillStatus.tax`에 반영하고 `ledger_outbox`에 정확히 저장한다. - - 수수료율·세율을 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 관리하고, paper·KIS 어댑터가 동일 정책을 사용한다. -- 이해관계자 - - 운영자/시스템 관리자 (Justin Ko): 정확한 일일 손익·수익률 리포트 필요 - - portfolio-hub(ledger 수신측): `fee_amount`/`tax_amount` 정확값 수신 - - 전략 모듈(trex-orb-v1): 비용 반영된 실현 손익 기반 의사결정 -- 제약사항 - - KIS 실 체결 응답에 수수료/세금 필드가 포함되면 우선 사용하고, 없으면 정책 추정값으로 폴백해야 함(브로커 정본 우선 원칙). - - 정책 상수는 코드 하드코딩 대신 설정 가능성을 검토(세율 변경 대응). - -## 주요 사용자 시나리오 - -- 매도 round-trip 체결 후 운영자가 일일 리포트를 확인 → 수수료+세금이 실제 부과액과 일치하고 실현 손익이 과대 계산되지 않음. -- KIS 라이브 매도 체결 시 브로커 응답의 세금/수수료가 있으면 그 값이, 없으면 정책 추정값이 ledger에 기록됨. -- paper 브로커 검증 시에도 동일 정책으로 세금이 계산되어 라이브와 비교 가능한 ledger 엔트리 생성. - -# 4. 이번에 정의할 변경 - -- 매도 증권거래세 산식 도입 — `calculate_tax(side, amount)`(매수 0, 매도 정책 세율) 추가 또는 `calculate_fee`를 fee+tax 동시 산출로 확장. -- paper·KIS 어댑터의 `tax=0.0` 하드코딩 제거 — 정책 함수 호출로 교체, KIS는 브로커 응답값 우선·정책 폴백. -- 수수료/세율 정책 상수를 env 설정으로 노출 (OQ-2 Closed) 및 baseline 문서(`runtime-flows.md`) fee/tax 흐름 갱신. - -## 제비용 계산 공식 (OQ-1 Closed 기준) - -정책 파라미터 (env 설정, 기본값): - -``` -TREX_KIS_FEE_BPS = 1.5 # 수수료율 (매수·매도 공통, 1.5bps = 0.015%) -TREX_KRX_TAX_SELL_BPS = 20.0 # KRX(KOSPI·KOSDAQ) 매도 증권거래세율 (20.0bps = 0.2%) -``` - -> 변수명에 `KIS`/`KRX` 도메인 라벨이 붙지만, 이 값은 단일 정책 지점으로 **paper·KIS 어댑터가 동일하게 공유한다**(FR-2·NFR-2). paper 브로커는 KIS 제비용을 모사하므로 같은 상수를 사용한다. - -체결 단위 제비용: - -``` -amount = price × qty - -fee = amount × TREX_KIS_FEE_BPS / 10_000 # 매수·매도 동일 -tax = amount × TREX_KRX_TAX_SELL_BPS / 10_000 # 매도만 적용; 매수 = 0 -total = fee + tax -``` - -side별 전개: - -| side | fee | tax | total | -|------|-----|-----|-------| -| buy | amount × 0.00015 | 0 | amount × 0.00015 | -| sell | amount × 0.00015 | amount × 0.002 | amount × 0.00215 | - -issue #24 역산 검증 (qty=1, OQ-1): - -``` -BUY 291,500원 → fee=43.73원, tax=0원, total=43.73원 -SELL 301,000원 → fee=45.15원, tax=602원, total=647.15원 -BUY 295,000원 → fee=44.25원, tax=0원, total=44.25원 -SELL 298,500원 → fee=44.78원, tax=597원, total=641.78원 -───────────────────────────────────────────────────── -합계 fee=177.9원, tax=1,199원, total=1,376.9원 -(이슈 기재 1,357원과 차이 약 20원 — 이슈 원작성자 추정 오차) -``` - -## 영향 범위 - -- 코드 영향: - - `trex_core/application/broker_port.py`: `calculate_fee` 정정 + `calculate_tax` 신규(또는 통합), 세율/수수료율 상수 - - `trex_core/adapters/broker/paper.py`: `tax=0.0` → `calculate_tax(...)` (line 90 주변) - - `trex_core/adapters/broker/kis.py`: `tax=0.0` → 브로커 응답 파싱 + 정책 폴백 (line 213·230 주변) - - `trex_core/application/ledger_outbox.py`·`execution.py`: fee/tax 전달 경로(계약 불변 가능성 높음, 검증 필요) -- 문서 영향: - - REQ-018(체결→원장 기록) change-req: fee/tax 산출 정책 명시 - - REQ-011(BrokerPort + paper double) change-req 가능성: 수수료/세율 정책 함수 계약 - - `docs/runtime-flows.md`: 체결→ledger fee/tax 산출 흐름 -- 운영 영향: - - 배포 후 신규 체결부터 정확한 fee/tax 저장. **과거 잘못 저장된 행(2026-06-11 등) 보정 여부는 별도 결정**(OQ). - - portfolio-hub 측 손익 재계산 영향 — 수신측 정합 확인 필요. - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: 매도 체결에 대해 정책 기준 증권거래세를 계산해 `FillStatus.tax`에 반영한다. 매수 체결의 세금은 0으로 한다(한국 주식 매수 무세 전제). -- FR-2: 수수료율·세율을 단일 정책 지점에서 관리하고 paper·KIS 어댑터가 동일 정책을 사용한다. `tax=0.0` 하드코딩을 제거한다. -- FR-3: KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고, 없을 때만 정책 추정값으로 폴백한다. -- FR-4: `docs/runtime-flows.md`에 체결→ledger fee/tax 산출·저장 흐름을 갱신한다. - -## 비기능 요구사항 - -- NFR-1: fee/tax 계산은 결정적이며 단위 테스트로 매수/매도 각 케이스의 기대값을 검증 가능해야 한다(issue #24 증거 수치 회귀 테스트 포함). -- NFR-2: paper·KIS 어댑터가 동일 입력(side, amount)에 동일 정책 결과를 산출한다(브로커 응답 미존재 시). -- NFR-3: 정책 변경(세율 조정) 시 코드 한 지점 수정으로 전 경로에 반영된다. - -## 범위 경계 - -- In Scope - - 매도 증권거래세 산식 + 수수료율 정정 - - paper·KIS `tax=0.0` 하드코딩 제거 및 정책 함수 적용 - - KIS 브로커 응답 fee/tax 파싱(존재 시 정본) - - REQ-018(+REQ-011) change-req + runtime-flows 갱신 + 회귀 테스트 -- Out of Scope - - 과거 잘못 저장된 ledger 행 소급 보정: **Out of Scope** — 보정하지 않음 (OQ-3 Closed) - - portfolio-hub 측 손익 재계산 로직 변경(수신측 저장소 책임) - - 세율의 동적 설정 UI/운영 도구(필요 시 후속) - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (High): 세율 산식 오류 시 손익이 반대 방향으로 다시 왜곡됨 — 검증 부족하면 버그를 버그로 교체. - - 완화 방안: §4 공식 기준 기대 합계(fee 177.9 + tax 1,199 = **1,376.9원**)를 회귀 테스트 기준으로 고정. 매수/매도 각 케이스 단위 테스트. (이슈 기재 1,357원은 원작성자 추정 오차 약 20원 — baseline 아님.) -- R-2 (Medium): KIS 실 체결 응답의 수수료/세금 필드명·단위(원 vs bp)를 잘못 파싱하면 라이브에서만 오류 발생(paper에서 미검출). - - 완화 방안: KIS 응답 raw 필드 확인 후 파싱, 미확인 필드는 정책 폴백 + 로그. 라이브 스모크 시 실제 값 대조. -- R-3 (Medium): 과거 저장 행 미보정 시 2026-06-11 이전 리포트는 계속 왜곡 상태로 남음. - - 완화 방안: OQ-3에서 소급 보정 범위·방법 결정. 보정 안 하면 release 문서에 알려진 한계로 명시. - -## 가정 - -- A-1: 한국 주식 기준 매수는 무세, 매도에 증권거래세(+농특세)가 부과된다. 정확한 세율 값은 사용자 확인 대상(OQ-1). -- A-2: 신규 REQ 없이 REQ-018(+필요 시 REQ-011) change-req로 닫힌다 — 계약 구조는 유지, 산식·값만 정정. -- A-3: KIS 체결 응답에 수수료/세금이 포함될 수 있으나 항상 보장되지는 않아 정책 폴백이 필요하다. - -## 오픈 질문 - -- OQ-1: 적용할 정확한 매도 세율(증권거래세+농특세 합산 %)과 수수료율(매수/매도 bp)은? - - 상태: Closed - - 결정: 매도 증권거래세 **0.2%** (매수 무세), 수수료율 **0.015% (1.5bps)** 매수·매도 동일. issue #24 4건 체결 역산(qty=1 확인, 계산 기대 합계 1,376.9원)으로 현행 코드값 0.015% 정합 확인. 이슈 기재 1,357원은 약 20원 추정 오차. - - 종료 조건: 충족 -- OQ-2: 정책 상수를 코드 상수로 둘지, 설정(env/TOML)으로 노출할지? - - 상태: Closed - - 결정: **env 설정**으로 노출한다. 확정 변수명: - - `TREX_KIS_FEE_BPS` — 한국투자증권 수수료율 (bps, 기본 `1.5` = 0.015%, 매수·매도 공통) - - `TREX_KRX_TAX_SELL_BPS` — KRX(KOSPI·KOSDAQ) 매도 증권거래세율 (bps, 기본 `20.0` = 0.2%) - - 기존 패턴(`TREX_WATCHDOG_TIMEOUT`, `TREX_PAPER_FILL_AFTER_POLLS` 등) 준용 — `TREX_{도메인}_{파라미터}` 형식 - - 종료 조건: 충족 -- OQ-3: 과거 잘못 저장된 ledger 행(2026-06-11 등) 소급 보정 여부와 방법은? - - 상태: Closed - - 결정: **소급 보정하지 않는다** — 과거 행은 그대로 둔다. 배포 후 신규 체결부터 정확한 fee/tax 저장. Out of Scope 확정. - - 종료 조건: 충족 - -# 7. 초기 성공 기준 - -- S-1: 매도 체결의 `tax`가 정책 세율로 계산되어 0이 아닌 정확값으로 `ledger_outbox`에 저장된다. issue #24의 4건 시나리오 재현 시 `fee+tax` 합계가 §4 공식 기대값(fee 177.9 + tax 1,199 = **1,376.9원**)에 정확히 수렴한다. -- S-2: paper·KIS 어댑터가 동일 정책으로 fee/tax를 산출하며, KIS는 브로커 응답값이 있으면 그 값을 사용한다. -- S-3: 기존 단위·통합 테스트 회귀 없음 + 신규 fee/tax 회귀 테스트 green, `ruff check` clean. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: 단위 테스트 — issue #24 4건 체결값(price/qty) 입력 후 `fee+tax` 합계가 §4 공식 기대 1,376.9원에 수렴 확인 / 데이터 출처: issue #24 Evidence + §4 공식 + `tests/application/` 신규 테스트 -- S-2 측정 방식: paper·KIS 어댑터 단위 테스트로 동일 입력 동일 출력 확인 + KIS 응답값 우선 경로 테스트 / 데이터 출처: `tests/adapters/broker/` -- S-3 측정 방식: `pytest` 전체 회귀 + `ruff check trex_core tests` / 데이터 출처: 로컬/CI 결과 - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 소급 보정(OQ-3) In/Out 확정 + 정책 상수 위치(OQ-2) 확정 - - 결과: OQ-2 env 설정 확정, OQ-3 소급 보정 Out of Scope 확정 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: §4 공식 기대값 1,376.9원 역산에 필요한 세율/수수료율(OQ-1) 확정 - - 결과: OQ-1 매도세 0.2%, 수수료 0.015% 확정 — §4 역산 합계 1,376.9원 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(세율 오류) 회귀 테스트 기준값 확정 담당 Justin Ko - - 결과: R-1 완화 방안 확정 — §4 기대 1,376.9원 회귀 테스트 기준 고정, Justin Ko 담당 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 - - 결과: OQ-1·OQ-2·OQ-3 모두 Closed — Open 항목 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: Justin Ko 승인 후 `confirm-discovery` 수행 - - 결과: Justin Ko 승인, confirm-discovery 완료 - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md -- 참조 문서: - - GitHub issue #24 — fee/tax 저장 오류 증거(2026-06-11 4건 체결, 기대 1,357원 vs 저장 177.9원) - - trex_core/application/broker_port.py:51·118-131 — fee 모델 + tax follow-up 주석(dcy-004 OQ-3) - - trex_core/adapters/broker/paper.py:90, kis.py:213·230 — `tax=0.0` 하드코딩(변경 대상) - - docs/discovery/dcy-004_20260604_portfolio-hub-ledger-integration.md — OQ-3 정밀 세금 모델 선행 참조 - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- 매도 세율: **0.2%**, 수수료율: **0.015% (1.5bps)** — 확정 (OQ-1 Closed) -- 정책 상수 위치: **env 설정** — `TREX_KIS_FEE_BPS` (기본 1.5), `TREX_KRX_TAX_SELL_BPS` (기본 20.0) — 확정 (OQ-2 Closed) -- 과거 ledger 행 소급 보정: **Out of Scope** — 확정 (OQ-3 Closed) - -## CONFIRM - -- KIS 체결 응답에 수수료/세금 필드가 실제 포함되는지 확인 — 포함 시 정본 우선, 미포함 시 정책 폴백(FR-3·R-2 연결). delivery 단계 KIS raw 응답 확인으로 종료. - -## DATA - -- 없음 — issue #24 4건 체결의 기대 수수료/세금 분해값은 §4 "제비용 계산 공식"에서 체결별로 제공됨(answered). - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: OQ-1(세율/수수료율), OQ-2(정책 위치), OQ-3(소급 보정) 3건 모두 Closed. §8 체크 완료. 구현 부재는 정상(신규 작업 정의 Discovery). -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-12 diff --git a/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md b/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md deleted file mode 100644 index c726f0a..0000000 --- a/docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md +++ /dev/null @@ -1,224 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-027_20260612_long-term-strategy-slot-operation -- 이슈명: 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20) -- 작성 시각(KST): 2026-06-12 -- 마지막 갱신 시각(KST): 2026-06-12 (draft-req: REQ-076·077·078 생성 — 생성된 REQ 참조 갱신) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-076(watchdog timeout per-instance TOML), REQ-077(중장기 운영 프로파일), REQ-078(중장기 baseline 정합) - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 점수 파일: 없음 - - 관련 TODO/메모: GitHub issue #20 (중장기 슬롯 운영 — 같은 프로젝트 내 신규 도메인 vs 별도 프로젝트 분리). dcy-020(자산 중심 영구 슬롯 — Out of Scope 로 "중장기 전용 운영의 전체"를 본 주기로 이월), dcy-024(safety_sell_enabled — "중장기 보유 인스턴스는 enabled=false" 용도로 설계), dcy-009(다중 인스턴스 토폴로지)와 직접 연계. -- 해석: - - issue #20 은 구 pool 모델(daily_slot·일일 preallocation·장 마감 강제 청산) 기준으로 "중장기와 정면 충돌"을 분석했으나, 이후 dcy-020/BAT-027~030 영구 자산 슬롯 cutover 와 dcy-024/BAT-036 `safety_sell_enabled=false` 출시로 충돌 지점 대부분이 이미 해소되거나 옵트아웃 가능해졌다 — 이슈의 전제를 현행 코드 기준으로 재정렬해야 한다. - - 본 Discovery 는 (1) 구현 위치(이슈 OQ-1)를 현행 모델 기준으로 재판정하고, (2) 잔여 갭 — safety-sell OFF 시 멀티데이 보호장치 부재, watchdog 의미 재검토, forced_close_at 재해석, 중장기 인스턴스 운영 프로파일 — 의 범위를 확정한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - dcy-020 이 issue #20 을 입력으로 참조했으나 Out of Scope 에 "중장기 전용 운영(issue #20)의 전체 — 본 모델은 토대만 제공"으로 명시적으로 제외했다. 그 이월 범위를 다루는 독립 주기가 없다. - - 멀티데이 리스크 규칙(트레일링/타임스톱)·중장기 인스턴스 운영 프로파일은 기존 어떤 REQ 도 다루지 않아 change-req 로 흡수할 대상 REQ 자체가 없다. - -# 3. 문제점의 요약 - -- 현재 상태 - - **이슈 #20 의 충돌 분석 중 이미 해소된 항목**: `daily_slot` 일일 리셋·preallocation 은 BAT-030 cutover 로 폐기됨(영구 자산 슬롯이 날 경계를 넘어 holding 유지 — `trex_core/domain/slot/`, `(instance_key, portfolio_id)` 키). `closed→idle` 재진입·`entry_count` 도 폐기됨(BAT-030/034). 장 마감 강제 청산·safety-sell 은 `safety_sell_enabled=false`(BAT-036, per-instance TOML)로 인스턴스 단위 옵트아웃 가능. 다중 인스턴스 토폴로지(dcy-009)·per-instance TOML(REQ-048)·config-dir(BAT-026)로 `instance_key=longterm` 독립 데몬 운영이 이미 가능. - - **잔여 갭 1 — 멀티데이 보호장치 부재**: `SafetySellService`(`trex_core/application/safety_sell.py`)의 stop-loss·forced_close·market-close 는 전부 safety-sell 워커에 묶여 있고 watchdog-gated(당일 전략 이탈 전제)다. `safety_sell_enabled=false` 인 중장기 인스턴스는 **어떤 코어 보호장치도 없다** — 멀티데이 stop-loss·트레일링 스톱·타임스톱이 존재하지 않는다. - - **잔여 갭 2 — watchdog 의미 충돌**: 워치독 timeout 기본 120s(REQ-034)는 분 단위 폴링 당일 전략 전제다. 하루 1~2회 체크하는 중장기 전략은 watchdog expired 가 일상 상태가 되어 가시화 신호로서 의미를 잃는다. - - **잔여 갭 3 — forced_close_at 의미**: 당일 청산 시각 전제 필드(REQ-032/037)로, 중장기에서는 타임스톱으로 재해석하거나 미사용 처리할지 미정이었음 → **미사용(의미 없음)으로 확정** (§10 CONFIRM, FR-4). - - **잔여 갭 4 — 운영 프로파일 부재**: 중장기 인스턴스용 표준 TOML 구성(safety_sell off + market-close 미설정 + watchdog 완화 + max_active_slots)이 문서화·검증된 적 없다. baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재·최신이나 당일 운영 중심으로 기술돼 있다. -- 기대 상태 - - 구현 위치 확정 완료 — **(a′) 기존 슬롯 도메인 + 중장기 운영 프로파일 추가** (OQ-1 Answered). 신규 `PositionBook` 도메인 불필요. - - 보호 규칙 경계 확정 완료 — **"전략 모듈 전적 책임"으로 명시적으로 선을 긋는다** (OQ-2 Answered). 코어는 운영자 CLI `exit` 백스톱만 제공하며, 이 경계가 운영 문서에 명시된다. - - 중장기 인스턴스 운영 프로파일(TOML 레시피 + watchdog 정책)이 baseline 문서에 기술되고 실 데몬으로 검증된다. -- 이해관계자 - - 운영자/시스템 관리자 (Justin Ko): 당일·중장기 인스턴스 병행 운영, 리스크 정책 결정 - - 중장기 전략 모듈(신규 — trex-orb-v1 와 별개): intent 발신·슬롯 read 소비자 - - 기존 당일 인스턴스: 회귀 없이 무간섭 운영 유지 -- 제약사항 - - 검증된 당일 경로(BAT-027~036)에 회귀를 주지 않아야 한다 — 이슈도 "당일 슬롯 머신에 멀티데이 의미를 끼워 넣으면 조건 분기 폭증" 위험을 지적. - - 플랫폼 계층(브로커/ledger/transport/slots RPC/CLI)은 호라이즌 무관으로 공유한다 — 분리 시 인프라 복제 손해(이슈 분석 유지). - - 중장기 인스턴스는 별도 `instance_key` 로 큐·DB 스코프를 분리해 당일 인스턴스와 무간섭이어야 한다(dcy-009 토폴로지 전제). - -## 주요 사용자 시나리오 - -- 운영자가 `instance_key=longterm` 데몬을 중장기 프로파일 TOML 로 기동 → 매수 후 슬롯이 며칠~수개월 holding 을 유지하고, 장 마감·자정·재시작을 넘어도 청산되지 않는다. -- 중장기 전략 모듈이 하루 1회 `trex.slots.req` 로 보유 현황을 읽고 리밸런싱 sell/buy intent 를 발신 → watchdog 가 이를 비정상으로 오인하지 않는다. -- 보유 중 종목이 손절 기준을 하회 → (코어 규칙 제공 시) 멀티데이 stop-loss 가 발화하거나, (전략 책임 시) 전략 모듈이 sell intent 로 청산한다 — 어느 쪽인지 정책이 명확하다. -- 당일 인스턴스와 중장기 인스턴스가 같은 PostgreSQL·RabbitMQ 인프라에서 병행 운영되어도 슬롯·큐·정책이 간섭하지 않는다. - -# 4. 이번에 정의할 변경 - -- 구현 위치 확정 — **(a′) 기존 슬롯 도메인 유지 + 중장기 운영 프로파일/규칙 추가** (OQ-1 Answered). 현행 영구 자산 슬롯이 멀티데이 보유를 구조적으로 지원하므로 신규 `PositionBook` 도메인(이슈 원안 (a))·별도 레포((c))는 미채택. 이 판정이 REQ 분할의 전제다. -- 멀티데이 보호 규칙 경계 확정 — **전략 모듈 전적 책임** (OQ-2 Answered). `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴(stop-loss·트레일링·타임스톱)을 일절 사용하지 않고, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다(FR-2). -- 중장기 인스턴스 운영 프로파일 정의 — 표준 TOML 레시피(safety_sell off·market-close 미설정·watchdog per-instance timeout(OQ-3: TOML `watchdog_timeout_sec`, 중장기 1800s 이상)·max_active_slots) + baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 중장기 운영 흐름 갱신. - -## 영향 범위 - -- 코드 영향 (구현 위치 (a′) 확정 기준 — OQ-1): - - `trex_core/runtime/config.py` — 중장기 프로파일 설정: per-instance TOML `watchdog_timeout_sec`(OQ-3, 현재 env `TREX_WATCHDOG_TIMEOUT` 전용 → TOML 우선 지원) - - watchdog timeout per-instance 적용 경로(`trex_core/application/watchdog.py` + config wiring) - - 멀티데이 리스크 워커/`safety_sell.py` 변경 **없음** — OQ-2 결정(코어 손절매 루틴 미제공, 전략 전적 책임)으로 코어 보호 규칙 구현이 범위에서 제외됨 - - 당일 경로(`domain/slot/`, `intents.py`, `execution.py`)는 불변 목표 -- 문서 영향: - - `docs/project-structure.md`·`docs/runtime-flows.md`: 중장기 인스턴스 운영 흐름·프로파일 추가 (baseline 갱신) - - 신규 REQ 다수 예상(운영 프로파일·리스크 규칙) + REQ-048(TOML 설정)·REQ-034(watchdog) change-req 가능성 -- 운영 영향: - - 신규 `instance_key=longterm` 데몬 추가 기동(별도 TOML)— 기존 당일 인스턴스 무중단 - - 멀티데이 보유 포지션의 일일 회계·리포트 해석 변화(실현손익은 청산일 귀속) - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: 중장기 인스턴스 운영 프로파일을 정의한다 — 표준 TOML 레시피(safety_sell_enabled=false, market-close 미설정, watchdog 정책, max_active_slots)와 기동 검증 절차를 문서화하고 실 데몬으로 확인한다. -- FR-2: `safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴을 일절 사용하지 않는다. 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈의 전적 책임이며, 코어는 운영자 CLI `exit` 백스톱만 제공한다. 이 경계를 운영 문서에 명시한다. -- FR-3: watchdog 의 중장기 의미를 재정의한다 — per-instance timeout 설정 또는 중장기 인스턴스 비활성화 옵션 중 확정안을 적용해, 저빈도 폴링 전략이 expired 로 오인되지 않게 한다. -- FR-4: `forced_close_at` 은 중장기 인스턴스에서 **미사용(의미 없음)** 으로 확정한다 (CONFIRM 2026-06-12). 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가 — 운영자 CLI·intent 계약 문서에 "중장기 인스턴스에서 미사용"을 명시한다. -- FR-5: `docs/project-structure.md`·`docs/runtime-flows.md` 에 중장기 인스턴스 운영 구조와 흐름(병행 인스턴스 토폴로지, 프로파일, 리스크 규칙 경로)을 갱신한다. - -## 비기능 요구사항 - -- NFR-1: 당일 인스턴스 경로 무회귀 — 기존 단위·통합 테스트 전부 green, 당일 운영 TOML 의 동작 변화 0건. -- NFR-2: 인스턴스 간 무간섭 — 중장기 인스턴스의 슬롯·큐·정책 변경이 당일 인스턴스 DB/큐 스코프에 영향을 주지 않음을 통합 테스트로 검증 가능해야 한다. -- NFR-3: 프로파일 설정은 기존 per-instance TOML 메커니즘(REQ-048)을 재사용하고 새 설정 채널을 만들지 않는다. - -## 범위 경계 - -- In Scope - - 구현 위치 판정(이슈 OQ-1 재평가) 및 REQ 분할 전제 확정 - - 중장기 인스턴스 운영 프로파일(TOML 레시피 + watchdog 정책 + 기동 검증) - - 멀티데이 보호 규칙 경계 문서화 — **전략 모듈 전적 책임** + 운영자 CLI exit 백스톱 명시(코어 구현 없음) - - forced_close_at 중장기 의미 확정 - - baseline 문서(project-structure·runtime-flows) 중장기 운영 갱신 -- Out of Scope - - 중장기 전략 모듈 자체의 구현(별도 저장소/주기 — 코어는 계약·운영 토대만 제공) - - 포트폴리오 리밸런싱 엔진·목표 비중 관리(전략 모듈 책임) - - 일일 회계·리포트 체계 개편(현행 ledger 날짜 필터 해석 유지, 필요 시 후속) - - 신규 `PositionBook` 도메인 신설 — **Out of Scope 확정** (OQ-1: 구현 위치 (a′) 결정으로 미채택) - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (High): 보호장치 경계가 모호한 채 중장기 인스턴스를 운영하면, safety-sell OFF + 전략 모듈 장애 시 손실 포지션이 무기한 방치된다. - - 완화 방안: **전략 모듈 전적 책임으로 확정(OQ-2)**. 코어는 운영자 CLI `exit` 백스톱만 제공. 보호 규칙 공백 위험은 전략 모듈 설계 및 운영 절차(운영자 수동 CLI)로 완화한다. -- R-2 (해소됨 — OQ-2): 멀티데이 규칙을 기존 safety-sell 워커에 얹으면 당일 경로와 조건 분기가 얽혀 회귀 위험(이슈가 경고한 "분기 폭증")이 생긴다. - - 해소 근거: OQ-2 결정으로 코어가 멀티데이 보호 규칙을 구현하지 않으므로 safety-sell 워커 변경 자체가 없다. 당일 경로 테스트 전체는 NFR-1 무회귀 게이트로 유지. -- R-3 (Medium): watchdog 완화가 과도하면 중장기 인스턴스에서 전략 이탈을 영영 감지하지 못한다. - - 완화 방안: 비활성화 대신 긴 timeout(예: 일 단위) 설정을 기본안으로 검토하고, slots RPC `watchdog.last_seen` 가시성은 유지한다. -- R-4 (Low): 멀티데이 보유 중 universe 에서 자산이 빠지면(상폐 등) tombstone/retire 경로가 중장기 보유와 충돌할 수 있다. - - 완화 방안: dcy-020 의 universe-detached holding(tombstone) 경로가 이미 존재 — 중장기 시나리오로 검증 항목에 포함. - -## 가정 - -- A-1: 현행 영구 자산 슬롯(BAT-027~030)은 멀티데이 보유를 구조적으로 이미 지원한다 — 슬롯 키에 날짜가 없고 holding 이 날 경계·재시작을 넘는다(dcy-020 OQ-3 결정). -- A-2: 중장기 운영은 별도 `instance_key` 데몬으로 분리한다(이슈 OQ-3 의 권장 방향) — 단일 데몬 내 호라이즌 혼합은 다루지 않는다. **(승인 확정 — 2026-06-12, Justin Ko, §10 CONFIRM)** -- A-3: 플랫폼 계층(브로커/ledger/transport/slots RPC/CLI)은 변경 없이 공유 가능하다 — 이슈의 재사용 분석 유지. -- A-4: KIS·portfolio-hub·RabbitMQ 인프라를 그대로 사용하므로 별도 레포 분리(이슈 OQ-1 의 (c))는 손해다 — 이슈 분석 수용. - -## 오픈 질문 - -- OQ-1: 구현 위치 최종 확정 — (a′) 기존 슬롯 도메인 + 프로파일/규칙 추가 vs (a) 신규 PositionBook 도메인 병렬 신설. 현행 영구 슬롯이 멀티데이를 이미 지원하므로 (a′) 가 유력하나, 중장기 전용 수명주기 요구(트레일링 상태, 리밸런싱 이력 등)가 슬롯 도메인을 오염시킬 수준인지 판단 필요. - - 상태: Answered (2026-06-12, Justin Ko) - - 결정: **(a′) 기존 슬롯 도메인 유지 + 운영 프로파일/규칙 추가**. 현행 영구 자산 슬롯이 멀티데이 보유를 구조적으로 이미 지원하므로(날짜 없는 슬롯 키, holding 날 경계 통과) 신규 PositionBook 도메인 신설은 불필요. `{{DECIDE_POSITION_BOOK}}` 은 Out of Scope 확정. - - 반영 위치: `# 5` 범위 경계({{DECIDE_POSITION_BOOK}} Out of Scope 확정), `# 10` DECIDE 항목. -- OQ-2: 멀티데이 보호 규칙의 코어 제공 범위 — 멀티데이 stop-loss / 트레일링 스톱 / 타임스톱(forced_close_at 재해석) 중 코어가 구현할 항목과 전략 모듈 전적 책임으로 둘 항목은? - - 상태: Answered (2026-06-12, Justin Ko) - - 결정: **`safety_sell_enabled=false` 인스턴스는 코어 손절매 루틴을 일절 사용하지 않는다.** stop-loss·트레일링 스톱·타임스톱 등 모든 보호 규칙은 전략 모듈의 전적 책임이다. 코어는 운영자 CLI `exit` 백스톱(수동 청산)만 제공한다. R-1(방치 위험)은 전략 모듈 설계 책임 + 운영 절차(운영자 CLI) 로 완화한다. - - 반영 위치: `# 5` FR-2(멀티데이 보호 규칙 경계), `# 6` R-1 완화 방안 갱신, `# 10` DECIDE 항목. -- OQ-3: watchdog 중장기 정책 — per-instance timeout 연장(일 단위) vs 중장기 인스턴스 비활성화 옵션 중 어느 쪽인가? - - 상태: Answered (2026-06-12, Justin Ko) - - 결정: **per-instance timeout 연장**. OQ-4 최대 폴링 주기(10분/600s) 기준으로 per-instance TOML `watchdog_timeout_sec` 설정을 지원하고, 중장기 인스턴스는 1800초(30분) 이상으로 설정한다. `last_seen_at` 가시성은 유지한다. - - 알림 후속 주석: watchdog 소유권 진입(전략 이탈 감지) 시 사용자에게 외부 알림(예: 슬랙·이메일 등)을 발송하는 기능을 추후 별도 Discovery 로 구현할 예정이다. 현재 주기에서는 로그 경고(`watchdog ownership entered`) 수준으로 유지하고 알림 채널 연동은 범위 밖이다. - - 반영 위치: `# 5` FR-3(watchdog 재정의), `# 10` DECIDE 항목. -- OQ-4: 중장기 전략 모듈의 폴링·intent 발신 주기 전제 — 하루 몇 회 슬롯 read/intent 발신을 가정하는가? (watchdog·프로파일 수치의 입력) - - 상태: Answered (2026-06-12, Justin Ko) - - 결정: **최소 1분, 최대 10분 주기**로 `trex.slots.req` 호출 및 intent 발신. 빠른 경우 1분 간격, 느린 경우 최대 10분(600s) 간격을 전제. - - 반영 위치: OQ-3 watchdog timeout 수치 입력 — 10분 폴링 대비 충분한 여유(권장: 30분 이상). - -# 7. 초기 성공 기준 - -- S-1: `instance_key=longterm` 프로파일 TOML 로 기동한 데몬에서 매수→day 경계 통과→보유 유지가 확인된다(장 마감·재시작 후에도 holding 유지, 자율 청산 미발화). -- S-2: 보호 규칙 경계가 운영 문서에 명시된다 — `safety_sell_enabled=false` 인스턴스의 모든 보호 규칙(stop-loss·트레일링·타임스톱)은 전략 모듈 전적 책임(OQ-2), 코어는 운영자 CLI `exit` 백스톱만 제공. 백스톱 경로(CLI exit → 청산)가 중장기 인스턴스에서 동작함이 확인된다. -- S-3: 당일 인스턴스 전체 테스트 무회귀 + 중장기 인스턴스와의 무간섭(슬롯·큐 스코프)이 통합 테스트로 검증된다. -- S-4: baseline 문서(project-structure·runtime-flows)에 중장기 운영 구조·흐름이 반영되어 이후 REQ/batch 가 참조 가능하다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: 실 데몬 run-the-app 검증 — longterm TOML 기동 후 paper 매수, 시스템 시각/일자 경계 시나리오로 holding 유지 확인 / 데이터 출처: `run-trex-core-daemon` skill 경로 + 데몬 로그 + `trex-core slots` -- S-2 측정 방식: 운영 문서(baseline) diff 리뷰 — 보호 규칙 경계(전략 책임 + CLI 백스톱) 명시 확인 + 중장기 프로파일 인스턴스에서 `trex-core exit` 백스톱 paper 동작 확인 / 데이터 출처: `docs/runtime-flows.md` + 데몬 로그·`trex-core slots` -- S-3 측정 방식: `pytest` 전체 회귀 + instance_key 격리 통합 테스트(기존 `test_instance_topology.py` 패턴 확장) / 데이터 출처: 로컬/CI 결과 -- S-4 측정 방식: baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드와 정합 / 데이터 출처: `docs/project-structure.md`, `docs/runtime-flows.md` - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1(구현 위치 a′)·OQ-2(보호 규칙 전략 책임) Answered + A-2(병행 인스턴스) 승인 + forced_close_at 미사용 확정으로 충족 — PositionBook Out of Scope 확정. CONFIRM 잔여 0건 - - 결과: 충족 — In/Out 경계 §5 확정, 사용자 결정 잔여 0건 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: OQ-4 수치(1~10분 폴링, watchdog 1800s 이상) 확보로 S-1/watchdog 검증 수치 구체화 완료 - - 결과: 충족 — S-1~S-4 측정 방식·데이터 출처 §7 명시 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(보호장치 공백) — OQ-2 결정으로 경계 확정(전략 책임 + CLI 백스톱), 담당 Justin Ko - - 결과: 충족 — R-1 경계 확정, R-2 해소(OQ-2), R-3/R-4 완화 방안 확정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~4 전부 Answered — `Open` 항목 0개 - - 결과: 충족 — Open 0건 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: §10 CONFIRM 2건 해소 + Justin Ko 승인 후 `confirm-discovery` 수행 - - 결과: 충족 — Justin Ko 승인, confirm-discovery 수행 완료 - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md -- 참조 문서: - - GitHub issue #20 (closed — 본 Discovery 로 이월) — 중장기 슬롯 운영 도입, 호라이즌 결합 분석 + OQ-1~4 (구 pool 모델 기준 — 본 문서 §3 에서 현행 기준 재정렬) - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md — 영구 자산 슬롯 토대(Out of Scope 로 본 주기 이월 명시), OQ-3 trading_date 폐기 결정 - - docs/discovery/dcy-024_20260611_safety-sell-disable-mode.md — safety_sell_enabled per-instance TOML ("중장기 보유 인스턴스는 enabled=false" 용도 명시) - - trex_core/application/safety_sell.py — 당일 전제 보호장치(watchdog-gated stop-loss/forced_close/market-close), 멀티데이 규칙 부재 확인 - - trex_core/runtime/config.py — per-instance TOML(REQ-048)·market_close_liquidation_time·watchdog timeout 설정 현황 - - stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md — baseline 갱신 시 논리 템플릿 참조 - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE - -- ~~{{DECIDE_IMPLEMENTATION_LOCATION}}~~: 구현 위치 — **(a′) 기존 슬롯 도메인 + 운영 프로파일/규칙 추가** 확정 (OQ-1, 2026-06-12, Justin Ko) -- ~~{{DECIDE_RISK_RULES}}~~: 멀티데이 보호 규칙 — **전략 모듈 전적 책임** 확정. `safety_sell_enabled=false` 시 코어 손절매 루틴 미제공, 운영자 CLI exit 백스톱만 유지 (OQ-2, 2026-06-12, Justin Ko) -- ~~{{DECIDE_WATCHDOG_POLICY}}~~: watchdog 중장기 정책 — **per-instance timeout 연장** 확정 (OQ-3, 2026-06-12, Justin Ko). 알림 연동은 후속 Discovery. -- ~~{{DECIDE_POSITION_BOOK}}~~: PositionBook 도메인 신설 — **Out of Scope 확정** (OQ-1 결정, 2026-06-12, Justin Ko) - -## CONFIRM - -- ~~{{CONFIRM_PARALLEL_INSTANCE}}~~: 중장기 운영을 **별도 instance_key 데몬으로 분리** — 승인 확정 (A-2, 2026-06-12, Justin Ko). 단일 데몬 내 호라이즌 혼합은 다루지 않으며, dcy-009 토폴로지·REQ-048 per-instance TOML 전제 위에서 병행 운영한다. -- ~~{{CONFIRM_FORCED_CLOSE_SEMANTICS}}~~: forced_close_at 의 중장기 의미 — **미사용(의미 없음) 확정** (FR-4, 2026-06-12, Justin Ko). 중장기 모드에서 forced_close_at 은 의미가 없다. 집행 주체(safety-sell 워커)가 `safety_sell_enabled=false` 로 OFF 이므로 구조적으로 발화 불가하며, 타임스톱 재해석(별도 집행 경로 신설)은 OQ-2 결정(코어 손절매 미제공)과 충돌해 미채택. 운영자 CLI·intent 계약 문서에 "중장기 인스턴스에서 미사용" 명시. - -## DATA - -- 없음 — 중장기 전략 모듈의 폴링·intent 발신 주기는 OQ-4 에서 확보됨(answered): 최소 1분~최대 10분(600s) 주기, watchdog timeout 권장 1800초(30분) 이상의 입력값. - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1(구현 위치 a′)·OQ-2(보호 규칙 전략 전적 책임)·OQ-3(watchdog timeout 연장)·OQ-4(폴링 주기 1~10분) 4건 모두 Answered. CONFIRM 2건도 확정 — A-2(별도 instance_key 분리) 승인 + forced_close_at 중장기 미사용(의미 없음). 사용자 결정 잔여 0건. 구현 부재는 정상(신규 작업 정의 Discovery — 코어 변경은 watchdog per-instance TOML 정도로 경량). -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-12 diff --git a/docs/discovery/dcy-028_20260615_slot-strategy-state-store.md b/docs/discovery/dcy-028_20260615_slot-strategy-state-store.md deleted file mode 100644 index 6194eb8..0000000 --- a/docs/discovery/dcy-028_20260615_slot-strategy-state-store.md +++ /dev/null @@ -1,234 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-028_20260615_slot-strategy-state-store -- 이슈명: Slot Strategy-State 저장소 (per-strategy opaque state store on trex-core slots, issue #26 C-1/C-2/C-3) -- 작성 시각(KST): 2026-06-15 16:26 -- 마지막 갱신 시각(KST): 2026-06-15 18:05 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-079, REQ-080, REQ-081, REQ-082 (+ change-req: REQ-073, REQ-074) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #26 (trex-tsl strategy-daemon 팀 계약 제안), C-4 확인 코멘트·OQ 결정 코멘트(게시 완료), trex-tsl 팀 occupancy race 피드백(#4705990067) -- 해석: - - trex-tsl(매도 전략 데몬)이 TSL trailing stop line 계산에 필요한 **시간 누적 상태(예: entry 이후 peak price)**를 슬롯 수명에 묶어 server-side로 보관할 generic state store를 요청했다(C-1/C-2/C-3). - - 이번 Discovery는 "trex-core가 해석하지 않는 opaque state를 `(slot_id, strategy_id)` 키로 저장/조회/수명관리"하는 범위를 정의한다. OQ-1~4 전부 Answered. trex-tsl 팀이 점유 전환 ABA 누수(High)를 지적해 occupancy_seq 경량 가드(C-2b)를 v1 범위로 추가, full 3계층 분할만 후속 Deferred. C-4(SellIntent/watchdog)는 기존 계약 확인 항목으로 답변 완료. - - trex-tsl-v1 운영 모드 = **intraday, `safety_sell_enabled=true`**(core SL/TP watchdog backup + market_close EOD backstop 유지, 데몬이 primary 매도). 중장기 프로파일과 무관 — core가 backup으로 남으며 당일 재진입이 잦아 occupancy 가드 필요성이 큼. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - `docs/discovery/` 전체에서 `strategy_state`/per-strategy opaque store를 다루는 기존 Discovery가 없다(grep 결과 0건). 가장 가까운 dcy-025(strategy read surface — slots.req 노출면)는 슬롯 메타 read 확장이지 strategy 소유 상태 저장/CAS write/수명 삭제 계약이 아니다. - - C-2(version CAS write RPC)·C-3(close 시 삭제 불변식)는 신규 RPC와 신규 도메인 저장 개념을 도입하므로 기존 REQ/batch 산출물의 미세 조정 범위를 넘어선다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 슬롯 스냅샷 RPC(`trex.slots.req`)는 `trex_core/application/messaging_handlers.py`의 `build_slots_handler` + `slot_to_dict`(`trex_core/application/portfolio.py:250`)로 슬롯 행을 구성하며, strategy 소유 임의 상태를 실어 나르는 필드가 없다. - - 슬롯 도메인(`trex_core/domain/slot/models.py` `Slot`, `state.py`)에는 position/occupancy 필드만 있고 strategy opaque blob 저장소가 없다. 전략은 `evaluate()` 순수성 때문에 상태를 스스로 영속화할 수 없다. - - 슬롯 close는 영구 슬롯 모델(dcy-020/REQ-057, BAT-030)에서 **행 삭제가 아니라 `→ INACTIVE` 전이**다(`trex_core/domain/slot/service.py:174` — position/occupancy 필드 일괄 clear, asset 정체성 보존). 현재 이 전이에서 정리할 strategy_state 자체가 없다. - - 결과적으로 전략 데몬이 peak 등 누적 상태를 데몬 메모리에 두면 재시작/장애/failover 시 유실되어 stop line이 느슨해지는 실손실 위험이 있고, 별도 외부 스토어를 쓰면 orphan-cleanup 책임이 발생한다. -- 기대 상태 - - `(slot_id, strategy_id)`로 네임스페이스된 opaque state를 trex-core가 저장하고, 슬롯 스냅샷 응답에 round-trip(추가 왕복 없이 read), occupancy_seq + version CAS로 idempotent write, 슬롯 close(`→INACTIVE`) 시 자동 삭제한다. - - 전략/데몬은 cleanup 책임이 없고, 슬롯이 source of truth이자 GC가 된다. 재사용된 슬롯은 새 occupancy_seq + 빈 상태(version 없음)로 시작한다. -- 이해관계자 - - trex-tsl 매도 전략 데몬 팀 (제안자/주 소비자) - - trex-core 유지보수자 (계약·구현·검증 책임) - - 운영 담당자 (멀티데이 인스턴스 운영, dcy-027 long-term 프로파일 연계) -- 제약사항 - - trex-core는 `data`를 절대 파싱/검증/해석하지 않는다(opaque). schema는 전략 소유. - - 순수 AMQP 계약 통합만 — 데몬은 `trex_core`를 직접 import 하지 않는다. - - 단일 writer 전제(해당 slot·strategy를 운용하는 데몬 1개). version CAS는 재시도/중복 안전용이지 다중 writer 동시성 제어가 아니다. - - 영구 슬롯 모델 정합: 삭제는 행 삭제가 아니라 `→INACTIVE` 전이 훅에서 수행한다(dcy-020). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- TSL 데몬이 매 decision-cycle에 `trex.slots.req`로 슬롯과 함께 `strategy_state["tsl_v1"]`를 읽어 `evaluate()`의 read-only 입력으로 쓰고, 갱신된 peak 상태를 `trex.slots.strategy_state.set`(expected_version CAS)로 write 한다. -- 데몬이 재시작/장애로 메모리 상태를 잃어도 다음 사이클에 슬롯 스냅샷에서 peak를 복원해 stop line이 느슨해지지 않는다. -- 슬롯이 매도 체결로 close(`→INACTIVE`)되면 trex-core가 해당 슬롯의 모든 strategy_state를 삭제하고, 같은 슬롯이 재사용되면(occupancy_seq++) 전략은 빈 상태에서 다시 시작한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- C-1: 슬롯 스냅샷 RPC(`trex.slots.req`) 응답 각 슬롯 행에 optional `strategy_state` 맵을 추가(additive, 하위호환). 미보유 시 omit 또는 `{}`. -- C-2: 신규 RPC `trex.slots.strategy_state.set` — `(slot_id, strategy_id)` opaque value를 version CAS로 upsert. 기존 계약 패턴(direct reply-to RPC + JSON + `queue_name({base}.{instance_key})`) 준수. -- C-2b (occupancy_seq 경량 가드): 슬롯 행에 monotonic `occupancy_seq`(새 점유 시작 시 ++)를 추가하고 snapshot 행에 노출한다. `strategy_state.set`은 `expected_occupancy_seq`를 받아 **occupancy_seq 일치 + version 일치**를 함께 CAS 검증, 점유가 바뀌었으면 `occupancy_changed` reject. 점유 전환(read v0→close→reopen→write v0)의 ABA 누수를 구조적으로 차단(intraday 재진입 안전). full 3계층(occupancy 테이블 분할)은 후속 Deferred — 본 가드는 컬럼 1개로 정합성 임계부만 선취. -- C-3: 슬롯 close 시 strategy_state 삭제 불변식 — 영구 슬롯 모델 정합상 `→INACTIVE` 전이(`service.py:174`)에서 해당 slot_id의 모든 strategy_state 제거. -- (baseline) `docs/runtime-flows.md`에 strategy-state read/write/삭제 흐름 추가, `docs/project-structure.md`에 저장소 구조·계약 키 반영. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/messaging_handlers.py`(`build_slots_handler` 응답에 strategy_state + occupancy_seq 병합, 신규 strategy_state.set 핸들러 + occupancy_seq CAS) - - `trex_core/application/portfolio.py`(`slot_to_dict`에 strategy_state + occupancy_seq round-trip) - - 신규 저장 포트/리포지토리(예: `StrategyStateStore`)와 PG/in-memory 구현(`trex_core/adapters/persistence/postgresql.py`, `trex_core/domain/slot/in_memory_repository.py` 또는 별도 모듈) - - `trex_core/domain/slot/models.py`·`service.py`(슬롯에 `occupancy_seq` 필드 추가 + 새 점유 시작(`inactive→pending_buy`) 시 ++ + `→INACTIVE` 전이 시 strategy_state 삭제 훅), 스키마 마이그레이션(`occupancy_seq` 컬럼) 및 데몬 와이어링(`trex_core/runtime/daemon.py`) -- 문서 영향: - - `docs/runtime-flows.md`(strategy-state 흐름), `docs/project-structure.md`(저장소/계약 키), `docs/srs/index.md`(신규 REQ 등록) -- 운영 영향: - - 신규 RPC 라우팅 키/큐 추가 → 데몬·인프라 설정 반영. PG 사용 시 신규 테이블/스키마 마이그레이션. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1 (C-1): `trex.slots.req` 응답 각 슬롯 행에 optional `strategy_state` 맵(`{: {version:int, data:str, updated_at:str}}`)과 **`occupancy_seq`(int)**를 추가한다. 미보유 시 omit/`{}`. 기존 소비자는 unknown 필드 무시로 하위호환. 맵은 별도 `slot_strategy_state` 테이블(OQ-4)에서 반환 slot_id들로 batched 조회 후 group by 해 조립한다(슬롯 본체 스키마 불변; occupancy_seq는 슬롯 행 컬럼). -- FR-2 (C-2): 신규 RPC `trex.slots.strategy_state.set`로 `(slot_id, strategy_id)` opaque `data`를 **occupancy_seq + version CAS** upsert 한다. `expected_occupancy_seq`가 슬롯 현재 occupancy_seq와 일치하고 `expected_version`이 stored version과 일치 → commit 후 version+1. 점유 불일치 → `{ok:false, reason:"occupancy_changed", occupancy_seq:}` (저장 안 함; 데몬 re-read 후 fresh restart). version 불일치 → `{ok:false, reason:"version_conflict", version:}`. slot inactive/missing → `{ok:false, reason:"slot_inactive"}` (저장 안 함). 최초(없음 + expected_version=0, occupancy_seq 일치) → version=1 생성. 동일 `correlation_id` 재전송은 부작용 없음. `data`는 opaque UTF-8 문자열로 받아 (1) 유효 UTF-8 + (2) 크기 상한(NFR-2/OQ-1)만 검증하고 그대로 저장하며, 초과 시 `{ok:false, reason:"state_too_large"}`로 reject 한다(저장 안 함). malformed 페이로드는 `{ok:false, reason:"bad_request:"}`. **reject reason 전체 집합 = {`occupancy_changed`, `version_conflict`, `slot_inactive`, `state_too_large`, `bad_request`}**, 데몬은 미인식 reason에 fail-closed. -- FR-2b (C-2b, occupancy_seq 가드): 슬롯에 monotonic `occupancy_seq` 필드를 두고 **새 점유 시작(`inactive→pending_buy`) 시 ++**, snapshot 행(FR-1)에 노출한다. read v0→close→reopen→write v0 의 ABA 누수를 FR-2 occupancy CAS로 구조적으로 차단한다. (full occupancy 테이블 분할은 Out of Scope — 컬럼 1개로 정합성 임계부만 선취.) -- FR-3 (C-3): 슬롯이 어떤 경로로든 close(`→INACTIVE`)되면 해당 slot_id의 모든 strategy_state를 삭제한다. 재사용 슬롯은 새 occupancy_seq + 빈 상태(version 없음)로 시작한다. 삭제 즉시성 통지는 **신규 이벤트를 만들지 않고**(OQ-3 확정), 데몬은 다음 `slots.req` 스냅샷의 state 부재(또는 occupancy_seq 변화)로 감지한다; 즉시성이 필요하면 이미 발행되는 `slot_filled_sell`(status=inactive) transition 이벤트를 재사용한다(필요시 `strategy_state_cleared` additive 플래그). -- FR-4 (baseline): `docs/runtime-flows.md`에 strategy-state read/write/삭제 흐름·occupancy_seq 가드·영구 슬롯 close 훅 정합을, `docs/project-structure.md`에 저장소 구조·계약 키를 반영한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1 (불간섭/opaque): trex-core는 `data`를 파싱/검증/해석하지 않는다. 충돌/만료/해석은 전적으로 trex-core 밖이다. -- NFR-2 (인코딩/크기 한계, OQ-1 확정): `data`는 core가 해석하지 않는 **opaque UTF-8 문자열**이다 — JSON/base64를 계약 레벨에서 강제하지 않으며(base64 기본 강제는 ~33% 팽창·무이득), core 검증은 (1) 유효 UTF-8 + (2) 크기 상한(byte 기준) 두 가지뿐이다. 상한은 **2단** — TSL 등 알려진 용례 기본 **1~2KB**, **hard ceiling 4KB**. 초과 시 명시적 reject(`state_too_large`). C-1 read piggyback상 스냅샷 응답 크기 ≈ `max_active_slots × cap` 이므로 상한은 슬롯당 저장이 아니라 폴링 응답 크기 관점에서 정한다. -- NFR-3 (수명 정합): strategy_state는 슬롯 수명 동안만 존재하고 장기 영속/감사 대상이 아니다. read는 기존 스냅샷 RPC에 round-trip 되어 추가 왕복이 없고 watchdog 갱신을 깨지 않는다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - C-1 read 확장(strategy_state 맵 + occupancy_seq), C-2 write RPC, C-2b occupancy_seq 경량 가드, C-3 close 삭제 불변식, baseline 문서 정합 - - occupancy_seq + version CAS 의미(occupancy_changed/version_conflict/slot_inactive/state_too_large/bad_request/first-create/idempotent) -- Out of Scope - - 다중 writer 동시성 제어(단일 writer 전제) - - strategy_state 장기 보존/감사/이력 저장(별도 논의) - - C-4(SellIntent/watchdog) 신규 구현 — 기존 계약 확인 항목으로 issue #26 코멘트에서 답변 완료 - - **full 3계층 슬롯 구조 분할**(별도 `asset_slot`/`slot_occupancy` 테이블, strategy_state를 occupancy_id 키로 전환) — 후속 "영구 슬롯 구조 정합" Discovery로 Deferred. `daily_slot` 네이밍 정합·`daily_symbol_entries` 잔재 제거도 함께. (정합성 임계부인 occupancy_seq 가드는 본 batch 포함 — C-2b. 중장기 영구 슬롯엔 점유 회전이 드물어 full 분할은 과함; 가드 컬럼 1개로 충분.) - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (High→완화): 점유 전환 ABA 누수 — read v0→close→reopen→write v0 시 X가 계산한 상태가 새 점유 Y에 바인딩(잘못된 stop line, 안전 관련). version CAS(0 vs 0)·slot_inactive(write 시 Y로 이미 active)로는 못 잡음. intraday Mode A(당일 재진입)에서 실재(trex-tsl 팀 #26 지적). - - 완화 방안: **occupancy_seq 경량 가드(C-2b/FR-2b)** — 새 점유마다 occupancy_seq ++, write가 `expected_occupancy_seq`로 점유 일치를 server-side CAS 검증 → 다른 점유면 `occupancy_changed` reject. ABA를 구조적으로 차단. + `→INACTIVE` 단일 전이 지점 삭제 훅(모든 close 경로 커버)·테스트로 보강. -- R-2 (Medium): write RPC race/중복 전달로 상태 덮어쓰기. - - 완화 방안: occupancy_seq + version CAS + `correlation_id`("{slot_id}:{strategy_id}:{expected_version}") idempotency. 단일 writer 전제 문서화. -- R-3 (Low): PG 스키마 추가/마이그레이션 운영 부담(별도 `slot_strategy_state` 테이블 + 슬롯 `occupancy_seq` 컬럼). - - 완화 방안: opaque blob 별도 테이블 + occupancy_seq 컬럼 1개의 최소 스키마(JSONB-on-row는 OQ-4에서 기각). in-memory 구현 병행으로 paper/테스트 무중단. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1 (OQ-4로 확정): strategy_state는 별도 `slot_strategy_state` 테이블 + 맵(복합 PK `(slot_id, strategy_id)`) + slot_id 키. 슬롯 본체(`daily_slot`, 레거시 명 — 실제는 영구 자산 슬롯)에는 정합성 가드용 `occupancy_seq` 컬럼 1개만 추가(full 테이블 분할 없음). -- A-2 (OQ-2로 확정): read는 기존 `trex.slots.req`에 piggyback, write는 별도 신규 RPC `trex.slots.strategy_state.set`. (가정 → 확정) -- A-3: 영구 슬롯 모델(dcy-020) 정합상 삭제는 행 삭제가 아니라 `→INACTIVE` 전이 훅이다(issue #26 코멘트에서 데몬 팀에 이미 고지). - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1 (OQ-A): `data` 인코딩을 JSON 문자열로 할지 base64 바이트로 할지, 수용 가능한 크기 상한(제안 4KB)은 얼마인지. - - 상태: Answered (2026-06-15, Justin Ko) - - 결정: **opaque UTF-8 문자열 + 2단 상한**. JSON/base64를 계약 레벨에서 강제하지 않고 core는 유효 UTF-8 + byte 크기 상한만 검증(NFR-1 정합). 상한은 TSL 등 알려진 용례 기본 1~2KB, hard ceiling 4KB(byte 기준). 초과 시 `state_too_large` 명시적 reject. C-1 스냅샷 응답 크기(`max_active_slots × cap`) 관점에서 상한을 정한다. - - 반영 위치: NFR-2(인코딩/크기 한계), FR-2(write 검증·reject 사유). -- OQ-2 (OQ-B): 신규 RPC(`trex.slots.strategy_state.set`) vs 기존 메시지(heartbeat/intent) piggyback 중 어느 write 방식을 채택할지. - - 상태: Answered (2026-06-15, Justin Ko) - - 결정: **write = 별도 RPC `trex.slots.strategy_state.set`(C-2), read = `trex.slots.req` piggyback(C-1) 유지**. piggyback 기각 근거 — (1) write(거의 매 사이클)와 sell intent(드묾)의 빈도 불일치, (2) trex-core에 dedicated heartbeat 부재 + watchdog는 `slots.req`가 모든 holding 슬롯에 매 사이클 갱신해야 해 조건부 write와 합치면 커버리지 붕괴, (3) read RPC에 write를 얹으면 멱등성·`spy` 의미 붕괴, (4) CAS 응답/재시도 격리, (5) trex-core가 이미 concern별 개별 RPC 패턴이라 추가 비용 낮음. read는 어차피 폴링하는 `slots.req`에 공짜로 얹히므로 비대칭(read piggyback / write 분리)이 의도된 설계. - - 반영 위치: FR-1(C-1 read piggyback), FR-2(C-2 별도 write RPC). A-2 가정 → 확정. - - 비고: 사이클당 다중 슬롯 write 시 N개 set RPC 발생 — optional batch 변형은 v1 범위 밖 후속 최적화. -- OQ-3 (OQ-C): close 시 strategy_state 삭제를 slot-transition 이벤트(`trex.events.strategy.*`)로 노출할지(데몬 즉시 반응 가능). - - 상태: Answered (2026-06-15, Justin Ko) - - 결정: **신규 전용 이벤트 도입하지 않음.** 정합성은 (1) 스냅샷 부재(`slots.req`에서 strategy_state 없음 → 데몬 `expected_version=0` first-create) + (2) C-2 CAS rule(닫힌 슬롯 write는 `slot_inactive` reject로 부활 차단)가 보장한다. 즉시성이 필요하면 **이미 발행되는 `slot_filled_sell`(status=inactive) transition 이벤트를 재사용**(모든 매도 close가 execution loop `pending_sell→INACTIVE` 경로로 이 이벤트를 emit; best-effort). 정 필요하면 그 payload에 `strategy_state_cleared: true` **additive 필드**만 추가 — 새 event_type/route 아님. 전용 state-cleared 이벤트는 구체적 필요 발생 시 후속 반복으로 Deferred. - - 근거: 정합성이 best-effort 이벤트에 의존하지 않도록(source of truth = 스냅샷), 신규 event_type의 라우팅/바인딩/DLQ 비용 회피, transition 이벤트·스냅샷 부재와의 중복 제거. - - 반영 위치: FR-3(삭제 불변식 — 이벤트 노출은 옵션, 기존 transition 이벤트 재사용 명시), NFR-3(수명 정합). -- OQ-4 (OQ-D): `strategy_state`를 슬롯당 다중 strategy 맵으로 둘지 단일 객체로 둘지 + 저장 모델(별도 테이블 vs 슬롯 행 JSONB). - - 상태: Answered (2026-06-15, Justin Ko) - - 결정: **별도 `slot_strategy_state` 테이블 + 맵 + `slot_id` 키.** ① shape = 맵(strategy_id 네임스페이스, 계약 키 `(slot_id, strategy_id)`로 강제) — 한 슬롯이 `strategy_id`(매수)·`sell_strategy_id`(매도)를 동시 보유 가능하므로 단일 객체는 충돌. ② 저장 = 별도 테이블(JSONB-on-row 기각): CAS가 `UPDATE ... WHERE version=?` rowcount / `INSERT ON CONFLICT`로 race-safe, hot 슬롯 행(`daily_slot`) lean 유지, write 경합 분리, C-3 삭제 `DELETE WHERE slot_id=?` 한 문장. 테이블 PK = 복합 `(slot_id, strategy_id)`, strategy당 1행. C-1 read는 반환 slot_id들로 batched 조회 후 group by 해 맵 조립. in-memory repo는 복합키 dict로 미러. **참조 방향은 strategy_state → slot(테이블이 slot_id를 컬럼으로 보유), slot 본체 스키마 불변.** - - 키 선택(slot_id vs occupancy_id): **strategy_state 테이블은 slot_id 키 유지 + occupancy_seq 경량 가드(C-2b) 추가.** (2026-06-15 갱신, trex-tsl #4705990067 지적 반영) 당초 "occupancy 식별자 전면 deferred"는 점유 전환 ABA 누수(R-1)를 못 막음이 확인되어 수정 — 정합성 임계부인 occupancy 식별만 **슬롯 행 occupancy_seq 컬럼 1개**로 v1에 선취하고, write CAS가 `expected_occupancy_seq`로 점유 일치를 검증한다. **full 3계층(별도 occupancy 테이블 + strategy_state를 occupancy_id 키로 전환)만 Deferred** — 중장기 영구 슬롯은 점유 회전이 드물어 full 분할이 과하고, intraday(trex-tsl)가 요구하는 정합성은 가드 컬럼으로 충족. strategy_state 격리 테이블이라 추후 full 분할 시 seq→id 흡수도 contained. - - 반영 위치: FR-1(read 맵 조립), FR-2(CAS 저장), FR-2b/C-2b(occupancy_seq 가드, In Scope), FR-3(close 삭제), §4 영향 범위(별도 StrategyStateStore/테이블), 범위 경계(full 3계층만 Out of Scope), A-1 확정. - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 데몬이 `trex.slots.req`로 슬롯과 함께 strategy_state·occupancy_seq를 read하고, `trex.slots.strategy_state.set`으로 write한 뒤, 같은 슬롯의 다음 스냅샷에서 version+1과 갱신된 data가 보인다. -- S-2: CAS 의미(occupancy match+version match commit / version_conflict / occupancy_changed / slot_inactive / state_too_large / first-create)와 idempotent 재전송 무부작용이 테스트로 검증된다. -- S-3: 슬롯이 모든 close 경로(전략 매도 체결/safety-sell/forced-close)로 `→INACTIVE` 전이 시 strategy_state가 삭제되고, 재사용 슬롯이 새 occupancy_seq + 빈 상태(version 없음)로 시작한다. -- S-4: 점유 전환 ABA 시나리오(read v0 → close → reopen(occupancy_seq++) → 옛 점유 기준 write) 가 `occupancy_changed`로 reject 되어 새 점유에 stale 상태가 바인딩되지 않음이 테스트로 검증된다. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: read/write/재read end-to-end 통합 테스트 / 데이터 출처: messaging_handlers 핸들러 테스트, slots.req 응답 페이로드 -- S-2 측정 방식: strategy_state.set 핸들러 단위 테스트(6 케이스: occupancy+version commit/version_conflict/occupancy_changed/slot_inactive/state_too_large/first-create + 중복 재전송) / 데이터 출처: 신규 핸들러/스토어 테스트 -- S-3 측정 방식: 각 close 경로 후 strategy_state 부재 + occupancy_seq 증가 검증 테스트 / 데이터 출처: slot service `→INACTIVE`/`inactive→pending_buy` 전이 테스트, 스토어 상태 -- S-4 측정 방식: 점유 전환 race 단위 테스트(occupancy_seq mismatch → occupancy_changed) / 데이터 출처: strategy_state.set 핸들러/스토어 테스트 - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: C-1/C-2/C-2b/C-3 + baseline 포함, C-4·다중writer·장기보존·full 3계층 분할 제외 합의 — confirm-discovery 확정. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 테스트 가능 형태로 확정 (§7 측정 방식 참조) -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(점유 전환 ABA, High) → occupancy_seq 가드(C-2b/FR-2b)로 완화안 채택 확정. R-2/R-3 Medium/Low. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 - - 결과: OQ-1~4 전부 Answered (2026-06-15). full 3계층 분할만 후속 Discovery로 Deferred(occupancy_seq 가드는 v1 포함). -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: confirm-discovery 시 승인자 지정 — Justin Ko (2026-06-15) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-028_20260615_slot-strategy-state-store.md -- 참조 문서: - - GitHub issue #26 (Slot Strategy-State 계약 제안) + C-4 확인 코멘트 + OQ 결정 코멘트 + occupancy race 피드백/답변(#4705990067, #4706137866) - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (영구 슬롯/→INACTIVE 모델) - - docs/discovery/dcy-025_20260611_strategy-read-surface-slots-req.md (slots.req read surface) - - docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md (영구 슬롯/long-term 프로파일 연계) - - stage-pilot/templates/project-structure.md, stage-pilot/templates/runtime-flows.md - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- ~~`data` 인코딩 + 크기 상한 — OQ-A~~ → **해결됨(2026-06-15)**: opaque UTF-8 문자열 + 2단 상한(기본 1~2KB / hard ceiling 4KB), 초과 시 `state_too_large` reject. (OQ-1 참조) -- ~~write 경로(별도 RPC vs piggyback) — OQ-B~~ → **해결됨(2026-06-15)**: write = 별도 RPC(C-2), read = slots.req piggyback(C-1) 유지. (OQ-2 참조) -- ~~슬롯당 다중 strategy 맵 vs 단일 객체 + 저장 모델(테이블 vs JSONB) — OQ-D~~ → **해결됨(2026-06-15)**: 별도 `slot_strategy_state` 테이블 + 맵(복합 PK `(slot_id, strategy_id)`) + slot_id 키, occupancy_id 후속 Deferred. (OQ-4 참조) - -## CONFIRM -- ~~close 시 strategy_state 삭제를 slot-transition 이벤트로 노출할지 — OQ-C~~ → **해결됨(2026-06-15)**: 신규 이벤트 없음, 스냅샷 부재 + CAS 의존, 기존 `slot_filled_sell` 옵션 재사용. (OQ-3 참조) -- 삭제 훅을 영구 슬롯 `→INACTIVE` 전이로 두는 설계(행 삭제 아님) — A-3·FR-3로 확정, 데몬 팀 고지 완료. confirm-discovery 승인 시 함께 확정. - -## DATA -- ~~PG 컬럼/메시지 크기 기준 실수용 가능한 data 상한 값~~ → **확정(OQ-1)**: 기본 1~2KB / hard ceiling 4KB(byte 기준) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~4 전부 Answered(2026-06-15) — 인코딩(opaque UTF-8+2단 상한)/write 경로(별도 RPC)/삭제 이벤트(신규 없음)/저장 구조(별도 테이블+맵+slot_id 키 + occupancy_seq 경량 가드) 확정. trex-tsl 팀 점유 ABA 지적 반영(C-2b). full 3계층 분할만 후속 Discovery로 Deferred. 범위·성공기준·저장소 사실관계 확인 완료, C유형 placeholder·Open OQ 0개 → REQ 초안 작성 진행 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-15 17:52 diff --git a/docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md b/docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md deleted file mode 100644 index 52574cf..0000000 --- a/docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md +++ /dev/null @@ -1,237 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-029_20260616_operational-observability-logging-and-pnl-view -- 이슈명: 운영 관측성 — 주문 결과 로깅 + 로그 타임스탬프 + 청산사유 분류 + 슬롯/PnL 조회 + 운영자 런북 (issue #29) -- 작성 시각(KST): 2026-06-16 10:52 -- 마지막 갱신 시각(KST): 2026-06-16 12:55 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-084, REQ-085, REQ-086, REQ-087, REQ-088, REQ-089, REQ-090 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #29 (P0: 1·2 / P1: 3·4·5), 관련 issue #28(호가단위 거부 — 별도 정확성 버그 트랙) -- 해석: - - 2026-06-15/16 라이브 테스트에서 trex-core의 운영 가시성 부족이 진단을 막았다. 매수 거부(#28)가 로그에 없고 `slot_transition_log` DB에만 남아 수 시간 동안 인지하지 못했고, 로그에 타임스탬프가 없어 타임라인 재구성이 불가능했다. - - 이번 Discovery는 "거래 실패를 즉시·정확히 관측"하기 위한 로깅/분류/조회 표면을 정의한다. 청산사유 분류·조회 뷰가 런타임 흐름과 read 표면을 바꾸므로 minor-change가 아니라 정식 Discovery로 다룬다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 Discovery(dcy-001~028)는 슬롯 도메인·전송·브로커·안전매도·자산중심 슬롯·전략 read 표면을 다뤘고, "운영 관측성(로깅/타임스탬프/청산사유 분류/PnL 조회)"을 정면으로 다룬 주기는 없다. dcy-016/dcy-018(슬롯 상태 가시화·운영자 CLI)은 슬롯 상태 조회에 한정돼 주문 결과 로깅·PnL 집계와 범위가 다르다. - - issue #29는 신규 운영 환류(라이브 테스트 인사이트)에서 출발한 독립 주제로, 후속 반복을 독립 주기로 추적하는 편이 책임 경계가 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - 로그 포맷: `trex_core/runtime/daemon.py:408`·`trex_core/runtime/reset.py:129`가 `logging.basicConfig(level=logging.INFO)`만 호출해 **format 문자열이 없다** → 모든 로그 라인에 타임스탬프·레벨 prefix가 없다. - - 주문 결과 가시성: `trex_core/application/execution.py`의 submit/poll 경로는 산발적 `_log.debug`/`_log.warning`만 남기고, 주문 submit + 결과(체결가/수량/수수료/세금 또는 거부+사유)를 일관된 tailable 로그로 남기지 않는다. `_rollback()`은 거부 시 슬롯을 **조용히** 전이(`slot_transition_log`에만 기록)하고 ERROR 로그를 남기지 않아 호가단위 거부(#28)가 로그에 보이지 않았다. - - 청산사유 분류: `trex_core/application/safety_sell.py`·`trex_core/application/watchdog.py` 양쪽이 매도를 일으키는데, watchdog 청산이 `stop_loss`로 뭉뚱그려져 "진짜 손절"로 오해를 유발했다(2026-06-15 064400 사례). - - 조회 표면: 운영자 CLI(`trex_core/runtime/cli.py`)는 `slots/universe/pause/enter/exit/cancel`만 제공하고, 보유/오늘 체결/실현 PnL(수수료·세금 반영)/대기 주문을 한눈에 보는 뷰가 없다. 일일 실현 PnL을 `ledger_outbox`에서 수기 계산해야 했다. 데몬은 RabbitMQ 전용(BAT-007)이라 HTTP 표면이 없다. - - baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`)는 존재하며 최신(2026-06-15 갱신). 단, 로깅/관측성·PnL 조회 흐름은 아직 기술돼 있지 않다. -- 기대 상태 - - 모든 로그 라인에 ISO8601(KST) 타임스탬프 + 레벨이 붙고, 모든 주문 submit/결과/거부가 타임스탬프 달린 일관 로그(Loki 적재용 구조화 JSON + correlation id)로 즉시 관측된다. - - 청산이 기존 reason 어휘(`stop_loss`/`take_profit`/`forced_close_at`/`market_close_liquidation`)와 actor(`safety_sell`/`strategy`/`execution_loop`)를 함께 노출해, "안전망 자율청산"과 "전략 손절"이 구분 기록된다. - - 운영자가 보유·오늘 체결·실현 PnL(수수료·세금 반영)·대기 주문을 단일 CLI 명령으로 조회할 수 있어 일일 리포트 자동화의 기반이 된다. - - 주문 거부가 조용히 묻히지 않고 ERROR 로그로 즉시 인지된다(알림 전송 채널은 후속 반복으로 Deferred). -- 이해관계자 - - 운영 담당자(trex 운영팀): 실패를 즉시 인지하고 일일 리포트를 자동화해야 하는 1차 수요자. - - 문서 작성자(AI agent): Discovery/REQ/Batch 산출물 작성·구현·검증. - - 리뷰어/승인자(Justin Ko): 범위·계약·우선순위 확정. -- 제약사항 - - 데몬은 HTTP 표면이 없다(BAT-007, RabbitMQ-only). "조회 endpoint"는 운영자 CLI 확장 또는 RabbitMQ RPC로 구현해야 하며 HTTP 신설은 아키텍처 역행이다. - - 청산사유 reason 값은 전략/운영자가 읽는 계약 표면(`slots.req`, 이벤트, `slot_transition_log`)에 노출되므로 어휘 변경 시 하위호환을 고려해야 한다. - - 코어는 알림 전송 채널의 1차 소유자가 아니다(ERROR 로그는 코어, 전송은 운영 인프라/외부). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자가 거래일 중 `tail -f`로 trex-core 로그를 보며 매수 주문이 호가단위 오류로 거부되는 것을 **즉시** 발견하고(타임스탬프+ERROR), #28 같은 문제를 몇 시간이 아니라 즉시 인지한다. -- 운영자가 장 마감 후 단일 명령/RPC로 "오늘 체결 N건, 실현 PnL(수수료·세금 반영) X원, 현재 보유 M종목, 대기 주문 K건"을 조회해 일일 리포트를 자동 생성한다. -- 운영자가 슬롯 청산 기록을 보고 그것이 watchdog 강제청산인지 진짜 손절(stop_loss)인지 사유로 즉시 구분한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 로깅 기반(P0): 전 로그 라인에 ISO8601(KST) 타임스탬프 + 레벨 prefix 부여, 모든 주문 submit/결과(체결가·수량·수수료·세금 또는 거부+사유)를 타임스탬프 달린 일관 로그로 출력, Loki 적재용 구조화(JSON) 출력 + correlation id(intent→order→fill) 전파. -- 의미론·가시성(P1): 청산을 기존 reason 어휘 + actor를 함께 노출해 안전망/전략을 구분 기록(신규 필드 없음), 주문 거부 시 조용한 롤백 대신 ERROR 로그(알림 전송 채널은 Deferred). -- 조회 표면(P1): 보유·오늘 체결·실현 PnL(수수료·세금 반영)·대기 주문 조회 뷰를 운영자 CLI 서브커맨드로 추가(DB-backed, RPC 없음). -- baseline 문서 갱신: `docs/runtime-flows.md`에 로깅/관측성·청산사유 분류·PnL 조회 흐름을 추가하고, 신규 모듈(로깅 포맷/주문결과 로깅/PnL 집계)이 생기면 `docs/project-structure.md`에 책임을 반영. -- 운영자 런북 문서(P1): `docs/operations.md` 신설 — 배포/실행·운영자 CLI 사용법·로깅/관측성 사용법·디버깅/트러블슈팅. `run-trex-core-daemon` SKILL의 사실 자료 추출 + 본 반복 신설 관측성 기능 사용법 통합. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/runtime/daemon.py`·`trex_core/runtime/reset.py`: 로깅 포맷(타임스탬프+레벨, KST) 설정. - - `trex_core/application/execution.py`: 주문 submit/결과/거부 일관 로깅, `_rollback()` 거부 시 ERROR 로그. - - `trex_core/application/safety_sell.py`·`trex_core/application/watchdog.py`(+ `trex_core/domain/slot/service.py` reason 전파): 청산사유 분류. - - 신규/확장: 주문결과 로깅 헬퍼, PnL 집계 read(`ledger_outbox`/체결 집계), 조회 CLI 서브커맨드(`trex_core/runtime/cli.py`). - - 구조화 출력(Loki JSON) + correlation id(intent→order→fill) 전파 (FR-7, In Scope). -- 문서 영향: - - `docs/runtime-flows.md`(로깅/관측성·PnL 조회 흐름 추가), `docs/project-structure.md`(신규 모듈 책임), `docs/interface-contract.md`(조회 CLI·청산사유 reason+actor 어휘·거부 로그 계약). -- 운영 영향: - - 로그 포맷 변경으로 기존 로그 파싱(있다면) 조정 필요. 조회 뷰/PnL 집계로 일일 리포트 자동화 가능. (알림 채널은 이번 범위 밖.) - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1 (P0): 모든 로그 라인에 ISO8601 타임스탬프(KST) + 로그 레벨 prefix를 부여한다(현재 prefix 없음). -- FR-2 (P0): 모든 주문 submit과 그 결과(체결가·체결수량·수수료·세금, 또는 거부+사유)를 타임스탬프 달린 일관(tailable) 로그 라인으로 출력한다. 호가단위 같은 거부가 즉시 보여야 한다. -- FR-3 (P1): 청산 사유를 **두 축**으로 정확히 구분 노출한다(OQ-4 결정 = Option A: 신규 필드 없음). (1) 조건축 = 기존 `reason` 어휘 `{stop_loss, take_profit, forced_close_at, market_close_liquidation}`를 계약으로 고정, (2) 주체축 = 기존 `actor`(`safety_sell`=안전망 watchdog 소유 자율청산 vs `strategy`=전략 자율매도 vs `execution_loop`)를 reason과 **함께** 로그/`slot_transition_log`/조회 뷰에 노출. 현재 stop_loss/take_profit/forced_close_at는 모두 watchdog expired(전략 침묵→안전망 소유)에서만 발화하므로, actor를 같이 보여야 "안전망이 SL선에서 청산"과 "전략이 손절"을 구분할 수 있다(2026-06-15 064400 오해 원인). 신규 reason 값·신규 분류 필드는 추가하지 않는다. -- FR-4 (P1): 주문 거부 시 조용한 inactive/holding 롤백 대신 ERROR 레벨 로그를 남긴다(알림 전송 채널은 Deferred, OQ-3). 반복 거부를 즉시 인지할 수 있어야 한다. -- FR-5 (P1): 현재 보유·오늘 체결·오늘 실현 PnL(수수료·세금 반영)·대기 주문을 조회하는 운영 뷰를 **운영자 CLI 서브커맨드**로 제공한다(OQ-1 결정). 조회는 `ledger_outbox`/slots를 권위 출처로 한 DB-backed 시점 집계이며, RPC는 추가하지 않는다. 일일 리포트 자동화의 기반. -- FR-6: 이 변경이 런타임 흐름/구조를 바꾸므로 `docs/runtime-flows.md`(로깅/관측성·PnL 조회 흐름)와 필요 시 `docs/project-structure.md`(신규 모듈 책임)를 갱신한다. -- FR-7 (P0): 로그를 **Loki 적재용 구조화(JSON) 출력**으로 내보내고, correlation id(intent→order→fill)를 전파해 한 거래의 인텐트·주문·체결 로그를 단일 키로 묶는다(OQ-2 결정, 이번 범위 In Scope). FR-1·2·3·4의 로그가 이 구조화 스트림으로 흐른다. -- FR-8 (P1, Documentation): 운영자용 런북 문서(`docs/operations.md`)를 신설한다. 배포/실행(env 변수·`--config`·`--env-file`), 운영자 CLI 사용법(`pause/resume/slots/universe/enter/exit/cancel` + 본 반복 신설 PnL/조회 서브커맨드), 로깅·관측성 사용법(타임스탬프·주문결과 로그 grep·Loki/correlation id·청산 reason+actor 해석), 디버깅·트러블슈팅(거부 ERROR 확인, 미체결 진단, DB 상태 조회)을 포함한다. 기존 `.claude/skills/run-trex-core-daemon/SKILL.md`의 사실 자료를 사람용 문서로 추출·정리하고, FR-1~7로 추가되는 관측성 기능의 운영 사용법을 함께 기술한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1 (운영성): 로그 타임스탬프는 KST 기준 ISO8601로 일관되며, 주문 결과 로그는 `grep`/`tail`로 거부·체결을 즉시 필터링할 수 있는 안정적 키(예: 종목·사유·intent_id)를 포함한다. -- NFR-2 (하위호환): 청산사유 노출 방식 개선(기존 reason 어휘 고정 + actor 동반 노출, 신규 필드 없음)과 조회 표면 추가는 기존 `slots.req`/이벤트/CLI 동작을 깨지 않는 additive 변경을 원칙으로 한다. -- NFR-3 (정확성): PnL 집계는 BAT-038(매도 증권거래세+수수료) 정책과 일치하는 수수료·세금 반영 값을 사용하며 `ledger_outbox`/체결 기록을 단일 출처로 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - FR-1·FR-2(P0 로깅 기반), FR-3·FR-4(P1 분류·거부 가시성), FR-5(P1 조회 뷰), FR-6(baseline 문서 갱신), FR-7(P0 Loki 구조화 출력 + correlation id), FR-8(P1 운영자 런북 `docs/operations.md`). - - FR-5 조회 표면 = 운영자 CLI 서브커맨드(DB-backed). RPC는 추가하지 않음(OQ-1 결정). - - FR-7 Loki 구조화(JSON) 출력 + correlation id(intent→order→fill) 전파를 이번 범위에 포함(OQ-2 결정). -- Out of Scope - - issue #28(호가단위 tick-size 스냅) — 별도 정확성 버그 트랙. 본 Discovery는 그 거부를 "관측"하는 범위만 다룬다. - - FR-5 조회용 RabbitMQ RPC 표면 — 이번 반복 제외(CLI로 충분, OQ-1 결정). - - 거부 시 알림 "전송 채널" 구현(claude-bus/외부 webhook 등) — Deferred. 이번 범위는 FR-4의 ERROR 로그까지만(OQ-3 결정). 후속 반복/이슈로 인계. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Low): 청산사유 노출 방식 변경(FR-3)이 reason 계약을 바꿔 다운스트림 소비자(trex-tsl/trex-orb, 운영 리포트)에 영향을 줄 수 있다. (OQ-4=Option A로 신규 reason 값·필드 없음 → 리스크 Low로 하향) - - 완화 방안: 기존 reason 값 유지(어휘 고정만), actor는 이미 존재하는 필드를 함께 노출하는 additive 변경. `docs/interface-contract.md`에 reason+actor 어휘를 명문화, 표시 방식 변경은 issue 코멘트로 전략팀 통지. -- R-2 (Medium): 로그 포맷 변경(FR-1)이 기존 로그 수집/파싱 파이프라인(있다면)을 깨거나, KST 타임스탬프 도입이 컨테이너 TZ 설정에 의존해 불일치할 수 있다. - - 완화 방안: TZ는 코드에서 명시적으로 KST(Asia/Seoul) 변환, 환경 TZ 비의존. 포맷 변경 영향은 운영팀과 사전 합의. -- R-3 (Low): PnL 집계(FR-5)가 `ledger_outbox`/체결 기록의 부분 상태(미정산·재시도 중)와 수수료·세금 정책 차이로 수기 계산과 어긋날 수 있다. - - 완화 방안: BAT-038 정책과 동일 계산 지점 재사용, "오늘" 경계는 trading_date(KST) 기준, 집계 시점의 outbox 상태를 명시. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 조회 표면은 HTTP가 아니라 운영자 CLI 서브커맨드로 구현한다(OQ-1 결정, 데몬 RabbitMQ-only 제약). -- A-2: P0(FR-1·2·7)와 P1(FR-3·4·5·8)은 별도 batch로 분리해 P0를 먼저 배포할 수 있다(CONFIRM 확인됨). FR-8 운영자 런북은 관측성 기능 사용법을 함께 문서화하므로 P1에 둔다(정확한 batch 분할은 suggest-batch-reqs/draft-batch에서 확정). -- A-3: PnL·수수료·세금 계산은 BAT-038(매도세+수수료) 정책을 단일 출처로 재사용한다. -- A-4: 배포 환경이 Loki 로그 수집을 사용한다(OQ-2 결정의 전제). 구조화(JSON) 출력은 Loki 미사용 환경에서도 stdout tail로 동작하므로 비파괴적이다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. - -- OQ-1: FR-5 조회 표면을 운영자 CLI 서브커맨드 확장으로 할지, 신규 RabbitMQ RPC로 할지, 둘 다 제공할지. (HTTP는 제외) - - 상태: Answered (2026-06-16, Justin Ko) - - 결정: **운영자 CLI 서브커맨드(DB-backed)** 로 제공. RPC는 추가하지 않음. → FR-5 및 §5 범위에 반영. - - 종료 조건: 조회 표면 형태가 In Scope에 단일 값으로 고정되면 Answered. (충족) -- OQ-2: 기능 6(Loki 구조화 JSON 출력 + correlation id intent→order→fill 전파)을 이번 범위에 포함할지, 후속으로 Deferred할지. - - 상태: Answered (2026-06-16, Justin Ko) - - 결정: **이번 범위 In Scope.** 구조화(JSON) 출력 + correlation id 전파를 FR-7로 신설. FR-1·2·3·4 로그가 이 스트림으로 흐름. 배포 환경이 Loki를 사용한다는 전제(A-4)로 진행. - - 종료 조건: 범위 포함/Deferred가 확정되면 Answered. (충족) -- OQ-3: FR-4 거부 알림에서 ERROR 로그 외 "알림 전송 채널"(claude-bus/외부 webhook 등)을 이번 범위에 포함할지. - - 상태: Answered (2026-06-16, Justin Ko) - - 결정: **Deferred.** 이번 범위는 FR-4의 ERROR 로그까지만. 알림 전송 채널은 후속 반복/이슈로 인계(처리 방안: 차기 Discovery 입력으로 등록). → §5 Out of Scope에 반영. - - 종료 조건: 알림 전송 범위가 확정되면 Answered. (Deferred로 종료) -- OQ-4: FR-3 청산사유 분류를 신규 reason 값 추가(`watchdog_liquidation` 등)로 할지, 기존 reason 필드 + 보조 분류 필드로 할지. (계약 하위호환 방식) - - 상태: Answered (2026-06-16, Justin Ko) - - 결정: **Option A — 기존 reason 어휘를 계약으로 고정 + 기존 actor와 함께 노출. 신규 reason 값·신규 분류 필드 없음.** 근거: 코드 확인 결과 reason(조건축)·actor(주체축)가 이미 존재하며, 오해 원인은 둘이 합쳐 노출되지 않은 것. 우산 라벨(`watchdog_liquidation`)로 뭉치면 SL/TP/forced 구체 정보가 손실되어 비채택. → FR-3에 반영. - - 종료 조건: 어휘/매핑 방식이 확정돼 FR-3에 반영되면 Answered. (충족) - -### 오픈 질문 처리 기준 -- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. -- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. -- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. -- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. -- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. -- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. -- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 모든 로그 라인이 ISO8601(KST) 타임스탬프 + 레벨 prefix를 갖고, 주문 거부가 발생하면 ERROR 로그로 즉시 보인다(#28 유형 재현 시 로그만으로 인지 가능). -- S-2: 모든 주문 submit/결과(체결가·수량·수수료·세금 또는 거부+사유)가 단일 tailable 로그로 출력되어 `grep`으로 당일 체결/거부를 전수 추출할 수 있다. -- S-3: 청산 기록이 기존 reason 어휘 + actor를 함께 노출해, 안전망 자율청산(actor=safety_sell)과 전략 손절(actor=strategy)을 운영자가 구분할 수 있다(reason=stop_loss만으로 오해되지 않음). -- S-4: 운영자가 단일 CLI 호출로 보유·오늘 체결·실현 PnL(수수료·세금 반영)·대기 주문을 조회할 수 있고, 값이 `ledger_outbox` 수기 계산과 일치한다. -- S-5: `docs/operations.md`가 존재하고, 신규 운영자가 그 문서만으로 데몬 배포·실행, CLI로 상태/PnL 조회, 거부·미체결 디버깅을 수행할 수 있다(배포·CLI·로깅/관측성·트러블슈팅 4영역 모두 포함). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: 단위 테스트(로그 포맷터)+라이브 데몬 로그 라인 검사 / 데이터 출처: 데몬 stdout 로그, `execution.py` 거부 경로. -- S-2 측정 방식: 거부/체결 시나리오 구동 후 로그 grep 검증(단위+end-to-end) / 데이터 출처: 데몬 로그, paper 브로커 체결. -- S-3 측정 방식: safety_sell 각 경로(stop_loss/take_profit/forced_close_at/market_close_liquidation) + 전략 매도 단위 테스트로 기록 reason·actor 확인 / 데이터 출처: `slot_transition_log`, 이벤트 payload. -- S-4 측정 방식: 체결 후 조회 CLI 결과와 `ledger_outbox` 직접 집계 비교 / 데이터 출처: `ledger_outbox`, 체결 기록, BAT-038 수수료·세금 정책. -- S-5 측정 방식: `docs/operations.md` 4영역(배포·CLI·로깅/관측성·트러블슈팅) 포함 여부 리뷰 체크 / 데이터 출처: `docs/operations.md`, `run-trex-core-daemon` SKILL, 신설 CLI. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: OQ-1(조회 표면=CLI)·OQ-2(Loki/correlation In Scope)·OQ-3(알림 Deferred)·OQ-4(reason+actor) 결정으로 In/Out 경계의 플레이스홀더가 모두 해소됨 (충족) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4의 측정 방식과 데이터 출처가 테스트/로그/DB로 매핑됨 (충족) -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 현재 High 리스크 없음. Medium(R-2 로그포맷)·Low(R-1 reason 계약, R-3 PnL 집계)의 처리 방안이 명시됨 (충족) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개. OQ-1·2·4 Answered, OQ-3 Deferred 처리 완료 (충족) -- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 승인자(Justin Ko)가 confirm-discovery로 Freeze 확정 (confirm-discovery 단계에서 처리) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md -- 참조 문서: - - GitHub issue #29 (운영 관측성), issue #28 (호가단위 거부 — 관련 별도 트랙) - - docs/runtime-flows.md, docs/project-structure.md (baseline — 본 Discovery에서 갱신 대상) - - docs/interface-contract.md (조회 RPC/청산사유 어휘/거부 로그 계약 반영 대상) - - docs/operations.md (FR-8 신설 대상 운영자 런북), .claude/skills/run-trex-core-daemon/SKILL.md (런북 사실 자료 출처) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE -- (해소됨) FR-5 조회 표면 = CLI(DB-backed), RPC 없음 — OQ-1 Answered -- (해소됨) Loki 구조화 출력 + correlation id 이번 범위 포함 — OQ-2 Answered (FR-7) -- (해소됨) 알림 전송 채널 = Deferred, ERROR 로그만 — OQ-3 Answered -- (해소됨) FR-3 청산사유 = 기존 reason 어휘 고정 + actor 함께 노출, 신규 필드 없음 — OQ-4 Answered (Option A) - -## CONFIRM -- (확인됨, 2026-06-16, Justin Ko) P0(FR-1·2·7) batch 선행 → P1(FR-3·4·5·8) 후속 batch 분할 방향 동의. FR-8 운영자 런북은 P1(관측성 기능 사용법을 함께 문서화). 정확한 batch 경계는 suggest-batch-reqs에서 확정. - -## DATA -- (해소됨) 배포 환경 Loki 사용 — A-4 전제로 진행 (OQ-2 결정에 반영) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: OQ-1~4 결정 완료 + FR-8(운영자 런북 docs/operations.md) 추가 반영. 신규 FR은 추가 OQ 없는 P1 문서 산출물. 본문·범위·성공기준(S-5) 정합. REQ 초안 작성 진행 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-16 12:20 diff --git a/docs/discovery/dcy-030_20260616_slippage-limit-price-tick-size-snapping.md b/docs/discovery/dcy-030_20260616_slippage-limit-price-tick-size-snapping.md deleted file mode 100644 index 2bcea47..0000000 --- a/docs/discovery/dcy-030_20260616_slippage-limit-price-tick-size-snapping.md +++ /dev/null @@ -1,229 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-030_20260616_slippage-limit-price-tick-size-snapping -- 이슈명: slippage 지정가 KRX 호가단위 미반영 → KIS 거부 (tick-size 스냅, issue #28) -- 작성 시각(KST): 2026-06-16 11:18 -- 마지막 갱신 시각(KST): 2026-06-16 12:35 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-083 (신규, tick 유틸), REQ-070 (change-req CHG-20260616-01, 슬리피지 스냅) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub issue #28 (P0, 다음 거래일 전 수정 필수), REQ-070(slippage 지정가 — 호가단위 반올림을 "후속 주기"로 연기해 둔 당사자), 관련 issue #29/dcy-029(이 거부를 "관측"하는 별도 트랙) -- 해석: - - ORB v1 라이브 테스트(2026-06-16)에서 slippage 밴드가 적용된 매수 한계가가 KRX 호가단위를 위반해 KIS `주식주문호가단위 오류`로 전량 거부됐다. slippage_pct>0인 매수는 산출 한계가가 거의 항상 호가단위를 벗어나 0체결이 된다. - - REQ-070은 호가단위 정밀 반올림을 의도적으로 후속 주기로 미뤘고(int ceil/floor만 구현), 그 미뤄둔 항목이 이제 P0 정확성 버그로 표면화했다. 이번 Discovery는 KRX 호가단위 스냅을 구현하되, **반올림 방향을 REQ-070의 원래 메모(체결 보장 = 매수 상위/매도 하위)와 달리 "상한 준수(매수 내림/매도 올림)"로 전환**한다(승인자 결정). - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 유사 -- 근거 - - 기존 REQ-070(slippage 지정가 집행)의 동작 결함/미완 항목이다. REQ-070 Requirement/Notes가 "KRX 정밀 호가단위 반올림은 후속 주기"로 명시 연기했으므로, 새 기능이라기보다 REQ-070을 완성·정정하는 change-req 성격이다. - - 다만 반올림 방향 정책 전환과 가격대별 호가단위 테이블 도입이라는 결정 요소가 있어, change-req 근거를 남기기 위해 Discovery로 먼저 문제·정책을 정의한다(기존 REQ 본문 수정은 change-req로 수행). - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - `trex_core/application/execution.py:147-158`(`_dispatch`)이 slippage 밴드를 `매수=int(ceil(limit*(1+pct/100)))`, `매도=int(floor(limit*(1-pct/100)))`로 산출하고 **KRX 호가단위로 스냅하지 않는다**. 예: `ceil(147700*1.005)=148439` → 호가단위(100원) 위반 → KIS 거부. - - REQ-070(Status: Implemented) Requirement는 "KRX 호가단위 반올림은 체결 보장 방향(매수=상위, 매도=하위)으로 적용"을 적어뒀으나 Notes에서 "정밀 호가단위 테이블은 후속 주기, 현재는 int(ceil/floor)로 충분"으로 **미구현 상태로 남겨 둔 것**이 확인된다. 즉 현재 코드는 호가단위 스냅이 전혀 없다. - - 영향: `slippage_pct>0`인 매수는 산출 한계가가 거의 항상 호가단위를 벗어나 **전량 거부**(연결·시세 정상이어도 0체결). 매도(floor)도 limit_price+slippage를 쓰는 경로면 동일 위험. ORB v1처럼 slippage_pct를 쓰는 모든 인스턴스 해당. - - 매도 자율청산 경로: `trex_core/application/safety_sell.py`는 SL/TP/forced/market-close 모두 limit_price 없이 **시장가**(grep상 limit 미사용)라 이 버그 영향 밖. 영향받는 매도는 **전략 SellIntent(지정가+slippage)** 경로로 한정된다. - - baseline 문서(`docs/runtime-flows.md`, `docs/project-structure.md`)는 존재하며 최신. runtime-flows에 slippage dispatch 흐름은 기술돼 있으나 호가단위 스냅 단계는 없다. -- 기대 상태 - - slippage 산출 한계가가 항상 **유효한 KRX 호가단위 배수**로 제출되어 `주식주문호가단위 오류` 거부가 사라진다. - - 반올림 방향은 **상한 준수**: 매수는 호가단위로 **내림**(설정 slippage 상한을 넘지 않음), 매도는 호가단위로 **올림**(매도 하한을 넘지 않음). 즉 `slippage_pct`를 하드 리스크 상한으로 취급한다. - - 가격대별 호가단위 산출이 순수 함수(가격→tick)로 분리되어 단위테스트로 경계값까지 검증된다. -- 이해관계자 - - 운영 담당자(trex 운영팀): 매수 체결 복구가 1차 수요. 다음 거래일 전 수정 필요. - - 전략팀(trex-orb-v1 등 slippage_pct 사용 인스턴스): 거부 없는 체결의 직접 수혜자. - - 문서 작성자(AI agent) / 승인자(Justin Ko): 반올림 방향·범위 확정. -- 제약사항 - - KRX 주식 호가단위(2023.1.25~)는 가격 구간별로 다르다(아래 표). ETF/ETN 등은 호가체계가 다를 수 있다. - - `slippage_pct`는 리스크 상한이므로 스냅으로 상한을 넘기면 안 된다(반올림 방향 결정의 근거). - - 변경은 dispatch-time 변환만 — 슬롯의 `limit_price`(=entry_price) 원본은 덮어쓰지 않는다(REQ-070 설계 보존). - -KRX 주식 호가단위 (2023.1.25~): - -| 가격 구간(원) | 호가단위(원) | -| --- | --- | -| ~ 2,000 | 1 | -| 2,000 ~ 5,000 | 5 | -| 5,000 ~ 20,000 | 10 | -| 20,000 ~ 50,000 | 50 | -| 50,000 ~ 200,000 | 100 | -| 200,000 ~ 500,000 | 500 | -| 500,000 ~ | 1,000 | - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 전략이 entry_price=147,700, slippage_pct=0.5%로 매수 인텐트를 내면, dispatch가 산출 한계가 148,439를 **148,400(호가단위 내림)** 으로 스냅해 유효 지정가로 제출하고 정상 체결된다(거부 없음). -- 전략이 entry_price=54,500, slippage_pct=0.5%로 매수하면 54,772.5 → **54,700(내림)** 으로 제출되어 0.5% 상한 안에서 체결된다. -- 전략이 지정가+slippage로 매도하면 산출 한계가가 **호가단위 올림**으로 스냅되어 매도 하한을 넘지 않는 유효 지정가로 제출된다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- KRX 호가단위 스냅 도입: slippage 산출 한계가를 가격대별 호가단위 유효 배수로 정규화. 방향 = **상한 준수(매수 내림 / 매도 올림)**. -- 호가단위 산출 유틸리티 신설: 가격 → tick 순수 함수(KRX 2023.1.25~ 구간표). 재사용·테스트 가능. -- REQ-070 정정: 호가단위 반올림 방향을 원래 메모의 "체결 보장(매수 상위/매도 하위)"에서 "**상한 준수(매수 하위/매도 상위)**"로 전환하고, "후속 주기"로 미뤄둔 정밀 호가단위 테이블을 이번에 구현으로 확정(change-req). -- baseline 문서 갱신: `docs/runtime-flows.md`의 slippage dispatch 흐름에 호가단위 스냅 단계 추가, 신규 tick 유틸 모듈이 생기면 `docs/project-structure.md`에 반영. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/execution.py`(`_dispatch`): slippage 산출 직후 호가단위 스냅 적용(매수 내림/매도 올림). - - 신규: 호가단위 유틸(가격→tick, tick 배수 내림/올림). 위치는 design에서 확정(예: `trex_core/domain/` 또는 `trex_core/application/` 순수 함수 모듈). -- 문서 영향: - - `docs/runtime-flows.md`(slippage dispatch + 호가단위 스냅 흐름), `docs/project-structure.md`(신규 tick 유틸 모듈 책임), REQ-070 본문/AC(change-req). -- 운영 영향: - - slippage_pct 사용 인스턴스의 매수 거부가 해소돼 체결 복구. 반올림 방향 변경으로 체결가가 entry에 더 가깝게(매수) 형성됨(상한 미초과). 배포 절차 변경 없음. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 가격 → KRX 호가단위(tick)를 산출하는 순수 함수를 제공한다(2023.1.25~ 구간표). 구간 경계값에서 정확한 tick을 반환한다. -- FR-2: slippage 산출 한계가를 호가단위 유효 배수로 스냅한다. 방향 = **상한 준수**: 매수 = 해당 tick의 **내림 배수**(≤ 산출 한계가, slippage 상한 미초과), 매도 = 해당 tick의 **올림 배수**(≥ 산출 한계가, 매도 하한 미초과). -- FR-2b: band < 1 tick 엣지에서 미체결을 막기 위해 **하한(매수)/상한(매도)을 보장**한다(OQ-2 결정). 매수 실효가 = `max(floor_to_tick(entry×(1+pct)), ceil_to_tick(entry))`, 매도 실효가 = `min(ceil_to_tick(entry×(1-pct)), floor_to_tick(entry))`. 즉 slippage가 1틱 이상이면 정상 반영, 1틱 미만이어도 최소 entry의 유효 호가는 보장한다. 이 보장값도 slippage 상한/하한을 넘지 않는다. -- FR-3: `limit_price=None`(시장가) 또는 `slippage_pct` 없음/0인 경로는 기존 동작을 보존한다(호가단위 스냅·회귀 없음). slippage가 적용된 경우에만 스냅한다. -- FR-4: REQ-070을 change-req로 정정한다 — 반올림 방향을 "상한 준수(매수 하위/매도 상위)"로 변경하고, 정밀 호가단위 테이블 구현을 확정한다. 변경 근거(상한 준수 > 체결 보장)를 Change Log에 남긴다. -- FR-5: 이 변경이 런타임 흐름/구조를 바꾸므로 `docs/runtime-flows.md`(slippage dispatch 호가단위 스냅)와 필요 시 `docs/project-structure.md`(신규 tick 유틸 모듈)를 갱신한다. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1 (정확성): 호가단위 스냅 후 제출 지정가는 항상 유효한 KRX 호가단위 배수이며, `slippage_pct`가 정의한 가격 상한(매수)/하한(매도)을 넘지 않는다. -- NFR-2 (하위호환): slippage 미사용·시장가 경로의 기존 동작과 REQ-070 AC-3~6의 회귀 없음을 보장하는 additive 변경이다. -- NFR-3 (테스트 가능성): 가격→tick 함수와 스냅 방향을 각 가격 구간 경계값(2,000 / 5,000 / 20,000 / 50,000 / 200,000 / 500,000)에서 단위테스트로 검증한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - FR-1·FR-2·FR-2b·FR-3·FR-4·FR-5. KRX **주식** 호가단위 스냅(상한 준수 방향 + band 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: 승인 -- Handoff Rationale: 반올림 방향(상한 준수), OQ-1(주식 전용), OQ-2(하한 보장) 모두 결정 완료. REQ-070 change-req 방향 동의. 저장소 사실관계(execution.py 미스냅, REQ-070 연기, hub_read 유형필드 부재) 확인. REQ 초안 작성 진행 가능. -- Ready for REQ Drafting: true -- Confirmed By: Justin Ko -- Confirmed At (KST): 2026-06-16 11:30 diff --git a/docs/discovery/dcy-031_20260619_event-publisher-connection-reliability.md b/docs/discovery/dcy-031_20260619_event-publisher-connection-reliability.md deleted file mode 100644 index b950c96..0000000 --- a/docs/discovery/dcy-031_20260619_event-publisher-connection-reliability.md +++ /dev/null @@ -1,182 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-031_20260619_event-publisher-connection-reliability -- 이슈명: 이벤트 발행 연결 신뢰성 — idle heartbeat 사망 후 첫 발행 유실 + DLQ 가시성 (OPS-005) -- 작성 시각(KST): 2026-06-19 23:00 -- 마지막 갱신 시각(KST): 2026-06-19 23:50 -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-091, REQ-092, REQ-093, REQ-094 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: ROADMAP.md(M2 트랙B), memory/trex-backlog.md(Task #16) -- 해석: - - 운영팀 OPS-005 리포트(trex-core RabbitMQ 발행 실패)를 코드로 검증한 결과, 발행 전용 연결의 idle heartbeat 사망 후 첫 발행이 재시도·DLQ 없이 유실되는 실결함을 확인했다. - - M2(매도전략 tsl 라이브, 6/28 목표)에서 슬롯 전이 이벤트 유실은 매도 미발동=실손실로 직결되므로, tsl 라이브 선행 조건으로 발행 신뢰성을 보강한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 dcy-006(RabbitMQ transport 도입)·dcy-009(멀티 인스턴스 토폴로지)는 전송 계약 수립이 주제였고, 발행 연결의 idle 수명/재시도 신뢰성은 다루지 않았다. - - OPS-005는 운영 환류로 발견된 신규 신뢰성 결함이며 독립 주기로 추적하는 것이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. - -- 현재 상태 - - 발행자는 단일 공유 인스턴스다(`runtime/daemon.py:206` `self.event_publisher = _build_transport`). 슬롯 전이 이벤트는 `_make_event_emitter`(`daemon.py:238-248`)가 `publish_event`로 topic exchange `trex.events`에 발행한다. - - 발행 전용 연결은 publish 사이에 `process_data_events`를 돌리지 않는다(consumer 루프와 달리 — `rabbitmq.py:337/379`는 0.2초마다 호출). `params.heartbeat = 30`(`rabbitmq.py:83`)이나 idle 구간엔 heartbeat가 나가지 않아 ~60초 후 RabbitMQ 서버가 연결을 종료한다. - - 다음 발행 시 `_ensure_connected`(`rabbitmq.py:94-103`)가 stale `is_open=True`를 신뢰해 재연결을 건너뛰고, `basic_publish`(confirm_delivery 대기, `rabbitmq.py:88/180`)가 실패한다 → `AMQPError`(`rabbitmq.py:193`) → `_mark_disconnected` + `TransportUnavailableError`. - - 이 예외가 `emit()`(`daemon.py:245`)로 올라가 `:246`에서 잡혀 warning 로그만 남기고 삼켜진다("publish failure must not roll back"). 재시도·DLQ 없이 **그 이벤트는 유실**된다. 다음 발행은 재연결돼 정상 → 증상 = 한산 구간 직후 첫 이벤트 간헐 유실. - - 한편 unroutable(구독자 없음)은 `mandatory=True` → `UnroutableError`(`rabbitmq.py:190`) → `_publish_dlq`로 `trex.events.dlq`(durable)에 보존된다 → 유실 아님. 단 DLQ를 소비/알림하는 주체가 없어 조용히 누적되는 가시성 공백이 있고, `_publish_dlq`는 best-effort(`rabbitmq.py:215` 실패 시 로그만)다. -- 기대 상태 - - 발행 연결의 idle 사망을 예방하고, 사망/half-open 시에도 첫 발행이 투명 재시도로 복구되어 이벤트가 유실되지 않는다. - - DLQ(unroutable + 발행 실패 양쪽)의 유입/깊이가 운영에서 관측·경보 가능하다. -- 이해관계자 - - 작성/검수: 팀장(개발 거버넌스) - - 구현: trex-core-dev - - 영향 소비자: trex-orb-v1(라이브), trex-tsl-v1(M2 라이브 예정) - - 운영: TREX 운영팀(DLQ 가시성/경보 소비) -- 제약사항 - - trex-core는 M1 운영 배포본(`9e35467`)이다. wire 계약·기존 consume/RPC 경로 무회귀가 필수다. - - pika BlockingConnection은 스레드 비안전 — keepalive는 발행 전용 연결을 소유한 스레드에서만 I/O를 돌려야 한다. - -## 주요 사용자 시나리오 -- 점심·저활동·개장 직후 등 한산한 구간 뒤 슬롯이 HOLDING으로 전이될 때, 첫 전이 이벤트가 유실되지 않고 tsl가 수신해 트레일링을 시작한다. -- RabbitMQ 일시 단절/재시작 후에도 발행이 재연결·재시도로 복구되어 청산 전이 이벤트가 누락되지 않는다. -- 운영자가 DLQ 유입(구독자 없음 또는 발행 실패)을 대시보드/로그/경보로 인지하고 환류한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. - -- (B) 발행 재시도 + 강제 재연결: `publish_event`가 `is_open`을 맹신하지 않고, 발행 실패 시 강제 재연결 후 1회 재시도한다. 재시도까지 실패한 경우에만 실패로 처리한다. -- (A) idle keepalive: 발행 전용 연결에 heartbeat 주기 내 `process_data_events` 틱을 보장해 서버 측 연결 종료를 예방한다. -- (D) DLQ 가시성/경보: `trex.events.dlq` 유입·깊이를 관측 가능하게 한다(unroutable + 발행 실패 양쪽 추적). 운영 관측성(dcy-029) 자산 재사용 여부 검토. - -## 영향 범위 -- 코드 영향: - - `trex_core/adapters/messaging/rabbitmq.py`(`publish_event`/`_ensure_connected`/`_publish_dlq` + keepalive 추가) - - `trex_core/runtime/daemon.py`(`_make_event_emitter` 실패 처리 강화, keepalive 틱 연동) -- 문서 영향: - - `docs/operations.md`(DLQ 가시성/경보 운영 절차), `docs/runtime-flows.md`(발행 재시도/keepalive 흐름) 갱신 검토 -- 운영 영향: - - DLQ 모니터링/경보 신규 절차. 배포는 trex-core 재배포 필요(M1 배포본 갱신) — 사장님 release 결재 대상. - -# 5. 요구사항 목록 - -## 기능 요구사항 -- FR-1 (B): `publish_event`는 연결 종료/half-open으로 발행 실패 시 강제 재연결 후 1회 재시도한다. 재시도까지 실패한 경우에만 `TransportUnavailableError`를 올린다. `is_open` 기반 스킵에 의존하지 않는다. -- FR-2 (A): 발행 전용 연결에 idle keepalive를 도입해 heartbeat 주기 내 I/O가 보장되도록 한다(서버 측 idle 종료 예방). pika 스레드 안전성을 위반하지 않는다. -- FR-3 (D): `trex.events.dlq` 유입/깊이를 운영에서 관측·경보 가능하게 한다(메트릭/로그/소비자 중 택1). unroutable과 발행 실패 양쪽이 추적되어야 한다. -- FR-4: `_make_event_emitter`의 발행 실패 처리를 보강한다 — 재시도 후에도 실패 시 단순 warning 삼킴이 아니라 식별 가능한 경보 레벨 로그/메트릭을 남긴다. - -## 비기능 요구사항 -- NFR-1: M1 운영 배포본(`9e35467`) 무회귀 — 기존 consume/RPC/wire 계약과 토폴로지(`declare_topology`)는 불변. -- NFR-2: 재시도/keepalive가 execution-loop를 장시간 블록하지 않는다(매도 타이밍 보호) — 재시도 1회 + 명시적 타임아웃 상한. -- NFR-3: 변경은 transport 어댑터·발행 경로에 국소화하고, 슬롯 도메인/broker 집행 로직은 건드리지 않는다. - -## 범위 경계 -- In Scope - - 발행 전용 연결의 재시도·재연결·keepalive(B+A) - - DLQ 유입/깊이 가시성·경보(D) - - emit 실패 처리 강화(FR-4) -- Out of Scope - - 이벤트 영속 outbox(옵션 C) — 본 주기 제외, 필요 시 후속(M3) Discovery. (사장님 스코프 결정 B+A+D) - - consumer(orb/tsl) 측 idempotency 구현 — 본 주기는 검증/확인까지, 구현은 해당 repo 별도 추적 - - RPC client(`call_rpc`) 연결 정책 — 본 주기는 이벤트 발행 경로 한정 - -# 6. 리스크/가정 목록 - -## 리스크 -- R-1 (High): trex-core는 M1 라이브 배포본이고 발행 경로는 orb(라이브)·tsl(M2)에 동시 영향 → 회귀 시 사고 반경 큼. - - 완화 방안: opus QC 강제, 기존 회귀 스위트 + wire 계약 무회귀 검증, transport 어댑터로 변경 국소화. -- R-2 (Medium): keepalive가 pika 스레드 비안전성과 충돌 가능(다른 스레드에서 I/O 호출 시 손상). - - 완화 방안: 발행 전용 연결을 소유한 스레드에서만 keepalive I/O 실행하도록 설계 단계에서 확정(OQ-1). -- R-3 (Medium): 재시도는 at-least-once 의미 → 동일 이벤트 중복 발행 가능. - - 완화 방안: 소비자(orb/tsl) idempotency 검증(OQ-2). 슬롯 전이 이벤트는 멱등 적용 가능 추정. - -## 가정 -- A-1: 본 변경은 transport 어댑터·발행 경로에 한정되며 trex-core dcy-028(strategy-state, BAT-042 released) 등 기존 계약은 불변이다. -- A-2: 슬롯 전이 이벤트 소비자(orb/tsl)는 중복 수신에 견고하거나 견고화 가능하다. -- A-3: DLQ 가시성은 dcy-029(운영 관측성) 로깅/뷰 자산을 일부 재사용할 수 있다. - -## 오픈 질문 -- OQ-1: keepalive를 별도 스레드로 둘지, execution-loop 틱에 발행 연결 `process_data_events`를 삽입할지 (pika 스레드 안전성 제약). - - 상태: Deferred - - 처리 방안: draft-batch-design에서 trex-core-dev가 스레드 소유 모델 기준으로 결정. REQ에 설계 결정 추적 인계. - - 종료 조건: batch design 문서에 keepalive 실행 위치·스레드 소유가 확정되면 Answered 처리. -- OQ-2: 재시도 at-least-once로 인한 이벤트 중복 시 소비자(orb/tsl)가 멱등한가. - - 상태: Deferred - - 처리 방안: delivery 단계에서 trex-orb-v1/trex-tsl-v1 이벤트 핸들러의 멱등성 확인(§10 DATA로 추적). 비멱등이면 해당 repo 후속 티켓. confirm-discovery를 막지 않음(전이 이벤트는 멱등 적용 가능 추정). - - 종료 조건: 두 소비자의 중복 수신 동작이 확인되면 Answered. -- OQ-3: DLQ 가시성을 메트릭/로그/소비자 데몬 중 무엇으로 구현할지. - - 상태: Deferred - - 처리 방안: draft-batch-design에서 dcy-029 관측성 자산 재사용 가능성과 함께 결정. - - 종료 조건: batch design에 DLQ 가시성 구현 방식이 확정되면 Answered. - -# 7. 초기 성공 기준 -- S-1: idle(>heartbeat×2) 구간 직후 발행이 유실 없이 성공한다(재연결·재시도로 복구). -- S-2: RabbitMQ 강제 단절/재시작 시나리오에서 발행 이벤트 누락 0건. -- S-3: DLQ 유입(unroutable + 발행 실패)이 관측 지표로 노출되고 경보 가능하다. - -## 측정 방식 및 데이터 출처 -- S-1 측정 방식: idle 주입 후 발행 통합 테스트(연결 강제 종료 후 publish) / 데이터 출처: trex-core 테스트 스위트(신규 케이스). -- S-2 측정 방식: broker 재시작 fault-injection 테스트에서 발행 카운트 vs 수신 카운트 대조 / 데이터 출처: 통합 테스트 + trex-sim e2e(M2 트랙C 연계). -- S-3 측정 방식: DLQ 깊이/유입 메트릭 노출 확인 / 데이터 출처: 관측 지표(로그/메트릭), `docs/operations.md`. - -# 8. REQ로 넘기기 전 확인 체크 -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 사장님 스코프 B+A+D 확정 반영 + outbox(C) 제외 합의 명시 (2026-06-19 확정) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~3의 테스트/메트릭 출처가 trex-core 스위트·trex-sim e2e로 연결됨 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(회귀) 해소 책임 = trex-core-dev 구현 + opus qc-reviewer (CONFIRM① 확정) -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1/3 Deferred(design 인계), OQ-2 Deferred(delivery 검증) 처리 완료 — Open 0개 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 사장님(CEO) via 팀장 - -# 9. 파일 처리 결과 -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-031_20260619_event-publisher-connection-reliability.md -- 참조 문서: - - trex_core/adapters/messaging/rabbitmq.py - - trex_core/runtime/daemon.py - - docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE -- (해소됨) 수정 스코프 = B(재시도)+A(keepalive)+D(DLQ 가시성). outbox(C)는 제외→M3 후보. — 사장님 결정 2026-06-19, §5에 반영. - -## CONFIRM -- (확정) opus QC: 본 batch는 고위험(M1 라이브 trex-core)이라 qc-reviewer를 opus로 승격 운용. — 사장님 확정 2026-06-19. -- (확정) M2 게이트: tsl(M2) 라이브 전 선행 완료 — 6/28 릴리즈 창 내 배포·검증. — 사장님 확정 2026-06-19. - -## DATA -- {{DATA_CONSUMER_IDEMPOTENCY}}: orb/tsl 이벤트 핸들러의 중복 수신 멱등성 확인 결과(OQ-2 종료용). - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: confirm-discovery 완료. 스코프 B+A+D 확정, CONFIRM 2건(opus QC·M2 게이트) 확정, OQ 전부 Deferred 처리(Open 0). DATA 1건(소비자 멱등성)은 delivery 검증으로 추적. trex-core-dev에 draft-req 인계. -- Ready for REQ Drafting: true -- Confirmed By: 사장님(CEO) via 팀장 -- Confirmed At (KST): 2026-06-19 23:20 diff --git a/docs/discovery/dcy-032_20260621_fill-missing-price-cost-basis-backfill.md b/docs/discovery/dcy-032_20260621_fill-missing-price-cost-basis-backfill.md deleted file mode 100644 index 37c2bb0..0000000 --- a/docs/discovery/dcy-032_20260621_fill-missing-price-cost-basis-backfill.md +++ /dev/null @@ -1,202 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-032_20260621_fill-missing-price-cost-basis-backfill -- 이슈명: 체결가 누락(브로커 평균가 0/None) 시 슬롯 평단·원장 cost basis 0 오염(무경고·영구) — 지정가 백필 + 로깅 + 알람 검토 -- 작성 시각(KST): 2026-06-21 (KST) -- 마지막 갱신 시각(KST): 2026-06-21 (KST) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-095, REQ-096, REQ-097 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: 팀장 워크스페이스 백로그 `trex-backlog.md`("avg_price=None → 무경고 고정 2% 폴백" finding에서 상류 원인으로 추적) -- 해석: - - tsl tier 폴백(avg_price=None) finding을 상류로 추적한 결과, 진짜 원인은 trex-core 체결 처리에서 브로커 평균체결가가 0/None으로 올 때 **슬롯 평단과 원장 cost basis가 동시에 0으로, 무경고·영구히 오염**되는 데이터 무결성 갭이었다. - - 사장님 방향은 "지정가(주문 limit_price) 백필 + 로깅 + (이 기회에) 알람 기능 검토"이며 본 건은 M2 포함으로 확정되었다. 본 Discovery는 그 방향을 trex-core 변경 단위로 정식화한다. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-026(매도 세금/수수료 정책), dcy-029(운영 관측성·PnL 뷰)는 체결 *기록*과 인접하나, "체결가 자체가 0/None일 때의 무결성 방어"는 어디서도 다루지 않았다. 기존 코드의 `if avg else 0.0`/`fs.avg_price if not None else 0.0`은 방어가 아니라 0 통과 경로다. - - 신규 알람(alert) 능력 검토가 포함되어, 기존 Discovery의 미세 조정이 아니라 독립 주기로 추적하는 편이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. - -- 현재 상태 - - KIS 체결조회(`inquire-daily-ccld`)가 체결수량(`ccld_qty`)은 보고하나 평균체결가(`avg`)가 없거나 0으로 오는 순간 스냅샷이 있을 수 있다. `adapters/broker/kis.py:209,219`(완전체결)·`:232,238`(부분체결+취소)·`:243`(부분체결)에서 `fill_price = float(avg) if avg else 0.0` → `avg_price = fill_price or None`로, **체결인데 가격이 None/0**인 FillStatus가 생성된다. - - 이 값이 두 곳을 동시에 오염시킨다. (1) 슬롯 평단: 초기 매수 시 `application/execution.py:242-251`에서 `old_avg=0, fill_price=0 → new_avg=0`로 HOLDING 전이 → `slot.avg_price=0` 저장. (2) 원장: `application/execution.py:424` `price = float(fs.avg_price) if fs.avg_price is not None else 0.0` → **매수가 price=0으로 ledger_outbox에 기록**. - - **무경고**: 원장 skip 가드는 `portfolio_id` 부재(`:405`)·`execution_id` 부재(`:415`) 두 가지뿐이고 둘 다 WARN한다. "체결됐는데 price≤0"은 어느 가드에도 안 걸려 **조용히 통과**한다. - - **영구성**: 완전체결(`ccld_qty >= ord_qty`)이면 즉시 HOLDING 전이 + `clear_dispatch`(`execution.py:248-285`)라 **다음 폴링을 하지 않는다.** KIS가 다음 틱에 올바른 평균가를 줘도 다시 읽지 않아 0이 영구히 박힌다. - - **하류 영향 확인됨**: 슬롯 평단 0 → slots RPC가 0.0 전송(`messaging_handlers.py:561`) → tsl이 `≤0 → None`(tsl_strategy.py:213)로 보고 tier 무시 2% 폴백 매도(무탐지). 원장 price=0 → 실현손익이 cost basis가 아닌 당일 net 현금흐름으로 계산되므로(`operator_report.py`) 매수 대금 과소 → **실현손익 과대 표시**. - - trex-core에는 구조화된 알람(alert) 채널이 없다. 운영자 표면은 CLI `trex-core report` + 구조화 로그가 전부이며, 이상 징후를 푸시로 알리는 메커니즘은 부재(`daemon.py:248`에 alert 주석 1건뿐). -- 기대 상태 - - 체결됐는데 브로커 평균체결가가 없거나 0이면, 슬롯 평단과 원장 price를 **주문 지정가(limit_price)로 백필**해 cost basis가 0으로 오염되지 않는다. - - 백필이 발생하면 반드시 식별 가능한 WARN 로그(슬롯·심볼·주문·대체가)를 남겨 사후 탐지·정합화가 가능하다. - - (검토 대상) 이런 무결성 이상을 운영자에게 즉시 알리는 알람 채널의 도입 가치/범위를 평가한다. -- 이해관계자 - - 팀장(Claude) — Discovery/게이트 관리, 본 finding 추적자 - - 사장님 — M2 범위·알람 채널·비가역 운영 결정 승인자 - - trex-core-dev — delivery 담당 - - 운영자 — 백필/이상 알림의 최종 소비자, 첫 거래일 매도 모니터링 주체 -- 제약사항 - - trex-core wire 계약(slots RPC 필드 등) 변경은 blast radius가 커 본 건 범위에서 가급적 피한다(slot.avg_price 값 정정은 wire 스키마 변경이 아님). - - 백필 가격은 추정치다 — 실제 체결가와 다를 수 있으므로 "정확값"이 아니라 "0보다 합리적인 근사 + 추적 가능"이 목표다. - - orb 매수는 지정가 주문이라 limit_price가 존재한다는 전제에 의존한다(시장가/지정가 부재 케이스는 별도 폴백 필요). - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자: 장중 매수 체결 직후 KIS 체결조회가 일시적으로 평균가 빈 행을 반환 → 현재는 슬롯 평단 0 + 원장 매수 price 0이 영구히 남아 그날 실현손익 리포트가 부풀려진다. 백필 후엔 지정가 근사로 기록되고 WARN으로 추적된다. -- tsl 매도 데몬: 평단 0인 holding 슬롯을 받아 tier 무시 2% 폴백으로 매도(정책 위반)하고 아무도 모른다. 백필 후엔 정상 tier로 평가된다. -- 운영자(알람 검토 시나리오): 무결성 이상(백필 발생, 0가격 체결)이 즉시 푸시 알람으로 도달해 당일 수기 점검 없이도 인지한다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. - -- 변경 1 — 체결가 무결성 가드 + 지정가 백필: 체결(완전/부분) 처리에서 브로커 평균체결가가 None/0이면, 슬롯 평단과 원장 price를 주문 지정가(`slot.limit_price`/주문 컨텍스트)로 백필한다. 지정가도 없으면 최종 폴백 정책을 적용한다(범위 경계 참조). -- 변경 2 — 백필/0가격 체결 로깅: 백필 또는 price≤0 체결 통과 시 식별 가능한 WARN 로그를 남긴다(슬롯·심볼·주문·원가격·대체가격·사유). 원장 skip 가드와 동일한 운영 가시성 수준으로 맞춘다. -- 변경 3 — 알람(alert) 능력 검토(스파이크): trex-core 무결성 이상(백필 발생 등)을 운영자에게 푸시로 알리는 알람 채널의 도입 가치·범위·채널을 평가하고, M2 포함 여부와 최소 구현 범위를 결정한다. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시한다. - -- 코드 영향: - - `trex_core/application/execution.py` — `_on_filled`/`_on_partial_cancel`의 평단 계산(`:242-251`,`:305-314`)과 `_record_ledger`의 price 결정(`:424`)에 백필 분기 추가. - - `trex_core/adapters/broker/kis.py` — 평균가 None/0 표면화 지점(`:209,219,232,238,243`)의 의미 정합성 검토(어디서 백필할지 결정: adapter vs application). - - (알람 채택 시) 신규 alert 포트/어댑터 모듈 + 호출 지점. 본 Discovery에서는 검토만, 구현 범위는 OQ로 둔다. -- 문서 영향: - - `docs/discovery/index.md` 신규 행 추가. - - (알람 채택 시) `docs/runtime-flows.md`에 알람 흐름 추가 가능 — 채택 결정 후 반영. -- 운영 영향: - - 백필 발생 시 원장에 지정가 근사가 들어가므로, 운영자 PnL 리포트의 정확도가 개선되되 "근사"임을 런북에 명시 필요. - - (알람 채택 시) 운영자 알림 수신 채널·임계 설정이 신규 운영 절차로 추가됨. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 체결(완전·부분) 처리에서 브로커 평균체결가가 None 또는 ≤0이면, 슬롯 평단(`slot.avg_price`)을 주문 지정가로 백필해 0으로 저장되지 않게 한다. -- FR-2: 동일 조건에서 원장 기록 price(`_record_ledger`)도 동일한 백필 가격을 사용해 매수/매도가 price=0으로 기록되지 않게 한다. -- FR-3: 백필이 발생하거나 price≤0 체결이 통과할 때, 슬롯·심볼·주문ID·원가격·대체가격·사유를 포함한 WARN 로그를 남긴다(원장 skip 가드와 동일한 가시성 수준). -- FR-4: 알람(alert) 능력을 검토해 (a) 도입 여부, (b) 채널(로그 기반/AMQP 이벤트/외부 푸시), (c) M2 최소 범위를 산출물로 제시한다. - -## 비기능 요구사항 -- NFR-1: 백필은 trex-core wire 스키마(slots RPC 필드)를 변경하지 않는다(값 정정만, 계약 불변). -- NFR-2: 백필 분기는 happy path(정상 평균가 수신) 성능·동작에 영향이 없어야 하며, 정상 경로에서는 기존과 동일하게 동작한다. -- NFR-3: 백필 가격은 추적 가능해야 한다 — 어떤 체결이 실제 평균가가 아니라 백필 근사로 기록됐는지 로그/플래그로 구분 가능해야 한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 체결가 None/0 → 지정가 백필(슬롯 평단 + 원장 price) + WARN 로깅 (FR-1~3). - - 알람 능력 검토 산출물(FR-4) — 검토/결정까지 In Scope. -- Out of Scope - - slots RPC 등 wire 계약 스키마 변경. - - 과거에 이미 0으로 기록된 원장/슬롯의 소급 정정(별도 데이터 보정 작업). - - {{DECIDE_ALARM_IMPL_SCOPE}} — 알람의 실제 구현 범위(검토 결과 M2 포함으로 결정되면 In Scope로 승격, 아니면 후속 이월). - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. - -## 리스크 -- R-1 (Medium): 지정가 백필도 추정치라, 실제 체결가와 차이가 나면 원장 cost basis가 부정확해진다(0보다는 낫지만 정확하진 않음). - - 완화 방안: 백필을 WARN+플래그로 추적 가능하게 남기고, 런북에 "백필 항목은 근사" 명시. 필요 시 후속 정합화(잔고/주문 대조) 검토. -- R-2 (Medium): 백필 시점을 adapter(kis.py)로 둘지 application(execution.py)로 둘지에 따라 paper 브로커·재구성(dcy-021) 경로와의 일관성이 달라질 수 있다. - - 완화 방안: 백필 결정 지점을 단일화(application 권장 — 주문 컨텍스트/limit_price 접근 용이)하고 design에서 paper/재구성 경로 회귀 테스트. -- R-3 (Low): 알람 채널 도입이 범위를 키워 M2 일정을 압박할 수 있다. - - 완화 방안: FR-4를 "검토 산출물"로 제한하고, 구현은 검토 결과에 따라 별도 결정(OQ-1). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: orb 매수는 지정가 주문이라 대다수 케이스에서 `slot.limit_price`(또는 주문 지정가)가 존재한다. -- A-2: KIS 평균가 누락은 드문 순간 레이스이며, 정상 happy path에서는 평균가가 정상 수신된다(저빈도·고영향 특성). -- A-3: trex-core 기존 watchdog/원장 outbox/이벤트 발행 인프라는 그대로 재사용 가능하며, 본 변경은 그 위의 값 무결성 가드다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. - -- OQ-1: 알람(alert) 능력을 M2에 실제 구현까지 포함할지, 검토 산출물만 내고 구현은 후속으로 이월할지. 채널은 무엇으로 할지(구조화 로그 + 로그 기반 알람 / AMQP 이벤트 / 외부 푸시). - - 상태: Deferred (→ design) - - 처리 방안: design 단계에서 trex-core-dev가 채널별 장단점·최소 범위 분석 산출물 작성. 그 분석을 근거로 **알람의 M2 구현 포함 여부는 design review에서 사장님이 최종 결정**(범위·일정 결재 사안). FR-3 WARN 로깅은 채택 여부와 무관하게 M2 필수. - - 종료 조건: design review에서 알람 구현이 In Scope/후속 이월 중 하나로 확정되면 Answered (사장님 2026-06-21: design 분석 후 확정 합의). -- OQ-2: 주문 지정가(limit_price)마저 부재하거나 ≤0인 경우의 최종 백필 폴백 정책(예: 직전 종가/현재가 조회 / 해당 체결 보류·재폴링 / 기록 skip+강한 알림 중 무엇). - - 상태: Deferred (→ design) - - 처리 방안: design 단계에서 trex-core-dev가 후보별 영향 분석, 팀장 승인. - - 종료 조건: 폴백 우선순위가 단일 정책으로 확정되면 Answered (사장님 2026-06-21: design 분석 후 확정 합의). - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. - -- S-1: 평균가 None/0 체결 입력에 대해 슬롯 평단과 원장 price가 0이 아닌 지정가 백필 값으로 기록된다. -- S-2: 백필/0가격 체결 통과 시 식별 가능한 WARN 로그가 100% 남는다(무경고 통과 0건). -- S-3: 정상 평균가 수신 경로는 기존과 동일하게 동작한다(회귀 0건). 알람 능력 검토 산출물이 confirm-discovery에 제출된다. - -## 측정 방식 및 데이터 출처 -- S-1 측정 방식: 평균가 None/0을 주입한 단위/통합 테스트로 슬롯 평단·ledger_outbox price 검증 / 데이터 출처: 신규 테스트 + ledger_outbox 레코드. -- S-2 측정 방식: 백필 경로 테스트에서 WARN 로그 발생 단언 / 데이터 출처: 구조화 로그. -- S-3 측정 방식: 기존 execution/ledger 테스트 스위트 회귀 + 검토 문서 제출 여부 / 데이터 출처: 테스트 결과 + Discovery 첨부 산출물. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 알람 구현 범위(OQ-1)가 In Scope/이월 중 하나로 확정 — FR-1~3는 M2 In Scope 확정, 알람 구현 범위는 design review로 Deferred(설계 분석 후 사장님 결정) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-3 측정안에 사장님/팀장 이견 없음 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 현재 High 리스크 없음(R-1/R-2 Medium) — design에서 R-2 결정 지점 단일화 책임자(trex-core-dev) 지정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 — OQ-1/OQ-2 모두 design Deferred(사장님 2026-06-21) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: confirm-discovery 시 팀장이 CONFIRMED_BY로 기록 — 팀장(Claude) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-032_20260621_fill-missing-price-cost-basis-backfill.md -- 참조 문서: - - docs/discovery/dcy-026_20260612_sell-transaction-tax-and-fee-policy.md (체결 기록·세금/수수료 인접 정책) - - docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md (운영 관측성·실현 PnL 정의) - - trex_core/application/execution.py, trex_core/adapters/broker/kis.py, trex_core/application/operator_report.py (현재 구현 근거) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. - -## DECIDE -- (해소→design) 알람 능력 M2 구현 포함 여부 + 채널 선택(OQ-1) — design 분석 산출물 근거로 design review에서 사장님 결정(사장님 2026-06-21). -- (해소→design) 지정가 부재 시 최종 백필 폴백 정책(OQ-2) — design 단계 trex-core-dev 분석 후 팀장 확정(사장님 2026-06-21). - -## CONFIRM -- (확정) 본 건 FR-1~3(무결성 가드: 지정가 백필 + 로깅)의 M2 포함 — 사장님 결정(2026-06-21). - -## DATA -- {{DATA_KIS_AVG_MISSING_FREQUENCY}} — 실거래에서 KIS 평균가 누락이 실제로 관측된 빈도(있다면 운영 로그/사례). 백필 폴백 정책 결정의 근거 보강용. (없으면 저빈도·고영향 가정으로 진행) - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: FR-1~3(지정가 백필+로깅) M2 포함 사장님 확정. OQ-1(알람 구현범위/채널)·OQ-2(지정가 부재 폴백) 모두 design 단계 trex-core-dev 분석 후 확정으로 Deferred 처리(사장님 2026-06-21) → Open OQ 0건. REQ 초안 작성 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: 팀장(Claude) -- Confirmed At (KST): 2026-06-21 (KST) diff --git a/docs/discovery/dcy-033_20260621_suite-alarm-delivery-notifier.md b/docs/discovery/dcy-033_20260621_suite-alarm-delivery-notifier.md deleted file mode 100644 index 1ac1a28..0000000 --- a/docs/discovery/dcy-033_20260621_suite-alarm-delivery-notifier.md +++ /dev/null @@ -1,219 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-033_20260621_suite-alarm-delivery-notifier -- 이슈명: 제품군 알람 전달 체계 — 모듈 발신 + 중앙 notifier(trex-notifier) → 텔레그램(P1 아웃바운드) -- 작성 시각(KST): 2026-06-21 (KST) -- 마지막 갱신 시각(KST): 2026-06-21 (KST, draft-req trex-core-dev) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: - - trex-core: REQ-098 (알람 envelope wire 계약), REQ-099 (trex.alerts exchange 토폴로지), REQ-100 (core CRITICAL emitter 배선), REQ-101 (알람 발신 흐름 baseline 정합) — 모두 Approved - - (cross-repo, 모듈 emitter/서비스 — 각 repo SRS) trex-orb-v1: REQ-039 / trex-tsl-v1: REQ-024 / trex-notifier: REQ-001~008 — Approved (confirm-req 팀장 2026-06-21). ※각 dev가 trex-core 범위 밖이라 미기재한 역참조를 팀장이 보강. - - ~~kis-data-hub: REQ-057~~ — **Deprecated (사장님 결정 2026-06-21: kdh는 당분간 trex-notifier 미사용 → 알람 범위 제외)**. 향후 재도입 시 새 REQ로 부활. - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: `/home/genichin/workspace/trex-lead/ALARM-INVENTORY.md`(0단계 알람 인벤토리 스윕 결과 — core/orb/tsl/kdh ~100개 조건, 본 Discovery의 source-of-truth), `repos/trex-notifier`(부트스트랩 완료된 신규 서비스 repo) -- 해석: - - TREX 제품군엔 운영자에게 이상을 즉시 알리는 구조화된 알람 채널이 없다. ~100개 알람 후보가 로그(또는 예외)로만 존재하고, CRITICAL 상황도 운영자가 능동 인지해야 한다. M2(tsl 매도 라이브)에서 운영자가 첫 거래일을 수기 감시해야 하는 부담도 이 갭에서 나온다. - - 토론으로 큰 방향은 확정됐다: 신규 `trex-notifier`가 알람을 중앙 수집해 텔레그램으로 전달(P1 아웃바운드만). 본 Discovery는 그 알람 **전달 계약과 아키텍처**를 trex-core 변경 단위로 정식화한다(envelope는 trex-core 소유 wire 계약). - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - dcy-029(운영 관측성 — 로깅/PnL 뷰), dcy-031(이벤트 발행 신뢰성)은 인접하나 "이상을 운영자에게 능동 푸시"하는 알람 전달 체계는 어디서도 다루지 않았다. 기존은 전부 pull(로그/CLI) 기반이다. - - 제품군 횡단 신규 컴포넌트(trex-notifier)와 신규 wire 계약(알람 envelope)을 도입하므로 독립 주기로 추적하는 편이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. - -- 현재 상태 - - trex-core에 구조화 알람 채널 부재 — 운영자 표면은 CLI `trex-core report` + 구조화 로그(Loki)뿐(`daemon.py:248`에 alert 주석 1건). 푸시 메커니즘 없음. - - 인벤토리 스윕(ALARM-INVENTORY.md) 결과: core/orb/tsl/kdh에 ~100개 알람 후보. 대부분 WARN/ERROR로 로깅되나 **일부 CRITICAL은 예외 raise만 되고 로그조차 없다**(예: 토큰 격리, RPC 타임아웃 누적). 등급도 모듈마다 제각각. - - 연쇄/상관 실재: kis-data-hub 인증실패/다운 1건 → orb `kdh_auth_failure` + tsl `kis_auth_failure_abort` 동시 발생. 단 core safety-sell은 직접 KIS(다른 키)라 독립. - - `trex-notifier` repo는 부트스트랩 완료(골격+거버넌스+baseline, root commit `078d37d`, private). 단 전송 방식·의존성은 본 Discovery 확정까지 비어 있다. -- 기대 상태 - - 각 모듈이 이상 조건을 표준 **알람 envelope**로 발신하면, 중앙 `trex-notifier`가 등급 판정·중복/상관 억제 후 CRITICAL은 텔레그램 즉시 푸시, WARN은 일일 다이제스트로 전달한다. - - 크래시·정지로 자기 알람을 못 내는 데몬은 **하트비트(침묵=장애)** 로 탐지된다. - - 알람 전달이 기존 wire 계약(slots RPC/trex.events)·로그 저장 경로를 건드리지 않는다(가산·격리). -- 이해관계자 - - 사장님 — 운영자(알람 최종 수신자), 범위·보안·외부노출 결재자 - - 팀장(Claude) — Discovery/게이트, 인벤토리 작성자 - - trex-core-dev — envelope 계약 + core emitter delivery - - 각 모듈 dev(orb/tsl/kdh) — 모듈별 emitter delivery - - service-dev — trex-notifier 서비스 delivery -- 제약사항 - - 기존 wire 계약(slots RPC, trex.events) 스키마 변경 금지 — 알람은 가산적 신규 채널로만. - - orb/tsl/kdh는 `trex_core`를 import하지 않는다 — 공유는 **규약(envelope)** 이지 런타임 코드가 아니다(모듈당 thin emitter). - - notifier는 로그 firehose 게이트웨이가 아니다 — 로그 저장은 인프라 슈퍼(Vector/Promtail→Loki) 담당. notifier는 알람만. - - core는 텔레그램을 직접 호출하지 않는다(외부 의존·시크릿 격리). 시크릿(봇 토큰)은 notifier 1곳. - - 알람 채널이 의존하는 인프라(RabbitMQ/Loki)가 장애나면 그 경로 알람이 못 나간다 → 하트비트 필요. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자: tsl 매도 라이브 첫날, `sell_signal_rejected_terminal`(포지션 못 팜) 발생 → 즉시 텔레그램 CRITICAL 푸시로 인지하고 개입. 현재는 로그를 들여다봐야만 알 수 있다. -- 운영자: kis-data-hub 인증 만료 → orb·tsl 동시 알람을 notifier가 "시세 허브 장애 — 2개 모듈 영향" 1건으로 묶어 전달(폭풍 억제). -- 운영자: tsl 데몬이 크래시해 침묵 → 하트비트 부재로 notifier가 "tsl 데몬 응답 없음" 알람. -- 운영자: 장마감 후 WARN 다이제스트("오늘 백필 2건, watchdog 인수 0건, stale 양보 0건")로 비긴급 이상을 한 번에 검토. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. - -- 변경 1 — **알람 envelope 계약(trex-core 소유)**: `source / severity(critical·warn·info) / code / title / fields / occurred_at / schema_version` 표준 메시지 스키마 정의. 전 모듈이 이 규약으로 발신. -- 변경 2 — **알람 전송 채널**: 전용 **`trex.alerts` AMQP exchange**(확정, 사장님 2026-06-21) — 가산·격리(기존 slots RPC/trex.events 불변). 모듈은 기존 RabbitMQ 연결로 발행, notifier가 단일 소비자. -- 변경 3 — **모듈별 emitter**: core/orb/tsl/kdh 각 repo에 thin 알람 발신기(기존 연결 재사용, ~십수 줄). 인벤토리 CRITICAL 집합부터 배선. -- 변경 4 — **trex-notifier P1**: 알람 수집 → 등급 판정 → 중복/상관 억제 → 텔레그램(CRITICAL 즉시 / WARN 다이제스트) + 하트비트. - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시한다. - -- 코드 영향: - - trex-core: 알람 emitter(신규) + `trex.alerts` 채널 선언(채택 시). 기존 execution/safety_sell 등의 CRITICAL 지점에서 emit 호출. - - trex-orb-v1 / trex-tsl-v1 / kis-data-hub: 각 thin emitter + 주요 알람 지점 emit 호출(각 repo 별 batch). - - trex-notifier: classifier/deduplicator/sender/heartbeat 구현 + 진입점 + 의존성(텔레그램·AMQP 라이브러리) 확정. -- 문서 영향: - - trex-core `docs/interface-contract.md` — 알람 envelope + `trex.alerts` 채널 계약 신설. - - trex-core `docs/runtime-flows.md` — 알람 발신 흐름 추가(baseline 갱신). - - trex-notifier `docs/runtime-flows.md` — TBD였던 전송 방식 확정 반영. - - `docs/discovery/index.md` 갱신. -- 운영 영향: - - 운영자에게 텔레그램 알람 수신 채널·등급 정책이 신규 운영 절차로 추가(런북). - - 봇 토큰·화이트리스트 chat id 운영 비밀로 관리(notifier 1곳). - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: 표준 알람 envelope 스키마(`source/severity/code/title/fields/occurred_at/schema_version`)를 trex-core가 wire 계약으로 정의하고 `docs/interface-contract.md`에 명시한다. -- FR-2: 각 모듈(core/orb/tsl/kdh)이 인벤토리 CRITICAL 집합(ALARM-INVENTORY.md §A)의 조건 발생 시 envelope로 알람을 발신한다. -- FR-3: trex-notifier가 알람을 수집해 등급별로 처리한다 — CRITICAL은 텔레그램 즉시 푸시, WARN은 일일 다이제스트, INFO는 제외(로그만). -- FR-4: trex-notifier가 동일 근본원인의 상관 알람을 묶고(예: 시세 허브 장애 → 다모듈), 반복 알람을 rate-limit/중복 억제한다. -- FR-5: trex-notifier가 하트비트(dead-man's switch)로 데몬 침묵=장애를 탐지·알린다. -- FR-6: trex-core `docs/runtime-flows.md`에 알람 발신 흐름을, trex-notifier `docs/runtime-flows.md`에 확정 전송 방식을 반영한다(baseline 갱신). - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 알람 채널은 **가산·격리** — 기존 wire 계약(slots RPC, trex.events) 스키마를 변경하지 않는다. orb/tsl/kdh 기존 동작 무회귀. -- NFR-2: 보안 — 텔레그램 봇 토큰 시크릿은 notifier 1곳에만, 수신 대상은 화이트리스트(사장님 chat id). core/orb/tsl/kdh에 외부 시크릿 유입 없음. -- NFR-3: 신뢰성 — 알람 경로(notifier/RabbitMQ)가 죽어도 모듈 본업(매매)과 로그 저장은 영향 없음. notifier는 SPOF가 아니다. -- NFR-4: 모듈측 비용 최소 — emitter는 thin(기존 연결 재사용), `trex_core` import 없이 envelope 규약만 공유. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 알람 envelope 계약 + 전송 채널 + 모듈 emitter(CRITICAL 집합) + trex-notifier P1(수집·등급·상관/중복억제·텔레그램·하트비트). -- Out of Scope - - **kis-data-hub emitter** — 사장님 결정(2026-06-21) kdh는 당분간 trex-notifier 미사용 → 알람 범위에서 제외(REQ-057 Deprecated). 모듈 emitter는 **core/orb/tsl만**. (허브 장애는 orb `kdh_auth_failure`·tsl `kis_auth_failure_abort`가 소비자 측에서 탐지하므로 가시성 부분 유지.) 향후 재도입 시 새 REQ. - - **인바운드 명령 게이트**(텔레그램으로 시스템 제어) — P2/P3 후속 별도 discovery(보안·인가·감사). - - **로그 저장/Loki 적재** — 인프라 슈퍼(Vector/Promtail) 담당. notifier 경유 금지. - - 인벤토리 ~100개 code 전량 배선 — CRITICAL 우선, WARN 다이제스트는 점진. INFO 제외. - - 후보 C(core가 외부 푸시 직접 호출), 기존 slots RPC/trex.events 스키마 변경. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. - -## 리스크 -- R-1 (Medium): 알람 폭풍 — 장애 시 다모듈·매사이클 알람이 쏟아져 알람 피로 유발. - - 완화 방안: notifier 상관/중복억제 + rate-limit(FR-4) + 등급화(CRITICAL만 즉시 푸시). -- R-2 (Medium): 알람의 역설 — 알람 채널이 의존하는 인프라(RabbitMQ/notifier) 장애 시 그 경로 알람 미전달. - - 완화 방안: 하트비트(침묵=장애, FR-5). 매매 안전망(core safety-sell)은 알람과 독립이라 알람 실패가 거래 보호를 깨지 않음. -- R-3 (Low): 보안 — 봇 토큰 유출 시 알람 수신 노출(P1은 읽기 방향이라 제어 위험은 없음). - - 완화 방안: 토큰 1곳 격리(NFR-2), 화이트리스트, 인바운드 명령은 범위 밖(P3로 분리). - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 모듈들은 이미 RabbitMQ 연결을 보유(core=broker, orb/tsl=intent/slots RPC)하므로 AMQP emitter는 기존 연결 재사용으로 저비용이다. -- A-2: 알람은 저빈도 이벤트라 전용 채널의 트래픽·복잡도 부담이 작다. -- A-3: 인프라 로그 슈퍼(Vector/Promtail→Loki)는 알람과 별개로 존재/구성된다(로그 저장은 본 범위 밖). -- A-4: trex-notifier는 단일 컨테이너 내 bare 프로세스로 다른 모듈과 공존 배포된다. - -## 오픈 질문 -답을 아직 모르는 항목을 적는다. - -- OQ-1: 모듈→notifier 알람 전송 방식 — **전용 `trex.alerts` AMQP exchange**(권고: 실시간·격리·소비자 상관억제) vs **로그-tee**(슈퍼가 alert 라인 tee, 모듈측 코드 0이나 Loki 의존·지연). - - 상태: Answered — **전용 `trex.alerts` AMQP exchange로 확정**(사장님 2026-06-21). - - 처리 방안: trex-notifier는 `trex.alerts` 소비자. 각 모듈은 기존 RabbitMQ 연결로 발행. 가산·격리(orb/tsl 기존 계약 불변). trex-notifier 의존성(AMQP 클라이언트)·runtime-flows에 반영. - - 종료 조건: 충족됨. -- OQ-2: P1 즉시-푸시(CRITICAL paging) 대상 code 집합 확정 — ALARM-INVENTORY.md §A 제안 집합을 사장님이 검토해 실제 폰을 울릴 항목을 확정. - - 상태: Answered — **ALARM-INVENTORY.md §A 전체(약 25개 code)를 P1 CRITICAL paging으로 채택**(사장님 2026-06-21). 항목 축소 없음 — 알람 폭풍은 항목 삭제가 아니라 notifier 상관/rate-limit(FR-4)으로 제어. - - 처리 방안: §A의 core/tsl/orb/kdh CRITICAL code 전체가 즉시-푸시 대상. delivery에서 각 모듈 emitter가 해당 code 배선. - - 종료 조건: 충족됨. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. - -- S-1: 확정된 CRITICAL 조건 발생 시 운영자 텔레그램에 알람이 도달한다(엔드투엔드). -- S-2: 동일 근본원인의 다모듈 알람이 1건으로 묶이고, 반복 알람이 rate-limit된다(폭풍 억제). -- S-3: 데몬 침묵(크래시/정지) 시 하트비트 부재로 장애가 탐지된다. 기존 wire 계약·매매 동작은 무회귀. - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: CRITICAL 조건 주입(테스트/시뮬) → 텔레그램 수신 확인 / 데이터 출처: notifier 로그 + 텔레그램 메시지. -- S-2 측정 방식: 다모듈 동시 알람·반복 알람 주입 → 묶임/억제 단언 / 데이터 출처: notifier 상관·rate-limit 테스트. -- S-3 측정 방식: 데몬 하트비트 중단 시뮬 → 장애 알람 발생 단언 + 기존 wire/매매 회귀 스위트 / 데이터 출처: notifier 테스트 + 각 모듈 회귀 테스트(interface-contract diff 0). - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: P1(아웃바운드 알람)만 In Scope, 명령 게이트·로그 저장 제외에 사장님 동의 — 확정(토론 합의) -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-3 측정안 합의 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: 현재 High 리스크 없음(R-1/R-2 Medium) — 폭풍억제·하트비트 책임자(notifier service-dev) 지정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1(전송=`trex.alerts` AMQP)·OQ-2(paging=§A 전체) 모두 Answered(사장님 2026-06-21) -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: confirm-discovery 시 팀장이 CONFIRMED_BY로 기록 — 팀장(Claude) - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-033_20260621_suite-alarm-delivery-notifier.md -- 참조 문서: - - /home/genichin/workspace/trex-lead/ALARM-INVENTORY.md (인벤토리 스윕 — 현재상태/요구사항 근거) - - docs/discovery/dcy-029_20260616_operational-observability-logging-and-pnl-view.md (운영 관측성 인접) - - docs/interface-contract.md, docs/runtime-flows.md (envelope/채널 계약 + 발신 흐름 baseline 갱신 대상) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. - -## DECIDE -- (해소) 알람 전송 방식 = 전용 `trex.alerts` AMQP exchange — 사장님 확정(2026-06-21, OQ-1). -- (해소) P1 CRITICAL paging 집합 = ALARM-INVENTORY.md §A 전체(약 25개 code) — 사장님 확정(2026-06-21, OQ-2). - -## CONFIRM -- (확정) P1(아웃바운드 알람)만 범위, 인바운드 명령 게이트·로그 저장은 후속 분리 — 사장님 확정(2026-06-21). - -## DATA -- {{DATA_TELEGRAM_BOT}} — 텔레그램 봇 토큰 + 수신 chat id(화이트리스트). delivery 단계 필요(사장님 BotFather 생성). - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: confirm-discovery 완료. OQ-1(전송=`trex.alerts` AMQP)·OQ-2(paging=§A 전체) 모두 사장님 확정 → Open OQ 0건. 범위(P1 아웃바운드)·성공기준·리스크 책임자 확정. DATA(텔레그램 봇 토큰)는 e2e 단계 필요(비차단). **알람은 M2 포함 확정(사장님 2026-06-21) — delivery 착수.** (※이전 "M2 이후 큐잉"은 팀장 오판 정정. 원합의=가능하면 M2 포함이었고 BAT-048 완료로 착수 시점 도달. 알람은 best-effort·별도 채널이라 매매 위험 0.) -- Ready for REQ Drafting: true -- Confirmed By: 팀장(Claude) -- Confirmed At (KST): 2026-06-21 (KST) diff --git a/docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md b/docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md deleted file mode 100644 index 0e795a8..0000000 --- a/docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md +++ /dev/null @@ -1,228 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart -- 이슈명: 슬롯↔실계좌 차단형 reconcile + sync/verify 명령 — 중장기 cold-start (기존 보유 포지션 onboarding) -- 작성 시각(KST): 2026-06-25 (KST) -- 마지막 갱신 시각(KST): 2026-07-14 (KST, dcy-037 HOTFIX follow-up link) -- 대체됨: 없음 -- 후속 Discovery 참조: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (REQ-105/106 operator broker truth 및 managed non-holding divergence 운영 환류) -- 생성된 REQ 참조: REQ-102 ~ REQ-108 (draft-req 2026-06-25, Proposed — confirm-req 대기) - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub 보드 `fourblocks2020/trex` #7 항목 "trex-core: 슬롯↔실계좌 차단형 reconcile + sync/verify 명령 — 중장기 cold-start (M3)" (M3/P1, 설계방향 본문 보유). 워크스페이스 메모리 `m3-goal-midlong-core-instance`. - - 선행 Discovery: dcy-020(자산중심 슬롯), dcy-027(중장기 슬롯 운영·운영 프로파일), dcy-028(slot strategy-state store), dcy-029(슬롯/PnL 뷰). 본 건은 이들 위에 cold-start 동기화 갭을 정의한다. -- 해석: - - M3 메인 목표는 "중/장기 전략용 별도 trex-core 인스턴스 추가"(사장님 2026-06-25). 그 인스턴스는 **이미 실계좌에 보유 중인 중장기 종목 위에서 cold-start** 한다 — portfolio-hub에 종목·ledger(잔고)가 있고 브로커 계좌에 실보유가 있는데 **slot DB는 비어 있는 상태**에서 데몬이 처음 뜨는 상황이 정상 시나리오다. - - 현행 reconcile는 이 cold-start 갭에서 **탐지(WARN/error)만 하고 차단도 해소 경로도 없다.** 슬롯↔브로커·원장↔슬롯 불일치가 있어도 전략이 그대로 돌 수 있고(데이터 오염 위 매매 위험), 운영자가 불일치를 안전하게 정합화해 전략을 재개할 표준 명령이 없다. - - 본 Discovery는 이 갭을 "차단형 reconcile(A→B→C) + 운영자 sync/verify 명령 + 차단형 warning 상태"라는 trex-core 변경 단위로 정식화한다. (중장기 **전략 모듈 자체**·멀티데이 PnL·인스턴스 프로파일 신설은 별개 단위 — 범위 경계 참조.) - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 reconcile 자산은 **탐지 전용**이다. `reconcile_core_holdings`(REQ-062)는 A(슬롯 보유·브로커 0)만 ERROR 전이하고 B(브로커 보유·슬롯 없음)·C(수량 불일치)는 **WARN 로그만** 남긴다(`application/reconciliation.py:48-83`). `reconcile_positions`(REQ-031)는 portfolio-hub↔브로커 불일치 시 asset overlay `set_warning(pid)`를 켜지만 **in-memory·비차단**이며 docstring이 명시하듯 *"pause is the durable channel for manual holds"*(`reconciliation.py:1-8,118`). - - 즉 "불일치 시 전략을 **차단**하고, 운영자가 **명령으로 정합화해 차단을 해제**한다"는 행위는 어디에도 없다. dcy-027(중장기 슬롯 운영)은 운영 프로파일·멀티데이 리스크까지 다뤘으나 cold-start 시 기존 보유 onboarding·동기화 차단은 Out-of-Scope로 두었다. - - 신규 운영자 CLI 명령(sync/verify)과 차단형 슬롯 상태 도입은 dcy-016(운영자 CLI) 이후의 독립 능력 추가라 별도 주기로 추적하는 편이 명확하다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. - -- 현재 상태 - - **cold-start 갭**: 중장기 인스턴스가 처음 뜰 때 slot DB는 비어 있다. boot시 `snapshot_scheduler.run_once()`가 ① whoami → ② get_portfolios → ③ `store.swap` → ④ `reconcile_positions`(portfolio-hub↔브로커) → ⑤ `_reconcile_slots`(`reconcile_universe`로 INACTIVE 슬롯 생성) → ⑥ `reconcile_core_holdings`(슬롯↔브로커) 순으로 돈다(`snapshot_scheduler.py:85-146`). 그 결과 portfolio-hub 자산은 **INACTIVE 슬롯으로 생성**되지만, 실계좌에 이미 보유 중인 수량은 슬롯에 **HOLDING으로 반영되지 않는다**(슬롯 생성 기준은 자산 리스트지 브로커 잔고가 아님). 브로커가 보유한 종목은 `reconcile_core_holdings`의 케이스 B("unmanaged position")로 **WARN 로그만** 남고 슬롯은 INACTIVE에 머문다. - - **차단 부재**: 위 불일치(B 미반영, C 수량 차이, asset warning)는 전략 수행을 막지 않는다. SlotState는 5상태(inactive/pending_buy/holding/pending_sell/error)뿐이고 "warning"은 슬롯 필드가 아니다(`domain/slot/state.py:30-37`). asset warning은 in-memory overlay라 재시작 시 사라지고 전략 read/intent 경로를 차단하지 않는다. 유일한 durable 차단 채널은 **pause**(portfolio_id 단위, `snapshot_pause` 테이블/pause_store)뿐인데, 이는 reconcile가 자동으로 걸지 않는 **수동 채널**이다. - - **해소 명령 부재**: 운영자가 "슬롯을 실계좌에 맞춰라"라고 지시할 표준 CLI가 없다. 현 `trex-core` CLI는 pause/resume/paused/universe/slots/enter/exit/cancel/report뿐이다(`runtime/cli.py`). 불일치를 손으로 정합화하려면 원장 수정(pfhub) 또는 슬롯 직접 손질에 의존해야 하고, 정합 후 "warning 해제·전략 재개"를 안전하게 확인하는 경로가 없다. - - **결과 위험**: 평단 0/수량 불일치 슬롯 위에서 중장기 전략이 매도·증액 결정을 내리면(예: tsl 류 평단 기반 tier, 사이징) 잘못된 cost basis로 실거래가 나간다. dcy-032에서 평단 0 오염의 하류 피해(tier 무시 폴백 매도, 실현손익 과대)를 이미 확인했다 — cold-start 미정합은 그 위험을 인스턴스 기동 시점에 구조적으로 만든다. -- 기대 상태 - - 중장기 인스턴스 cold-start 시, portfolio-hub 자산은 슬롯 유니버스로 정합되고(A), 실계좌 보유는 **불일치로 탐지되면 해당 슬롯이 차단(전략 미수행) 상태**가 되어, 정합 전 잘못된 매매가 절대 나가지 않는다. - - 운영자는 표준 CLI로 (B) 슬롯을 브로커 잔고에 맞춰 동기화(`sync`)하고, (C) 원장↔슬롯 불일치는 portfolio-hub CLI로 원장을 고친 뒤 trex-core `verify`로 재확인해 차단을 해제한다. 강제 해제(force-clear) 없이 **재대조가 일치할 때만** 차단이 풀린다. - - 차단 채널이 pause(수동 hold)와 별개로 **reconcile 기반 자동 차단(warning)** 으로 durable하게 존재하고, 두 채널 중 하나라도 걸리면 전략이 차단된다. -- 이해관계자 - - 사장님 — M3 범위·차단/해제 정책·비가역 운영(실계좌 동기화) 승인자 - - 팀장(Claude) — Discovery/게이트 관리, cold-start 갭 추적자 - - trex-core-dev — delivery 담당 - - 운영자 — cold-start 시 sync/verify 수행 주체, 차단/해제의 최종 소비자 -- 제약사항 - - 브로커(KIS) 잔고가 reconcile의 ground truth다(invariant B, 단일 계좌). 슬롯/원장은 미러이며 권위는 브로커에 있다. - - **자동 무음 정정·자동 청산 금지**(issue #15 기조 유지). reconcile는 탐지+차단까지만 자동이고, 실제 정합(슬롯=브로커 반영, 원장 수정)은 **운영자 명령**으로만 일어난다. - - trex-core wire 계약(slots RPC 필드 등) 변경은 blast radius가 커 가급적 피한다. 차단 상태가 슬롯 read 표면에 노출돼야 한다면 그 스키마 영향은 design에서 최소화·명시한다. - - 단기 인스턴스(M1/M2 라이브: orb+tsl)에 회귀를 주지 않아야 한다 — 차단형 reconcile는 모든 인스턴스에 공통 적용되므로, 단기 인스턴스의 정상 운영(당일 슬롯·평소 일치 상태)에서 신규 차단이 오발동하지 않아야 한다. - -## 주요 사용자 시나리오 -사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. - -- 운영자(cold-start onboarding): 중장기 인스턴스를 처음 띄운다. portfolio-hub에 A·B·C 3종목이 있고 실계좌에 이미 보유 중이다. 데몬 기동 → 슬롯 유니버스 생성 + 보유 불일치 탐지로 해당 슬롯 차단(전략 정지). 운영자가 `trex-core sync`로 슬롯을 실계좌 잔고에 맞추고, 일치 확인 후 차단이 풀려 전략이 비로소 돈다. -- 운영자(원장↔슬롯 불일치, C): reconcile가 원장(portfolio-hub 스냅샷)과 슬롯/브로커 간 수량 불일치를 차단으로 표면화. 운영자가 `pfhub portfolio buy/sell/invest`로 원장을 정정 → `trex-core verify `로 재대조 → 일치 시 차단 해제, 여전히 불일치면 해제 거부(사유 출력). -- 운영자(직접 매매 phantom, B 주의): 운영자가 KIS HTS로 직접 사고판 종목이 슬롯 없이 브로커에만 있다. `sync`가 이를 슬롯에 반영할 때 방향(매니지 대상으로 편입할지/무시할지)을 잘못 잡으면 중장기 전략이 의도치 않은 포지션을 떠안는다 — 방향 정책 확인이 필요하다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. - -- 변경 1 — 차단형 reconcile(A→B→C): boot/일일 reconcile에서 불일치를 탐지하면 **해당 슬롯을 차단 상태로 전이**한다. A=portfolio-hub 자산→슬롯 유니버스 정합(기존 유지), B=슬롯↔브로커 수량 불일치→슬롯 차단, C=원장(portfolio-hub 스냅샷)↔슬롯 수량 불일치→슬롯 차단. 차단된 슬롯은 전략 수행(매수/매도/사이징 intent) 대상에서 제외된다. -- 변경 2 — 차단형 warning 상태 도입: pause(수동·durable)와 **별개의 reconcile 기반 차단 상태(warning)** 를 durable하게 도입하고, pause OR warning 중 하나라도 걸리면 전략을 차단하도록 차단 게이트를 확장한다. (저장 위치·표현 방식은 OQ.) -- 변경 3 — 운영자 sync 명령: `trex-core sync`(신규) — 슬롯 수량을 브로커 잔고(ground truth)에 맞춰 반영하고, 일치하면 해당 슬롯의 reconcile-warning을 해제한다. cold-start 시 기존 보유의 슬롯 onboarding 경로이기도 하다. -- 변경 4 — 운영자 verify 명령: `trex-core verify`(신규) — C(원장↔슬롯) 불일치로 차단된 슬롯에 대해, 운영자가 portfolio-hub CLI로 원장을 정정한 뒤 **재대조**해 일치 시 warning을 해제, 불일치면 사유와 함께 해제를 거부한다(force-clear 없음). - -## 영향 범위 -변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. - -- 코드 영향: - - `trex_core/application/reconciliation.py` — `reconcile_core_holdings`(B/C를 WARN→슬롯 차단으로 승격), `reconcile_positions`(asset warning을 durable 차단으로 연계할지 결정). - - `trex_core/application/snapshot_scheduler.py` — reconcile 순서/차단 적용 지점(`run_once`). - - 슬롯 도메인/저장소 — 차단형 warning 상태의 저장(슬롯 필드 신설 vs pause류 별도 store vs 신규 SlotState). `domain/slot/state.py`, slot repo. - - 전략 차단 게이트 — slots RPC read 표면/ intent 수용 경로(`application/messaging_handlers.py`)에서 pause에 더해 warning도 차단으로 반영. - - `trex_core/runtime/cli.py` — `sync`/`verify` 서브커맨드(데몬 RPC) 추가. -- 문서 영향: - - `docs/discovery/index.md` 신규 행 추가. - - `docs/runtime-flows.md`(reconcile/차단/해제 흐름), `docs/operations.md`(cold-start onboarding 런북) 갱신. -- 운영 영향: - - 중장기 인스턴스 cold-start 표준 절차 신설(기동→차단 확인→sync/verify→재개). - - 차단/해제가 운영자 명령 의존이므로 런북·교육 필요. pfhub 원장 정정과 trex-core verify의 2-서비스 협응 절차 명시. - -# 5. 요구사항 목록 -실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. - -## 기능 요구사항 -- FR-1: boot/일일 reconcile에서 슬롯↔브로커(B) 또는 원장↔슬롯(C) 불일치가 탐지되면 해당 슬롯을 **차단 상태(reconcile-warning)** 로 전이하고, 차단된 슬롯은 전략 read 표면/intent 수용에서 제외된다. -- FR-2: 전략 차단 게이트는 pause(수동) **또는** reconcile-warning 중 하나라도 걸리면 해당 슬롯/포트폴리오의 전략 수행을 차단한다. -- FR-3: `trex-core sync` 명령(별도 명령, OQ-2)은 **브로커 잔고(정본)를 유지한 채 대상 슬롯의 보유 수량을 브로커에 맞춰 갱신**하고(슬롯 미러를 브로커로 수렴, INACTIVE→HOLDING 전이 포함), 정합 후 일치하면 그 슬롯의 reconcile-warning을 해제한다. cold-start 시 portfolio-hub 유니버스에 있어 슬롯이 생성된 보유 종목(B)이 그 대상이다. **브로커에만 있고 portfolio-hub에 없는 종목(슬롯 부재)은 sync 대상이 아니다** — unmanaged 경고로 유지하며 자동 슬롯 생성하지 않는다(슬롯 생성 기준=portfolio-hub 유니버스, OQ-3). -- FR-4: `trex-core verify` 명령(별도 명령, read-중심, OQ-2)은 C로 차단된 슬롯을 재대조해, 일치하면 warning을 해제하고 불일치면 사유와 함께 해제를 거부한다(강제 해제 없음). 슬롯 수량을 변경하지 않는다(운영자가 portfolio-hub CLI로 원장을 이미 정정). -- FR-5: reconcile-warning 차단 상태는 **pause_store와 동형의 별도 durable store**(slot_id/pid 키)에 저장한다 — SlotState enum·전이표·slots RPC wire 계약을 변경하지 않는다(OQ-1). -- FR-6: 차단/해제 전이는 식별 가능한 로그(슬롯·심볼·원인 케이스·수량 before/after·actor)를 남기고, 고영향 불일치(예: A phantom)는 기존 알람 채널(dcy-033/trex.alerts)로 통지한다. -- FR-7(교차 서비스 의존): cold-start cost basis seed를 위해 **portfolio-hub에 "현재 보유 평단(WAC) read" 엔드포인트를 신규 추가**하고(→ **portfolio-hub `dcy-008` / `REQ-043` GET /v1/portfolios/{id}/holdings/cost-basis**, 2026-06-25 confirm), trex-core sync/onboarding가 이를 읽어 슬롯 평단을 seed한다(OQ-4). 현행 portfolio-hub는 미매도 보유분 평단 read가 없음(`pnl/realized` 매도행 `cost_basis_per_unit`만 노출). REQ-105(sync)는 이 read 가용 시 평단 seed, 미가용 시 수량만 갱신+WARN으로 graceful degrade. - -## 비기능 요구사항 -성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. - -- NFR-1: 차단형 warning 상태는 **durable**(재시작 후에도 유지)해야 한다 — in-memory overlay로 두지 않는다. -- NFR-2: 단기 인스턴스(M1/M2 라이브)의 정상(일치) 운영에서 신규 차단이 오발동하지 않아야 한다(회귀 0). 차단은 실제 불일치에서만 발생한다. -- NFR-3: reconcile/차단은 무음 자동 정정·자동 청산을 하지 않는다(issue #15 기조). 정합 행위는 운영자 명령으로만 일어난다. -- NFR-4: 가능하면 wire 계약(slots RPC 스키마) 변경을 피하거나 최소화한다. 차단 상태 노출이 필요하면 영향 범위를 design에서 명시한다. - -## 범위 경계 -이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. - -- In Scope - - 차단형 reconcile(B/C → 슬롯 차단), 차단형 warning durable 상태(별도 store, OQ-1), pause∨warning 차단 게이트. - - `trex-core sync` / `trex-core verify` 운영자 명령(별도 2명령, OQ-2) + cold-start onboarding 경로. - - 차단/해제 로깅 + 고영향 알람 연계, cold-start 운영 런북. - - (교차 서비스 의존, FR-7) portfolio-hub "현재 보유 평단(WAC) read" 엔드포인트 신규 추가 — cold-start cost basis seed용. portfolio-hub-dev 별도 REQ로 발주(trex-core REQ의 선행 의존). -- Out of Scope - - 중장기 **전략 모듈 자체**(매매 로직)와 중장기 인스턴스 TOML 프로파일 신설 — 별개 단위(dcy-027 기반 후속). - - 멀티데이 PnL 리포트 — portfolio-hub 실현 PnL API(REQ-040/041)로 별도 처리(보드 M3 항목). - - 과거 0/불일치로 기록된 원장·슬롯의 소급 일괄 정정. - - 실시간(틱 단위) 슬롯↔계좌 동기화 — 본 건은 boot/일일 reconcile + 운영자 명령 시점 동기화에 한정. - -# 6. 리스크/가정 목록 -불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. - -## 리스크 -- R-1 (Medium, OQ-3로 완화됨): B 동기화 방향 오판 — 운영자 직접 매매로 생긴 phantom 포지션을 `sync`가 무분별하게 슬롯에 편입하면 중장기 전략이 의도치 않은 포지션을 관리·매도하게 된다. - - 완화 방안(확정): `sync`는 **portfolio-hub 유니버스에 있어 슬롯이 생성된 종목만** 대상으로 하고 슬롯 qty를 브로커로 수렴한다. 브로커-only(portfolio-hub 부재, 슬롯 없음) 포지션은 sync가 건드리지 않고 unmanaged 경고로 유지 → phantom 자동 편입 경로가 구조적으로 차단된다(OQ-3). 추가로 sync는 종목/슬롯 명시 대상으로 한정(전역 일괄 금지), 적용 전 before/after 출력. -- R-2 (Medium): 차단 게이트 확장이 단기 인스턴스에 회귀를 줄 수 있다(정상 상태인데 차단). - - 완화 방안: 차단은 실제 수량 불일치에서만 발생하도록 조건을 좁히고, 단기 인스턴스 시나리오(당일 슬롯·일치) 회귀 테스트를 design/verification에 포함. -- R-3 (Medium): 차단형 warning 저장 위치 선택(슬롯 필드 vs 별도 store vs 신규 SlotState)이 slots RPC wire 계약·전이표(`state.py`)에 파급될 수 있다. - - 완화 방안: pause(별도 store, 비침습)를 선례로 우선 검토. wire 영향 최소 안을 design에서 비교. -- R-4 (Medium): cold-start 시 평단(cost basis)을 어디서 가져올지 — 브로커 잔고는 수량은 주지만 중장기 전략이 쓸 평단/취득가를 슬롯에 seed해야 한다. - - 완화 방안: portfolio-hub 단가/평단 read 가용성 확인(OQ). 부재 시 폴백(브로커 평균가/원장 재구성) 정책 design. - -## 가정 -아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. - -- A-1: 중장기 인스턴스는 기존 multi-instance 토대(dcy-009/BAT-013, instance_key별 큐/락/TOML)를 그대로 재사용한다. 본 건은 그 위의 cold-start 동기화 능력이다. -- A-2: 브로커 `get_balance()`가 cold-start 시점에 실보유 수량을 신뢰 가능하게 반환한다(ground truth). -- A-3: portfolio-hub에 중장기 종목과 ledger(잔고)가 선행 등록되어 있다(자산 리스트가 슬롯 유니버스의 기준). -- A-4: pause가 durable 차단의 검증된 선례이므로, reconcile-warning도 유사한 durable·비침습 저장 방식을 취할 수 있다. - -## 오픈 질문 (전부 Answered — 사장님 2026-06-25 confirm-discovery) -모든 OQ가 confirm-discovery에서 확정되었다. 결정 결과는 아래에 resolved 기록으로 남기고 본문(FR-3~7, 변경 1~4, R-1)에 반영했다. - -- OQ-1 (Answered): 차단형 warning 상태 저장 위치 → **(b) pause_store와 동형의 별도 durable store**(slot_id/pid 키). SlotState enum·전이표·slots RPC wire 계약 불변(blast radius 최소). 반영: FR-5. -- OQ-2 (Answered): 명령 표면 → **별도 2명령 `sync`/`verify`**(단일 --apply 통합 안 채택 안 함 — --apply 오타 시 의도치 않은 슬롯 변경 위험). sync=쓰기(슬롯 qty←브로커), verify=read-중심 재대조. 반영: FR-3/FR-4. -- OQ-3 (Answered): B 동기화 방향 → **브로커 보유 정본 유지, 슬롯 보유량을 브로커에 맞춰 갱신.** 슬롯이 있는 종목(portfolio-hub 유니버스 기반)만 sync 대상. 브로커-only(portfolio-hub 부재)는 슬롯 자동생성 안 하고 unmanaged 경고 유지(슬롯 생성 기준=portfolio-hub). 반영: FR-3, R-1. -- OQ-4 (Answered): cold-start 평단 seed → **portfolio-hub에 "현재 보유 평단(WAC) read" 엔드포인트 신규 추가** 후 trex-core가 읽어 seed. 교차 서비스 의존(별도 portfolio-hub REQ). 근거: 현행 portfolio-hub는 WAC를 내부 계산하나(`app/domain/pnl.py:calculate_realized_pnl` running_wac) 외부 노출은 `GET /v1/portfolios/{id}/pnl/realized`(REQ-040) 매도행 `cost_basis_per_unit`뿐 — 미매도 보유분 현재 평단 read 부재(팀장 확인 2026-06-25). 반영: FR-7. 브로커 평균가 단독 의존은 dcy-032(평균가 0/None 케이스)로 위험. -- OQ-5 (Answered): C(원장↔슬롯) 대조 정의 → **현행 브로커 경유 2-대조 재사용**(브로커=단일 정본 일관). reconcile_positions(원장↔브로커)+reconcile_core_holdings(슬롯↔브로커) 양쪽을 브로커 기준으로 보고 불일치에 차단만 입힌다. B sync로 슬롯=브로커가 되면 원장↔슬롯 불일치=원장↔브로커 불일치로 환원되어 verify는 reconcile_positions 재실행으로 판정 가능. 반영: 변경 1, FR-4. - -# 7. 초기 성공 기준 -이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. - -- S-1: 보유 종목이 실계좌에 있고 slot DB가 빈 cold-start 입력에서, 데몬 기동 후 불일치 슬롯이 **차단 상태**가 되고 전략 intent가 발행되지 않는다. -- S-2: `trex-core sync` 후 슬롯 수량이 브로커 잔고와 일치하고 해당 슬롯의 reconcile-warning이 해제되어 전략이 재개된다. -- S-3: C로 차단된 슬롯은 원장 정정 전 `verify`가 해제를 거부하고, 원장 정정 후 `verify`가 해제한다(강제 해제 경로 없음). -- S-4: 단기 인스턴스 정상(일치) 시나리오에서 신규 차단이 발생하지 않는다(회귀 0). - -## 측정 방식 및 데이터 출처 -각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. - -- S-1 측정 방식: cold-start 픽스처(빈 slot DB + 브로커 보유 + portfolio-hub 자산) 통합 테스트로 슬롯 차단 상태·intent 미발행 검증 / 데이터 출처: 신규 테스트. -- S-2 측정 방식: sync 명령 테스트로 슬롯 수량=브로커, warning 해제, 전략 게이트 통과 검증 / 데이터 출처: 슬롯 repo + RPC 응답. -- S-3 측정 방식: 원장 불일치 상태에서 verify 거부 → 정정 후 verify 해제 시나리오 테스트 / 데이터 출처: 신규 테스트 + 로그. -- S-4 측정 방식: 기존 단기 reconcile/실행 테스트 스위트 회귀 / 데이터 출처: 테스트 결과. - -# 8. REQ로 넘기기 전 확인 체크 -REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. - -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 전략 모듈/인스턴스 프로파일/멀티데이 PnL을 본 건에서 제외하는 데 사장님 합의 — M3 framing(메모리 m3-goal-midlong-core-instance)상 전략 모듈은 별개 단위, 멀티데이 PnL은 portfolio-hub REQ-040/041로 분리 확정(사장님 2026-06-25). portfolio-hub 평단 read만 교차 의존으로 In Scope 편입. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 측정안에 사장님/팀장 이견 없음 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(B 동기화 방향)은 OQ-3 확정(슬롯 qty←브로커, 브로커-only 자동편입 금지)으로 Medium으로 완화. 해소 책임=trex-core-dev(sync 구현 시 대상 한정·before/after 출력). -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1~5 전부 Answered(사장님 2026-06-25). Open 0건. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 팀장(Claude)이 CONFIRMED_BY로 기록. - -# 9. 파일 처리 결과 -이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. - -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md -- 참조 문서: - - docs/discovery/dcy-020_20260610_asset-centric-slot-model.md (자산중심 슬롯·유니버스 reconcile) - - docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md (중장기 슬롯 운영·운영 프로파일, cold-start Out-of-Scope 이월원) - - docs/discovery/dcy-028_20260615_slot-strategy-state-store.md (slot strategy-state — onboarding 시 seed 대상) - - trex_core/application/reconciliation.py, trex_core/application/snapshot_scheduler.py, trex_core/runtime/cli.py, trex_core/domain/slot/state.py (현재 구현 근거) - - GitHub 보드 #7 "슬롯↔실계좌 차단형 reconcile + sync/verify" 항목(설계방향 본문) - -# 10. 사용자 결정 필요 항목 요약 -사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. - -## DECIDE (전부 결정됨 — 사장님 2026-06-25) -- (확정) OQ-3 B 동기화 방향 → 브로커 정본 유지·슬롯 qty를 브로커로 수렴. 브로커-only(portfolio-hub 부재) 자동 슬롯 편입 금지(unmanaged 경고 유지). -- (확정) 범위 경계 → 중장기 전략 모듈·인스턴스 TOML 프로파일·멀티데이 PnL은 본 건에서 제외(별개 단위). portfolio-hub 평단 read는 교차 의존으로 In Scope. -- (확정) OQ-1 warning 저장=별도 durable store / OQ-2 명령=별도 2명령 sync,verify / OQ-5 C대조=현행 브로커 경유 2-대조 재사용. - -## CONFIRM (확정) -- 본 Discovery 핵심 방향(차단형 reconcile + sync/verify + durable warning)을 M3 cold-start 단위로 채택 — 사장님 2026-06-25. -- pause∨warning 차단 게이트로 전략 차단 일원화 — 채택. - -## DATA (확보됨) -- (OQ-4 확인 완료) portfolio-hub는 미매도 보유분 현재 평단 read 부재 → "현재 보유 평단(WAC) read" 신규 추가로 결정(FR-7, 교차 서비스 의존 REQ). - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: 핵심 방향(차단형 reconcile + sync/verify + durable warning) 채택, OQ-1~5 전부 Answered(사장님 2026-06-25), High 리스크(R-1) OQ-3 확정으로 Medium 완화. portfolio-hub 평단 read(FR-7)는 교차 서비스 선행 의존으로 명시 — draft-req 시 trex-core REQ와 portfolio-hub 의존 REQ를 함께 분해. Open OQ 0건 → 인계 가능. -- Ready for REQ Drafting: true -- Confirmed By: 팀장(Claude) -- Confirmed At (KST): 2026-06-25 (KST) - -# 12. 작성 가이드 -- 이 문서는 dcy 템플릿 기반으로 작성되었으며, Open Questions는 가정으로 대체하지 않고 그대로 유지한다. -- confirm-discovery 시 OQ-1~5의 처리(Answered/Deferred)와 Freeze 플래그를 갱신한다. diff --git a/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md b/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md deleted file mode 100644 index c7952cc..0000000 --- a/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md +++ /dev/null @@ -1,154 +0,0 @@ -# 0. 문서 상태 -- 상태: confirmed -- 문서 ID: dcy-035_20260626_multiday-realized-pnl-report-wac -- 이슈명: 중장기 운영 리포트 — portfolio-hub WAC 실현PnL(/pnl/realized) 연동 (멀티데이 정확 실현손익) -- 작성 시각(KST): 2026-06-26 (KST) -- 마지막 갱신 시각(KST): 2026-06-26 (KST) -- 대체됨: 없음 -- 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: REQ-109, REQ-110, REQ-111 - -# 1. 계획 상태 요약 -- 연계 문서 현황: - - 관련 TODO/메모: GitHub 보드 #7 "중장기 운영 리포트 — portfolio-hub WAC 실현PnL API(/pnl/realized) 연동 (멀티데이, M3)". M3 중장기 인스턴스 운영 도구(슬롯 동기화 dcy-034의 리포트 짝). - - 선행: dcy-029(운영 관측성·실현 PnL 뷰, REQ-088 operator report), dcy-032(체결가 누락→cost basis 오염), portfolio-hub dcy-007/REQ-040(WAC 실현 PnL API). -- 해석: - - 현행 trex-core 운영 리포트(`application/operator_report.py`, REQ-088/dcy-029)는 실현 PnL을 **당일 net 매매 현금흐름**(`Σ매도(qty·price−fee−tax) − Σ매수(qty·price+fee)`)으로 계산한다. **일중 진입·청산 전략에선 net ≈ 실현손익**이라 충분했으나(M1/M2 단기), **중장기(수일~수개월 보유)에선 cost basis(WAC)를 무시한 당일 net이 실현손익과 다르다**. - - portfolio-hub는 이미 **WAC 기반 실현 PnL**(`GET /v1/portfolios/{id}/pnl/realized`, REQ-040/041: `(매도가−running_wac)·qty−fee−tax`)을 제공한다. M3 중장기 인스턴스의 운영 리포트는 이 WAC API를 소비해 **멀티데이 정확 실현손익**을 보여야 한다. - - 본 Discovery는 그 연동을 trex-core 변경 단위로 정식화한다(portfolio-hub는 기존 API 그대로 — 무변경). - -# 2. 요구사항 판정 결과 -- 판정: 신규 -- 근거 - - dcy-029(REQ-088)는 **당일 net** 실현 PnL을 정의했다(`operator_report.py` 주석: "일중 진입·청산 전략에서는 net ≈ 실현손익"). 멀티데이 WAC 실현손익은 다른 계산 정의이며, **외부(portfolio-hub) WAC API 소비**라는 신규 연동이다. - - trex-core에 portfolio-hub `/pnl/realized` 클라이언트 메서드/포트가 없다(신규). - -# 3. 문제점의 요약 -- 현재 상태 - - `operator_report.py`(REQ-088): `realized_pnl(trades)`가 `ledger_outbox` 당일 거래로 net(매도대금−매수대금−수수료−세금)을 계산. 자산별 분해 포함. `trex-core report` CLI로 노출. - - 이 정의는 **당일 매수→당일 매도(일중)** 에서만 실현손익과 일치한다. **전일 이전 매수분을 오늘 매도**하면, 당일 net은 "오늘 매도대금 − (오늘 없는 매수)"가 되어 cost basis가 빠진 채 실현손익이 과대/왜곡된다(중장기 보유의 본질). - - portfolio-hub `/pnl/realized`(REQ-040)는 매수 누적 WAC를 cost basis로 써 매도별 정확 실현손익을 주지만, trex-core 리포트가 이를 소비하지 않는다. -- 기대 상태 - - 중장기 인스턴스 운영 리포트의 실현 PnL이 **portfolio-hub WAC API(/pnl/realized) 기준의 멀티데이 정확 실현손익**으로 표시된다(매수 cost basis 반영). - - portfolio-hub 이력 불완전 시(`data_completeness_warning`)가 리포트에 표면화된다. - - portfolio-hub 미가용 시 리포트가 안전하게 degrade(에러 표시 또는 당일 net 폴백)한다. -- 이해관계자 - - 사장님 — 리포트 정의(멀티데이 기준)·범위 결정 - - 팀장(Claude) — Discovery/게이트 - - trex-core-dev — delivery - - 운영자 — 중장기 인스턴스 손익 리포트 소비자 -- 제약사항 - - portfolio-hub `/pnl/realized`는 **매도 기준 date-filter, 전 매수 WAC 참여**(REQ-041): 즉 기간 내 *매도*의 실현손익을 주고, 그 cost basis는 기간 밖 매수도 포함. (멀티데이 정의에 부합.) - - **매수만 한 날**은 매도가 없어 realized=0 — 사장님 정의("매수만 한 경우 수수료 비용")를 충족하려면 리포트가 **수수료(비용)를 별도 표시**해야 함(realized와 분리). - - trex-core wire 계약(slots RPC) 무관(리포트는 read-only 운영 표면). portfolio-hub HTTP 호출 추가(read). - -## 주요 사용자 시나리오 -- 운영자: 중장기 인스턴스의 `trex-core report`(또는 동등)로 기간 실현손익을 본다 → 전일 매수분을 오늘 매도한 손익이 WAC 기준으로 정확히 표시(당일 net 왜곡 아님). -- 운영자: 매수만 한 날 → 실현손익 0 + 수수료 비용 표시. 매도한 날 → WAC 실현손익. 둘 다 → 매수(WAC) 반영 후 매도 실현손익. - -# 4. 이번에 정의할 변경 -- 변경 1 — portfolio-hub `/pnl/realized` 소비 포트/어댑터 신규: `PortfolioHubReadPort`(또는 신규 포트)에 realized-pnl read 메서드 + HTTP 어댑터(`GET /v1/portfolios/{id}/pnl/realized`, from/to/symbol). -- 변경 2 — 운영 리포트의 실현 PnL을 WAC 기반으로 **전 인스턴스 통일**(OQ-1 결정): `operator_report.py`가 당일 net 대신 portfolio-hub WAC 실현손익을 사용(단·중·장기 공통 — WAC는 일중에도 정확). data_completeness_warning 표면화. 매수만 한 날의 수수료 비용 별도 표시. -- 변경 3 — degrade: portfolio-hub 미가용 시 **실현손익 섹션에 에러 명시**(OQ-5 결정 — 당일 net 폴백 안 함). 나머지 리포트 섹션(보유/체결/대기주문)은 정상 출력. - -## 영향 범위 -- 코드 영향: `trex_core/adapters/portfolio/*`(realized-pnl read 어댑터 신규), `trex_core/application/portfolio.py`(포트), `trex_core/application/operator_report.py`(실현 PnL 소스 분기), `trex_core/runtime/cli.py`(report 명령 — 기간/모드 옵션 필요시). -- 문서 영향: `docs/discovery/index.md`, srs/batches. `docs/operations.md`(리포트 해석 갱신). -- 운영 영향: 리포트 실현손익 정의가 중장기 모드에서 WAC로 바뀜 — 런북에 명시. portfolio-hub 호출 의존 추가. - -# 5. 요구사항 목록 -## 기능 요구사항 -- FR-1: trex-core가 portfolio-hub `GET /v1/portfolios/{id}/pnl/realized`(from/to/symbol)를 호출하는 read 포트+어댑터를 갖는다. -- FR-2: 운영 리포트의 실현 PnL을 **전 인스턴스 공통으로** portfolio-hub WAC 실현손익 기준으로 산출·표시한다(당일 net 대체, 멀티데이 cost basis 반영). 자산별 분해 유지. -- FR-3: 매수만 한 기간(매도 0)은 실현손익 0 + **수수료 비용을 별도 항목으로 표시**(사장님 정의). -- FR-4: portfolio-hub `data_completeness_warning`(이력 불완전)을 리포트에 표면화한다. -- FR-5: portfolio-hub 미가용 시 **실현손익 섹션에 에러를 명시**하고(폴백 없음), 나머지 리포트 섹션은 정상 출력한다. - -## 비기능 요구사항 -- NFR-1: trex-core wire 계약(slots RPC) 불변 — 리포트는 read-only 운영 표면. -- NFR-2: 전 인스턴스 통일이므로, **일중 매수+매도(같은 날) 케이스에서 WAC 실현손익이 기존 당일 net과 일치**함을 회귀 테스트로 보장한다(단기 라이브 의미 보존 — WAC(=당일 매수가)·매도가 동일 정의). -- NFR-3: portfolio-hub 호출 실패가 데몬 본업(슬롯/집행)에 영향 주지 않는다(리포트 격리). - -## 범위 경계 -- In Scope - - portfolio-hub /pnl/realized 소비(포트+어댑터) + 중장기 운영 리포트 WAC 실현손익 + 매수일 수수료 표시 + completeness 경고 + degrade. -- Out of Scope - - portfolio-hub 측 변경(이미 REQ-040 제공 — 무변경). - - 미실현 손익(현재가 결합), 세무 리포트. - - 슬롯↔계좌 동기화(dcy-034 별건). - -# 6. 리스크/가정 목록 -## 리스크 -- R-1 (Medium): WAC 실현손익 소스 전환이 단기 인스턴스 리포트(당일 net)에 회귀를 줄 수 있다. - - 완화: 모드/인스턴스로 분리(단기=net 유지 or WAC 통일은 OQ-1), 회귀 테스트. -- R-2 (Low): portfolio-hub 호출 추가로 리포트가 외부 의존·지연. 미가용 시 리포트 실패. - - 완화: degrade(폴백/에러 명시, OQ-5) + 본업 격리(NFR-3). - -## 가정 -- A-1: portfolio-hub `/pnl/realized`(REQ-040/041)는 멀티데이 WAC 실현손익을 정확히 제공한다(매도 date-filter + 전 매수 WAC). -- A-2: 중장기 인스턴스는 portfolio_id별로 실현손익을 조회한다(슬롯↔portfolio_id 1:1). -- A-3: trex-core는 portfolio-hub를 세션/토큰으로 호출 가능하다(기존 whoami/get_portfolios 경로 재사용). - -## 오픈 질문 -- OQ-1: WAC 실현손익을 전 인스턴스 통일인지, 중장기 모드만인지. - - 상태: **Answered (2026-06-26, 사장님)** — **전 인스턴스 WAC 통일**(당일 net 폐기). WAC는 일중에도 정확하므로 단기 라이브도 WAC로 통일하되, NFR-2 회귀(일중 케이스 net 일치)로 의미 보존. -- OQ-2: 리포트 기간 정의 — 당일/지정 기간(from~to). /pnl/realized는 from_dt/to_dt(매도 기준). 멀티데이 리포트의 기본 기간·옵션. - - 상태: Open / 처리: design, 팀장 / 종료: 기간 모델 확정 시 Answered -- OQ-3: 매수만 한 날 "수수료 비용" 표시 방식 — 실현손익과 별도 라인(비용)으로. 수수료 출처(portfolio-hub 거래내역 REQ-039 또는 trex-core ledger). - - 상태: Open / 처리: design, 팀장 / 종료: 표시·출처 확정 시 Answered -- OQ-4: trex-core가 realized PnL을 어떻게 조회 — portfolio_id별 N콜 vs daemon 집계. (slots↔portfolio 1:1이라 N콜이 자연스러우나 종목 多면 비용.) - - 상태: Open / 처리: design, 팀장 / 종료: 조회 단위 확정 시 Answered -- OQ-5: portfolio-hub 미가용 시 degrade. - - 상태: **Answered (2026-06-26, 사장님)** — **(b) 실현손익 섹션 에러 명시**(당일 net 폴백 없음). 나머지 섹션(보유/체결/대기주문) 정상 출력. - -### 오픈 질문 처리 기준 -- `Open`/`Answered`/`Deferred`/`Dropped` 표준 처리. - -# 7. 초기 성공 기준 -- S-1: 전일 이전 매수분을 당일 매도한 시나리오에서 리포트 실현손익이 **WAC 기준 정확값**(당일 net 왜곡 아님)으로 표시된다. -- S-2: 매수만 한 기간 → 실현손익 0 + 수수료 비용 표시. -- S-3: portfolio-hub `data_completeness_warning`이 리포트에 표면화된다. -- S-4: portfolio-hub 미가용 시 리포트가 degrade(정의된 방식)하고 데몬 본업 무영향. -- S-5: 일중 매수+매도(같은 날) 케이스에서 WAC 실현손익 = 기존 당일 net (단기 의미 보존, NFR-2). - -## 측정 방식 및 데이터 출처 -- S-1/S-2: 멀티데이 픽스처(전일 매수+당일 매도, 매수만) 통합 테스트(portfolio-hub 모킹) / 신규 테스트. -- S-3/S-4: 경고 주입·미가용 모킹 테스트. -- S-5: 기존 operator_report 테스트 회귀. - -# 8. REQ로 넘기기 전 확인 체크 -- [x] 적용 범위 확정 — OQ-1 = 전 인스턴스 WAC 통일 -- [x] degrade 정책 — OQ-5 = 실현손익 섹션 에러 명시 -- [ ] 기간/수수료/조회단위 design 인계 — OQ-2/3/4 (draft-req/design 단계) -- [x] 성공지표 측정 이견 없음 -- [x] Freeze 담당자(CONFIRMED_BY) 지정 — 팀장 - -# 9. 파일 처리 결과 -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md -- 참조 문서: - - docs/discovery/dcy-029(운영 리포트 REQ-088), dcy-032(cost basis 오염) - - portfolio-hub dcy-007/REQ-040/041(WAC 실현 PnL), app/api/v1_portfolios.py:712, app/domain/pnl.py - - trex_core/application/operator_report.py(현행 당일 net), runtime/cli.py(report) - -# 10. 사용자 결정 필요 항목 요약 -## DECIDE -- (OQ-1) WAC 실현손익 적용 범위: 전 인스턴스 통일 vs 중장기 모드만. -- (OQ-5) portfolio-hub 미가용 시 degrade 방식. -## CONFIRM -- 본 건을 M3 중장기 운영 리포트(멀티데이 WAC 실현손익) 단위로 채택 — portfolio-hub 무변경, trex-core 연동. -## DATA -- (OQ-3 보조) 매수일 수수료 출처(portfolio-hub 거래내역 REQ-039 vs trex-core ledger) — 팀장 design서 확인. - -# 11. Discovery Freeze -## 섹션 참조 -- 범위: [# 5 > 범위 경계](#5-요구사항-목록) / 리스크·OQ: [# 6](#6-리스크가정-목록) / 성공: [# 7](#7-초기-성공-기준) / 인계 전 확인: [# 8](#8-req로-넘기기-전-확인-체크) / 결정: [# 10](#10-사용자-결정-필요-항목-요약) -## Freeze 플래그 -- Handoff Decision: 진행 (인계) — OQ-1/OQ-5 사장님 결정 완료, OQ-2/3/4는 draft-req/design 단계 처리 -- Handoff Rationale: portfolio-hub WAC API는 이미 존재(무변경), trex-core 연동만. 적용범위=전 인스턴스 WAC 통일, degrade=섹션 에러 명시 확정. 기간/수수료/조회단위는 REQ·design에서 구체화. -- Ready for REQ Drafting: true -- Confirmed By: 팀장 (Claude) -- Confirmed At (KST): 2026-06-26 - -# 12. 작성 가이드 -- trex-core dcy 템플릿 기반. Open Questions는 가정 대체 금지. confirm-discovery 시 OQ 처리 + Freeze 갱신. diff --git a/docs/discovery/dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md b/docs/discovery/dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md deleted file mode 100644 index a087d6e..0000000 --- a/docs/discovery/dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md +++ /dev/null @@ -1,190 +0,0 @@ -# 0. 문서 상태 -이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - -- 상태: confirmed -- 문서 ID: dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load -- 이슈명: 그날-시작 KIS 인증정보 fetch(portfolio-hub) + fail-safe 거래 로드 — 진실원천 부재 시 신규 거래 미개시, 장중 무중단 (교차 발원 portfolio-hub dcy-010) -- 작성 시각(KST): 2026-06-28 -- 마지막 갱신 시각(KST): 2026-07-14 (KST, dcy-037 HOTFIX follow-up link) -- 대체됨: 없음 -- 후속 Discovery 참조: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (fetched credential operator broker truth/token lifecycle 운영 환류) -- 생성된 REQ 참조: REQ-112, REQ-113, REQ-114, REQ-115 - -# 1. 계획 상태 요약 -현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: M3 메인(중장기 trex-core 인스턴스). 교차 발원 = portfolio-hub `dcy-010`(daemon-scoped 브로커 인증정보 소유·전달). 선행 = trex-core `dcy-027`(중장기 슬롯 운영 프로파일), `dcy-034`(cold-start reconcile) -- 해석: - - portfolio-hub가 universe·전략·KIS 인증정보의 단일 진실원천이라는 결정(사장님)에 따라, trex-core가 KIS 인증정보를 env 직접주입 대신 **그날 시작 시점에 portfolio-hub `/v1/user_info`(daemon-scoped 단일 broker)에서 fetch**해 `KISConfig`를 구성하도록 소비자측을 정의한다. - - 핵심 판단: "그날-시작 로드를 신선도 게이트로 삼아 진실원천 부재 시 신규 거래를 개시하지 않되(fail-closed), 이미 시작된 그날의 전략 실행은 장중 portfolio-hub 장애에도 중단하지 않는다"는 fail-safe 의미론을 trex-core 런타임에 어떻게 구현·경계지을지. - -# 2. 요구사항 판정 결과 -이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. - -- 판정: 신규 -- 근거 - - 기존 trex-core는 KIS를 `TREX_KIS_BROKER_*` env로만 주입하며(`runtime/config.py`), portfolio-hub에서 인증정보를 받지 않는다. portfolio-hub 연동은 원장 쓰기(outbox)와 read(whoami/portfolios)만 존재. - - "그날-시작 fail-safe 로드 게이트"는 기존 어느 REQ도 정의한 바 없는 신규 런타임 동작이며, 외부 서비스(portfolio-hub) 의존을 매매 임계 경로 경계에 도입하는 구조 변경이라 독립 주기로 추적한다. - -# 3. 문제점의 요약 -문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. - -- 현재 상태 - - `KISConfig`는 `TREX_BROKER=kis`일 때 `TREX_KIS_BROKER_BASE_URL/APP_KEY/APP_SECRET/ACCOUNT_REF` 등 env에서만 구성된다(`trex_core/runtime/config.py:257`). 인스턴스별 계좌 분리는 프로세스별 env/.env 격리에만 의존. - - portfolio-hub read 어댑터(`trex_core/adapters/portfolio/hub_read.py`)가 이미 존재 — `whoami()`가 `GET /v1/user_info`를 호출해 `daemon_id`만 읽고, `get_portfolios(daemon_id)`로 universe(클라이언트측 daemon 필터)를 받는다. 인증 토큰(`TREX_PORTFOLIO_HUB_*`) 토대 존재. - - 일일 스냅샷 리프레시(`TREX_SNAPSHOT_SCHEDULE_KST`, 기본 07:30 KST; `SnapshotScheduleConfig`)가 존재 — "그날-시작" 로드의 자연스러운 후크 후보. - - 체결 원장은 trex-core 자기 PG `ledger_outbox`(REQ-019)에 즉시 durable 기록 후 portfolio-hub로 eventual drain → portfolio-hub 장애 시에도 거래내역 무손실(지연만). -- 기대 상태 - - trex-core 인스턴스가 그날 시작 시 portfolio-hub에서 자기 daemon의 KIS 인증정보(+universe+전략+trading_enabled)를 받아 `KISConfig`를 구성하고 그날 거래를 개시한다. - - portfolio-hub가 그날-시작 시점에 부재/불가면 그날 신규 거래를 **개시하지 않는다(fail-closed)**. - - 장중 portfolio-hub 장애는 **이미 시작된 그날의 전략 실행(진입·청산 포함)을 중단시키지 않는다**. 그날 인증정보는 캐시로 운행, 원장은 outbox로 지연 흡수. -- 이해관계자 - - 사장님(fail-safe 정책·계좌 분리 방향·결재), 팀장(Discovery·거버넌스), trex-core-dev(소비자 어댑터·로드 게이트 구현), 운영자(그날-시작 로드 실패 대응) -- 제약사항 - - 라이브 단기 인스턴스(M1/M2)에 회귀 금지 — portfolio-hub 의존 도입이 기존 매매를 깨지 않아야 한다(break-glass 경로 필요). - - portfolio-hub dcy-010(daemon-scoped 단일 broker `/v1/user_info`)이 선행 의존 — 미배포 시 이 기능 비활성. - - 최민감 시크릿(app_secret)을 메모리/캐시에서 다루므로 로깅·노출 주의. - -## 주요 사용자 시나리오 -- 아침 07:30 스냅샷에서 중장기 인스턴스가 portfolio-hub fetch에 성공 → 자기 계좌로 그날 거래 개시. 11시 portfolio-hub가 잠시 다운되어도 그날 진입/청산은 캐시된 인증정보로 계속, 원장은 복구 후 drain. -- 아침 07:30에 portfolio-hub가 다운 → 인스턴스는 그날 신규 거래를 개시하지 않고 운영자에게 알람. portfolio-hub 복구 후 다음 로드 사이클에 정상화. -- 운영자가 비상 시 env로 KIS 인증정보를 직접 주입(break-glass)해 portfolio-hub 없이 인스턴스를 띄운다. - -# 4. 이번에 정의할 변경 -이번 Discovery에서 다루는 핵심 변경 항목. - -- (C1) **KISConfig 소스 확장** — env-only → "그날-시작 portfolio-hub fetch"를 1차 소스로, env를 break-glass 오버라이드로. (`hub_read` 어댑터에 broker 인증정보 read 추가) -- (C2) **그날-시작 로드 게이트(fail-closed 진입)** — 로드 시점 portfolio-hub 부재 시 그날 신규 거래(진입) 미개시. 로드 성공분으로 그날 운행. -- (C3) **장중 무중단 보장** — 그날 시작 후 portfolio-hub 장애가 이미 로드된 그날 실행(진입·청산)을 중단시키지 않음(인증정보 캐시 + 원장 outbox 의존). - -## 영향 범위 -- 코드 영향: - - `trex_core/adapters/portfolio/hub_read.py`(broker 인증정보 read 추가), `trex_core/runtime/config.py`(KISConfig 소스/우선순위), 그날-시작 로드 후크(`application/snapshot_scheduler.py` 또는 daemon 부트스트랩), 로드 게이트가 진입 admission(IntentService)와 맞물리는 지점 - - 캐시 저장소(기존 `TREX_KIS_REDIS_URL` 토큰 캐시 패턴 재사용 검토) -- 문서 영향: - - `docs/srs/`(신규 REQ), `docs/discovery/index.md`(본 문서 등록), `docs/runtime-flows.md`(그날-시작 로드 흐름 추가 검토) - - 교차: portfolio-hub `dcy-010` 계약 소비측 정합 -- 운영 영향: - - 그날-시작 로드 실패 시 운영 절차(알람·수동 개입·break-glass). 인스턴스별 service token(daemon_id 바인딩) 발급·배치. - -# 5. 요구사항 목록 - -## 기능 요구사항 -- FR-1: trex-core가 그날-시작 시점에 service token으로 portfolio-hub `/v1/user_info`(daemon-scoped 단일 broker)를 호출해 KIS 인증정보(account_ref + app_key/app_secret + credential_version)를 받아 `KISConfig`를 구성한다. **그날-시작 체크포인트는 horizon별로 분리(결정 2026-06-28)**: 단기 = 07:30 KST 스냅샷 1회, 중장기 = dcy-027 운영 프로파일 기반 리밸런싱 주기. 그날 로드된 인증정보는 **그날 고정**이며 장중 portfolio-hub 복구 시에도 재fetch하지 않는다(결정론). -- FR-2: 그날-시작 로드 시점에 portfolio-hub가 불가(네트워크/비2xx/인증정보 부재/`trading_enabled=false`)면 그날 **신규 진입을 개시하지 않는다(fail-closed)**. 사유를 로깅하고 운영자에게 알람한다. -- FR-3: 그날 시작 후 발생한 portfolio-hub 장애는 이미 로드된 그날 실행을 중단시키지 않는다. 그날 인증정보는 캐시로 유지되어 진입·청산이 계속된다. -- FR-4: 평시에는 **portfolio-hub fetch가 1차 소스**이고, env(`TREX_KIS_BROKER_*`)는 **명시적 break-glass 오버라이드**다(결정 2026-06-28). 즉 fetch를 우선 시도하되, 운영자가 의도적으로 env break-glass를 활성화하면 portfolio-hub 없이도 인스턴스를 띄울 수 있다. 단기 인스턴스(M1/M2)는 회귀 차단을 위해 **당분간 fetch opt-in**(미설정 시 기존 env-only 경로 보존, NFR-1). - -## 비기능 요구사항 -- NFR-1 (회귀안전): portfolio-hub fetch 미설정/비활성 시 기존 env-only 동작이 byte-for-byte 보존된다(M1/M2 단기 인스턴스 무회귀). -- NFR-2 (보안): 캐시·로그에 app_secret 평문이 남지 않는다(마스킹). 전송은 내부망/TLS·service-token. -- NFR-3 (관측성): 그날-시작 로드 성공/실패/캐시운행 상태가 로그·카운터·알람으로 가시화된다. - -## 범위 경계 -- In Scope - - trex-core 소비자 어댑터(인증정보 read) + 그날-시작 로드 게이트(fail-closed 진입) + 장중 무중단 + break-glass. -- Out of Scope - - portfolio-hub 측 daemon-scoped 인증정보 소유·전달(= dcy-010, 선행 의존). - - universe·전략 fetch 자체의 재설계(현 get_portfolios 경로 재사용; 본 문서는 인증정보 로드와 fail-safe 게이트에 집중). - - {{DECIDE_BOOTSTRAP_UNIFY:universe+인증정보+trading_enabled를 단일 부트스트랩 호출로 통합할지 — dcy-010 OQ-3와 공동 결정}} - -# 6. 리스크/가정 목록 - -## 리스크 -- R-1 (High): portfolio-hub 의존 도입이 라이브 단기 인스턴스(M1/M2) 매매를 깨뜨림(회귀). - - 완화 방안: fetch는 opt-in. 미설정 시 env-only 경로 보존(NFR-1). break-glass 오버라이드 상시 유지. 단기 인스턴스는 당분간 env 유지 가능. -- R-2 (High): "그날-시작"의 정의가 horizon별로 달라(단기=매일 07:30 / 중장기=리밸런싱 주기) 로드 게이트 의미가 모호. - - 완화 방안: OQ-1로 horizon별 체크포인트 정의를 confirm 전 확정. 단기는 07:30 스냅샷, 중장기는 프로파일 기반 주기로 분리. -- R-3 (Medium): app_secret을 메모리/캐시에서 다뤄 노출 표면 증가. - - 완화 방안: 마스킹 로깅, 캐시 TTL/스코프 한정, Redis 사용 시 키 스코프(daemon/앱키) 격리. - -## 가정 -- A-1: portfolio-hub = universe·전략·KIS 인증정보의 단일 진실원천. (사장님 confirmed) -- A-2: 신선도 게이트 = **그날-시작 로드 시점**. 장중 portfolio-hub 장애는 그날 실행을 중단시키지 않는다("그날의 전략 실행은 그대로 유지"). (사장님 confirmed 2026-06-28) -- A-3: portfolio-hub `dcy-010`(daemon-scoped 단일 broker `/v1/user_info`)이 선행 제공된다. -- A-4: env break-glass 오버라이드는 유지된다(비상 경로). -- A-5: 그날 거래내역은 trex-core `ledger_outbox`로 무손실 흡수되므로 장중 portfolio-hub 장애가 원장 유실로 이어지지 않는다. - -## 오픈 질문 -- OQ-1: "그날-시작" 로드 체크포인트 정의 (horizon별). - - 상태: **Answered (2026-06-28)** - - 결정: 단기 = 07:30 KST 스냅샷 1회, 중장기 = dcy-027 운영 프로파일 기반 리밸런싱 주기로 분리. 본문 FR-1에 반영. - - 반영 위치: §5 FR-1. -- OQ-2: 장중 portfolio-hub 복구 시 재fetch vs 그날 고정. - - 상태: **Answered (2026-06-28)** - - 결정: **그날 고정(결정론)** — 장중 재fetch 안 함. 본문 FR-1에 반영. - - 반영 위치: §5 FR-1. -- OQ-3: break-glass(env)와 fetch 우선순위. - - 상태: **Answered (2026-06-28)** - - 결정: **fetch 1차, env는 명시적 break-glass 오버라이드**(평시 fetch 우선). 단기는 fetch opt-in으로 회귀 차단. 본문 FR-4에 반영. - - 반영 위치: §5 FR-4, NFR-1. -- OQ-4: 그날-시작 로드 실패 알람 경로 — trex-notifier(dcy-033) 연동 형태와 등급(CRITICAL?). - - 상태: Deferred - - 처리 방안: dcy-033 알람 envelope 계약 재사용. REQ 단계에서 구체화. - - 종료 조건: REQ 작성 시 알람 등급·채널 확정. - -# 7. 초기 성공 기준 -- S-1: portfolio-hub fetch 성공 시 그날 거래가 fetch된 계좌 인증정보로 개시됨을 검증할 수 있다. -- S-2: 그날-시작 시점 portfolio-hub 불가 시 신규 진입이 개시되지 않고(차단) 알람이 발생함이 테스트로 확인된다. -- S-3: 그날 시작 후 portfolio-hub를 내려도 그날 진입/청산이 캐시 인증정보로 계속되고 원장이 복구 후 drain됨이 검증된다. -- S-4: fetch 미설정 시 기존 env-only 동작이 회귀 없이 보존됨이 테스트로 확인된다. - -## 측정 방식 및 데이터 출처 -- S-1 측정 방식: fetch 모의 + KISConfig 구성 결과 검증 / 데이터 출처: 통합테스트 -- S-2 측정 방식: 로드 시점 portfolio-hub 불가 주입 → 진입 admission 차단 + 알람 카운터 / 데이터 출처: 통합테스트, 로그/카운터 -- S-3 측정 방식: 그날 시작 후 portfolio-hub 다운 주입 → 청산 경로 동작 + outbox drain 확인 / 데이터 출처: 통합테스트, ledger_outbox 행 -- S-4 측정 방식: fetch 미설정 환경에서 기존 테스트 그린 / 데이터 출처: 기존 회귀 스위트 - -# 8. REQ로 넘기기 전 확인 체크 -- [x] 범위(In Scope/Out of Scope) 확정 - - 기준: 부트스트랩 통합(dcy-010 OQ-3 공동)은 Out of Scope Deferred 처리 -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 기준: S-1~S-4 측정 가능성 검토 완료 -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 기준: R-1(회귀 — fetch opt-in·break-glass, trex-core-dev)·R-2(체크포인트 horizon별 정의 확정) 완화안 확정 -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 기준: OQ-1·OQ-2·OQ-3 Answered, OQ-4 Deferred 처리 완료 -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 기준: 사장님+팀장 confirm-discovery (DECIDE/CONFIRM 전건 해소) - -# 9. 파일 처리 결과 -- 처리 결과: 생성 -- 생성 경로: docs/discovery/dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md -- 참조 문서: - - portfolio-hub docs/discovery/dcy-010_20260627_daemon-scoped-broker-credential-provisioning.md (교차 발원 — 인증정보 소유·전달) - - docs/discovery/dcy-027_20260612_long-term-strategy-slot-operation.md (중장기 운영 프로파일) - - docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md (cold-start 동기화) - - trex_core/adapters/portfolio/hub_read.py / trex_core/runtime/config.py (현재 구현) - -# 10. 사용자 결정 필요 항목 요약 - -## DECIDE -- ~~그날-시작 체크포인트 (OQ-1)~~ → **RESOLVED 2026-06-28: 단기 07:30 스냅샷 / 중장기 dcy-027 프로파일 주기** -- ~~장중 재fetch (OQ-2)~~ → **RESOLVED 2026-06-28: 그날 고정(결정론)** -- ~~break-glass 우선순위 (OQ-3)~~ → **RESOLVED 2026-06-28: fetch 1차, env 명시적 break-glass** - -## CONFIRM -- ~~fail-safe 모델 (A-2)~~ → **RESOLVED: 그날-시작 fail-closed 진입 + 장중 무중단 confirmed (사장님)** -- ~~단기 fetch opt-in~~ → **RESOLVED 2026-06-28: 단기 인스턴스 당분간 env 유지(fetch opt-in)로 회귀 차단 (FR-4/NFR-1)** - -## DATA -- {{DATA_SERVICE_TOKEN_PROVISION:인스턴스별 service token(daemon_id 바인딩) 발급·배치 경로 — REQ/ops 단계에서 구체화(Deferred)}} - -# 11. Discovery Freeze -REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. - -## 섹션 참조 -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 -- Handoff Decision: REQ Drafting 진행 가능 -- Handoff Rationale: confirm-discovery 완료(2026-06-28). DECIDE 3건(OQ-1 horizon별 체크포인트·OQ-2 그날 고정·OQ-3 fetch 1차+env break-glass) 전건 Answered, CONFIRM 2건(fail-safe 모델·단기 opt-in) 해소, OQ-4(알람) Deferred. 잔여 Open 0건. **단, REQ 인계·구현은 선행 의존 portfolio-hub dcy-010(daemon-scoped 단일 broker /v1/user_info) 진척에 맞춰 조율.** -- Ready for REQ Drafting: true -- Confirmed By: 팀장(Claude) -- Confirmed At (KST): 2026-06-28 diff --git a/docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md b/docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md deleted file mode 100644 index 36c60e2..0000000 --- a/docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md +++ /dev/null @@ -1,197 +0,0 @@ -# 0. 문서 상태 - -- 상태: confirmed -- 문서 ID: dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix -- 이슈명: HOTFIX — fetched credential 운영자 broker truth 정합 및 managed non-holding divergence 분리 (#39, #40, #41) -- 작성 시각(KST): 2026-07-14 21:52:01 KST -- 마지막 갱신 시각(KST): 2026-07-14 22:24:41 KST (draft-req) -- 대체됨: 없음 -- 후속 Discovery 참조: `dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding` — portfolio-hub cost-basis canonical response parsing 및 안전한 sync avg_price seed P1 follow-up -- 생성된 REQ 참조: REQ-119, REQ-120, REQ-121, REQ-122 - -# 1. 계획 상태 요약 - -- 연계 문서 현황: - - 관련 점수 파일: 미생성 - - 관련 TODO/메모: GitHub `fourblocks2020/trex-core` issue [#39](https://github.com/fourblocks2020/trex-core/issues/39), [#40](https://github.com/fourblocks2020/trex-core/issues/40), [#41](https://github.com/fourblocks2020/trex-core/issues/41). 선행 Discovery `dcy-034`(차단형 reconcile/sync/verify) 및 `dcy-036`(fetched credential day-start fail-safe). -- 해석: - - `credential_fetch_enabled=true`인 `genichin-longterm`에서 daemon, operator CLI, broker token lifecycle이 같은 계좌 잔고 truth를 보장하지 못하여 `sync`/`verify` 결과를 운영 복구 판단에 사용할 수 없다. - - 이 HOTFIX는 잘못된 broker truth를 빈 잔고로 해석하는 경로를 차단하고, 이미 관리되는 inactive/error 슬롯을 진짜 unmanaged position과 구분해 안전한 운영자 triage와 이후 sync를 가능하게 한다. - -# 2. 요구사항 판정 결과 - -- 판정: 신규 -- 근거 - - `dcy-034`/REQ-105·106은 `sync`/`verify`의 상태 동기화 의미론을 정의했지만, fetched credential 모드에서 CLI가 daemon과 동일한 credential source 및 token lifecycle을 사용해야 한다는 운영 불변식은 정의하지 않았다. - - `dcy-036`/REQ-113·115는 day-start fetched credential과 fail-safe entry gate를 다루지만, direct-DB operator CLI의 credential 재해석·token throttle 및 inactive 슬롯의 reconcile 분류는 후속 운영 환류로 독립 추적하는 편이 명확하다. - -# 3. 문제점의 요약 - -- 현재 상태 - - issue #39 재현에서 daemon은 portfolio-hub fetched credential로 계좌 A를 사용하지만, `sync`/`verify`는 `_build_broker(cfg)`로 env/config 계좌 B를 생성했다. 동일 instance의 `005930`은 daemon broker truth에서 qty=5이나 CLI dry-run은 `broker_qty_zero`로 skip했다. 계좌 식별자는 문서·로그에서 안전하게 마스킹한다. - - issue #40의 `KISBroker.get_balance()`는 token/auth HTTP 오류를 잡아 `[]`로 반환한다. fresh fetched-credential broker가 KIS token throttle(`EGW00133`)을 받으면 실제 보유가 없는 것처럼 보이며, `broker_qty_zero` 같은 상태 판단으로 이어질 수 있다. - - issue #41의 `reconcile_core_holdings()`는 `list_holding_slots(instance_key)`만 대조한다. 따라서 broker가 보유한 symbol에 대해 같은 instance의 managed slot이 `inactive` 또는 `error`로 존재해도 `unmanaged position`으로 로그를 남긴다. -- 기대 상태 - - fetched credential 모드에서 daemon과 `sync`/`verify`는 동일한 portfolio-hub credential source와 명시적인 token/broker lifecycle 정책으로 broker truth를 조회하며, 인증·token 실패는 empty balance와 절대 동치가 아니다. - - reconciliation은 `slot 없음`의 true unmanaged position과 `slot 존재 + non-holding`의 managed state divergence를 별도 code/log/운영 조치로 구분한다. -- 이해관계자 - - 사장님: hotfix 범위·production 배포 및 실계좌 `sync --live` 최종 승인자 - - 팀장: Discovery/REQ/Batch 및 release gate 관리 - - trex-core-dev: broker 구성, reconciliation, CLI 및 회귀 테스트 delivery 담당 - - qc-reviewer: credential source, throttle fail-closed, divergence 분류의 독립 검증 담당 - - 운영자: dry-run 검증 후 승인된 live sync를 수행하는 주체 -- 제약사항 - - app key/app secret/token 및 raw credential payload는 로그, JSON 출력, 테스트 fixture에 기록하지 않는다. - - `sync --live`는 외부 실계좌/DB 상태를 변경하므로 본 HOTFIX 배포 후에도 별도 운영 승인 없이는 실행하지 않는다. - - 기존 `credential_fetch_enabled=false` env-only instance와 AMQP wire 계약은 회귀 없이 유지한다. - -## 주요 사용자 시나리오 - -- 운영자가 `genichin-longterm`의 inactive slot과 broker-held `005930`을 확인한다. `sync --dry-run`은 fetched credential source로 broker qty=5를 확인하고, token 오류가 나면 0 보유로 판단하지 않고 비0 종료와 안전한 오류를 반환한다. -- daemon reconcile이 `inactive` managed slot + broker qty>0을 발견하면 `managed_non_holding_divergence`로 구조화해 경고한다. slot이 전혀 없는 broker-held symbol만 기존 `unmanaged_position` 경로를 탄다. -- hotfix QC와 release 검증 후 운영자가 dry-run 결과, portfolio-hub 장부, broker truth를 교차 확인하고 명시 승인된 단일 대상에만 `sync --live --yes`를 실행한다. - -# 4. 이번에 정의할 변경 - -- 변경 1 — daemon/CLI 공통 broker truth 해석: `credential_fetch_enabled=true`일 때 operator CLI도 portfolio-hub fetched credential을 사용하고, raw env/config credential은 명시적인 break-glass 정책 외에는 조용히 fallback하지 않는다. -- 변경 2 — token/auth 실패 fail-loud: `get_balance()`와 operator 표면에서 token issuance/auth/transport 실패를 actual empty holdings와 분리하고, 반복 조회에 대한 broker/token 재사용 정책을 명확히 한다. -- 변경 3 — managed non-holding divergence 분류: holding-only 수량 대조는 보존하되, 미매칭 broker symbol에 대해 전체 slot 존재를 확인해 `unmanaged_position`과 `managed_non_holding_divergence`를 구분한다. -- 변경 4 — baseline/운영 문서 정합: credential source, error semantics, reconcile decision code, dry-run 및 live remediation gate를 `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`에 실제 구현과 일치하게 반영한다. - -## 영향 범위 - -- 코드 영향: - - `trex_core/runtime/daemon.py`, `trex_core/runtime/cli.py`, `trex_core/application/snapshot_scheduler.py` 또는 공통 broker factory: fetched credential source 및 CLI broker 생성 경로. - - `trex_core/adapters/broker/kis.py`: token/auth failure와 actual empty balance의 구분, KIS token lifecycle. - - `trex_core/application/reconciliation.py`, SlotRepository protocol 및 PostgreSQL/InMemory 구현: non-holding managed-slot existence check와 구조화 classification. -- 문서 영향: - - `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, 신규 REQ/Batch/verification 문서. - - `dcy-034`, `dcy-036`: 본 HOTFIX follow-up 관계 기록. -- 운영 영향: - - 배포 전 dry-run에서 credential source·account reference masking·broker query 결과·분류 code를 확인한다. - - 배포 후 live sync는 single portfolio/slot 대상, 사전 dry-run 및 사장님 승인으로 제한한다. - -# 5. 요구사항 목록 - -## 기능 요구사항 - -- FR-1: `credential_fetch_enabled=true` instance의 daemon, `sync`, `verify` 및 해당 operator broker-read 경로는 동일한 portfolio-hub fetched credential source를 해석해야 한다. fetch 실패 시 명시적 break-glass가 활성화되지 않은 한 env/config credential로 조용히 fallback하지 않는다. -- FR-2: token issuance, auth, HTTP transport 또는 malformed broker response 실패는 `get_balance()==[]`로 변환하지 않는다. operator CLI는 안전하게 sanitize된 오류와 비0 종료로 중단하며, `broker_qty_zero`/sync skip/verify 결과를 만들지 않는다. -- FR-3: repeated fetched-credential broker read는 구현된 token/broker lifecycle 정책에 따라 안정적으로 동작해야 하며, fresh object 생성으로 인한 token throttle을 실제 빈 계좌로 오판하지 않아야 한다. -- FR-4: `reconcile_core_holdings()`는 broker-held symbol이 holding slot에 매칭되지 않을 때 (a) instance에 어떤 slot도 없는 `unmanaged_position`, (b) slot은 있으나 `inactive`/`error` 등 non-holding인 `managed_non_holding_divergence`를 구분해 구조화 로그/decision code를 남겨야 한다. -- FR-5: `managed_non_holding_divergence`는 자동 slot 전이, 자동 warning 해제, 자동 매매 또는 자동 청산을 유발하지 않는다. 상태 수렴은 검증된 `sync`와 기존 운영 gate를 통해서만 수행한다. -- FR-6: runtime/interface/operations baseline은 credential source 우선순위, safe error semantics, reconciliation 분류 및 operator remediation 절차를 구현과 동기화해야 한다. - -## 비기능 요구사항 - -- NFR-1 (안전성): credential, access token, app secret, raw account number 및 HTTP authorization payload는 로그·CLI JSON·테스트 artifact에 노출되지 않는다. account reference가 필요한 운영 식별은 마스킹 또는 비밀이 아닌 stable label을 사용한다. -- NFR-2 (회귀안전): `credential_fetch_enabled=false` env-only instance, 정상 holding match, holding qty mismatch, slot 없는 unmanaged position, 기존 `sync`/`verify` live double-ack 의미론은 보존한다. -- NFR-3 (관측성): source mode, safe failure code, reconciliation decision code, slot_id/portfolio_id/symbol/status가 구조화 로그로 추적 가능해야 한다. -- NFR-4 (검증): unit/integration/CLI regression에 #39·#40·#41 재현 조건과 existing reconciliation/sync/verify 경로를 포함하고, independent QC가 실제 suite를 재실행한다. - -## 범위 경계 - -- In Scope - - #39 fetched credential source mismatch, #40 token throttle/empty balance masking, #41 managed non-holding divergence 분류 및 이들의 tests/docs. - - operator dry-run evidence 및 post-release single-target remediation runbook. -- Out of Scope - - portfolio-hub credential API 계약 또는 credential 저장 모델의 재설계; 필요한 계약 변화가 발견되면 cross-service gate로 중단한다. - - `sync --live`의 자동 실행, 전역 bulk sync, unmanaged broker position 자동 slot 생성, automatic remediation/trading. - - TVR 전략 로직 변경 또는 TVR 재배포. TVR은 core truth/slot 상태가 정합된 뒤 기존 `inactive,holding` read 의미론으로 재평가한다. - -# 6. 리스크/가정 목록 - -## 리스크 - -- R-1 (High): credential source 또는 account identity를 잘못 연결하면 다른 실계좌의 broker truth를 기준으로 sync 판단을 할 수 있다. - - 완화 방안: fetched mode CLI source를 daemon과 공통화하고, dry-run에서 비밀 없는 source/account fingerprint 일치 assertion을 테스트한다. live sync는 사전 dry-run과 명시적 승인 없이는 실행하지 않는다. -- R-2 (High): auth/token 오류를 empty balance로 취급하면 false zero 기반의 잘못된 상태 수렴이 발생할 수 있다. - - 완화 방안: typed/sanitized failure를 도입하고 balance가 성공적으로 조회된 경우에만 qty=0 판단을 허용한다. throttling 재현 테스트를 추가한다. -- R-3 (Medium): non-holding slot existence check가 holding 수량 mismatch 또는 true unmanaged warning 의미론을 바꿀 수 있다. - - 완화 방안: primary holding compare를 유지한 뒤 unmatched broker symbols에만 second-pass existence lookup을 적용하고, 4-case regression matrix를 고정한다. -- R-4 (Medium): HOTFIX 배포 직후 운영자가 빠르게 live sync를 실행하면서 장부/브로커/slot 증거 교차 확인을 건너뛸 수 있다. - - 완화 방안: release runbook에 dry-run → broker/ledger cross-check → single-target explicit approval → live sync → re-reconcile 순서를 필수 gate로 기록한다. - -## 가정 - -- A-1: portfolio-hub fetched credential path는 daemon의 현재 정상 운행에서 authority source로 사용 가능하며, service-token 접근 계약은 변경하지 않는다. -- A-2: `sync`는 REQ-105의 기존 원칙대로 managed slot만 브로커 truth에 수렴시키며, slot 없는 broker holdings를 자동 편입하지 않는다. -- A-3: `genichin-longterm`의 `005930` 사례는 generic regression fixture로 추상화할 수 있고, 실제 계좌 식별값/credential은 test data에 포함하지 않는다. - -## 오픈 질문 - -- OQ-1: #39, #40, #41을 하나의 emergency delivery batch로 묶어 single release candidate로 배포할지, credential hotfix(#39/#40)와 telemetry 분류(#41)를 분리할지? - - 상태: Answered (2026-07-14, 사장님) - - 결정: #39·#40·#41을 하나의 emergency batch로 확정한다. #41은 자동 state write가 없는 additive classification으로 유지해 rollback blast radius를 제한한다. - - 종료 근거: 사장님이 2026-07-14에 단일 emergency batch를 명시 승인했다. -- OQ-2: operator CLI의 fetched credential broker/token lifecycle을 process-local reuse, shared token cache, 또는 다른 공통 provider 중 무엇으로 고정할지? - - 상태: Deferred to REQ - - 처리 방안: delivery-runner/trex-core-dev가 current KISBroker token cache·daemon reinitialization·CLI process lifetime을 분석해 secret-safe alternatives와 retry/throttle behavior를 REQ draft에 제시한다. - - 인계 조건: REQ가 token lifecycle ownership, error type, retry/no-retry policy 및 regression test seam을 명시한다. 이는 구현 설계 선택이며 Discovery 범위·성공 기준을 바꾸지 않는다. -- OQ-3: hotfix release 뒤 `genichin-longterm`의 live sync 실행 대상을 `005930` 단일 slot으로 확정할지? - - 상태: Deferred to release gate - - 처리 방안: hotfix deployment와 dry-run 결과 후 사장님이 broker/ledger/slot cross-check evidence를 보고 승인한다. - - 인계 조건: release approval에 target portfolio/slot, executed command class, before/after evidence, rollback/hold plan이 기록된다. 본 Discovery는 live sync 실행을 승인하지 않는다. - -# 7. 초기 성공 기준 - -- S-1: fetched credential enabled fixture에서 daemon-equivalent path와 `sync --dry-run`/`verify`가 동일한 masked source identity와 동일 broker holding set을 사용한다. -- S-2: KIS token throttle/auth failure fixture에서 CLI가 non-zero failure와 sanitized code를 반환하고, empty balance/`broker_qty_zero`/state write를 만들지 않는다. -- S-3: 4-case reconciliation regression이 통과한다: slot 없음→unmanaged, inactive slot+broker holding→managed_non_holding_divergence, matching holding→ok, holding qty mismatch→기존 mismatch path. -- S-4: 실제 deployed hotfix에서 approved dry-run이 `genichin-longterm` 대상 broker truth와 divergence classification을 확인하며, live sync 전에는 DB 상태가 변경되지 않는다. - -## 측정 방식 및 데이터 출처 - -- S-1 측정 방식: daemon/CLI 공통 provider와 mocked fetched credential/broker fixture의 account fingerprint·balance 결과 비교 테스트 / 데이터 출처: 신규 runtime CLI 및 broker factory tests. -- S-2 측정 방식: token issuance error를 주입한 `get_balance`/CLI test에서 exit code, sanitized error, no-write assertion 확인 / 데이터 출처: KIS adapter 및 CLI tests. -- S-3 측정 방식: `reconcile_core_holdings` application tests와 PostgreSQL/InMemory repository parity tests 실행 / 데이터 출처: `tests/application/test_holding_divergence.py` 및 추가 regression tests. -- S-4 측정 방식: release runbook에 따른 production dry-run command output, broker/ledger/slot readback, audit logs 확인 / 데이터 출처: deployment evidence 및 운영자 기록. 실계좌 write는 승인 후 별도 증거로만 수행한다. - -# 8. REQ로 넘기기 전 확인 체크 - -- [x] 범위(In Scope/Out of Scope) 확정 - - 근거: 사장님이 2026-07-14 #39/#40/#41 단일 emergency batch를 승인했고, cross-service 계약 변경은 발견 시 별도 gate로 중단한다. -- [x] 성공 지표의 측정 방식/데이터 출처 확정 - - 근거: S-1~S-4에 test seam, safe production dry-run evidence, independent QC 재실행 요구가 명시되었다. runtime 계측값은 REQ/release evidence로 수집한다. -- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - - 근거: R-1/R-2 delivery owner=delivery-runner/trex-core-dev, independent verifier=qc-reviewer, live operation approver=사장님으로 고정한다. -- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - - 근거: OQ-1 Answered, OQ-2는 REQ 설계로 Deferred, OQ-3은 release approval gate로 Deferred 상태다. -- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 - - 근거: 사장님(genichin)이 2026-07-14 HOTFIX scope를 confirm-discovery에서 승인했다. live sync 실행은 별도 release approval 대상이다. - -# 9. 파일 처리 결과 - -- 처리 결과: 생성 -- 생성 경로: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` -- 참조 문서: - - `docs/discovery/dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md` - - `docs/discovery/dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md` - - GitHub issues #39, #40, #41; `trex_core/runtime/daemon.py`, `trex_core/runtime/cli.py`, `trex_core/adapters/broker/kis.py`, `trex_core/application/reconciliation.py` - - `.stage-pilot/templates/runtime-flows.md`, `.stage-pilot/templates/project-structure.md` - -# 10. 사용자 결정 필요 항목 요약 - -## 확정된 결정 - -- HOTFIX delivery boundary: #39·#40·#41을 하나의 emergency batch로 구현·검증해 single release candidate로 관리한다 (2026-07-14, 사장님 승인). -- Production safety gate: hotfix 배포 뒤 `sync --live`는 dry-run 및 broker/ledger/slot cross-check evidence가 준비된 경우에도 사장님 명시 release approval이 있어야만 실행한다. -- Post-deploy evidence: target instance의 masked credential source, broker holdings, slot 상태, portfolio-hub ledger readback은 REQ verification 및 release gate에서 수집한다. 현재는 구현 전이므로 실제 운영 데이터는 없다. - -# 11. Discovery Freeze - -## 섹션 참조 - -- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) -- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) -- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) -- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) -- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) - -## Freeze 플래그 - -- Handoff Decision: 승인 -- Handoff Rationale: 사장님이 #39/#40/#41 단일 emergency batch scope를 승인했다. OQ-2는 REQ에서 구현 설계로 구체화하며, OQ-3은 배포 후 별도 release approval gate이므로 REQ drafting을 막지 않는다. -- Ready for REQ Drafting: true -- Confirmed By: genichin -- Confirmed At (KST): 2026-07-14 22:19:29 KST diff --git a/docs/discovery/index.md b/docs/discovery/index.md deleted file mode 100644 index 8781d27..0000000 --- a/docs/discovery/index.md +++ /dev/null @@ -1,48 +0,0 @@ -# Discovery 전체 인덱스 - - - -## Discovery 목록 - -| ID | 날짜 | 이슈명 | 문서 | 상태 | -|---|---|---|---|---| -| dcy-001_20260603_slot-pure-domain-migration | 20260603 | trex-core 베이스 수립 및 slot 순수 도메인 이식 | [Discovery](./dcy-001_20260603_slot-pure-domain-migration.md) | confirmed | -| dcy-002_20260603_slot-core-daemon-http-api | 20260603 | trex-core 슬롯 코어 데몬화 + HTTP intent API | [Discovery](./dcy-002_20260603_slot-core-daemon-http-api.md) | confirmed | -| dcy-003_20260604_broker-execution-and-outbox | 20260604 | 브로커 단일 세션 + 주문 집행 + durable outbox (불변식 B) | [Discovery](./dcy-003_20260604_broker-execution-and-outbox.md) | confirmed | -| dcy-004_20260604_portfolio-hub-ledger-integration | 20260604 | portfolio-hub ledger 연동 — 체결을 외부 원장에 기록 | [Discovery](./dcy-004_20260604_portfolio-hub-ledger-integration.md) | confirmed | -| dcy-005_20260604_portfolio-hub-asset-snapshot-api | 20260604 | portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공 | [Discovery](./dcy-005_20260604_portfolio-hub-asset-snapshot-api.md) | confirmed | -| dcy-006_20260605_strategy-module-rabbitmq-transport | 20260605 | trex-core ↔ 전략 모듈 통신 transport — RabbitMQ 도입 (스냅샷 read + intent 제출 + 이벤트) | [Discovery](./dcy-006_20260605_strategy-module-rabbitmq-transport.md) | confirmed | -| dcy-007_20260606_core-autonomous-safety-sell | 20260606 | core 자율 안전 매도 — stop-loss·시간 만기 자동 트리거 | [Discovery](./dcy-007_20260606_core-autonomous-safety-sell.md) | confirmed | -| dcy-008_20260607_emergency-stop-kill-switch | 20260607 | 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외 | [Discovery](./dcy-008_20260607_emergency-stop-kill-switch.md) | confirmed | -| dcy-009_20260607_multi-instance-rabbitmq-topology | 20260607 | 인스턴스별 독립 RabbitMQ 채널 토폴로지 — trex-core 다중 인스턴스 운영을 위한 Topic Exchange + Fanout Exchange per-instance 큐 분리 | [Discovery](./dcy-009_20260607_multi-instance-rabbitmq-topology.md) | confirmed | -| dcy-010_20260607_safety-sell-persistent-rejection-backoff | 20260607 | safety-sell 영속 거부 재시도 백오프 — 장 휴장·잔고 부족·규제 등 지속 거부 상황에서 KIS 주문 spam 방지를 위한 슬롯별 재시도 backoff/cooldown 정책 | [Discovery](./dcy-010_20260607_safety-sell-persistent-rejection-backoff.md) | confirmed | -| dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve | 20260608 | buy intent — portfolio_id를 필수 키로, symbol은 universe(SnapshotStore)에서 resolve (+ issue #11 portfolio_id 누락 체결 ledger silent skip 가시화 흡수) | [Discovery](./dcy-011_20260608_buy-intent-portfolio-id-required-symbol-resolve.md) | confirmed | -| dcy-012_20260608_sell-order-pricing-entry-price-bug | 20260608 | 매도 주문이 현재가가 아닌 entry_price(매수가) 지정가로 나가는 버그 — sell dispatch 가격 정책 부재 (issue #10) | [Discovery](./dcy-012_20260608_sell-order-pricing-entry-price-bug.md) | confirmed | -| dcy-013_20260608_kis-external-cancel-pending-sell-stuck | 20260608 | KIS 외부 주문 취소 시 슬롯이 pending_sell 에 영구히 갇힘 — cncl_yn 미파싱 (issue #9) | [Discovery](./dcy-013_20260608_kis-external-cancel-pending-sell-stuck.md) | confirmed | -| dcy-014_20260608_trading-date-configurable-timezone-kst | 20260608 | 당일 슬롯 생성 기준 시간을 KST(설정 가능)로 분리 — trading_date 타임존 분리 + 일자 경계 preallocation (issue #14) | [Discovery](./dcy-014_20260608_trading-date-configurable-timezone-kst.md) | confirmed | -| dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap | 20260608 | portfolio-hub 404(미등록 포트폴리오)가 retryable 로 분류되어 백오프·상한 없이 영구 재시도됨 (issue #13) | [Discovery](./dcy-015_20260608_ledger-outbox-permanent-error-backoff-cap.md) | confirmed | -| dcy-016_20260608_operator-cli-rpc-commands | 20260608 | 운영자 CLI 명령 확장 — universe/슬롯 확인 + enter/exit + pause/resume (데몬 RPC 통일) | [Discovery](./dcy-016_20260608_operator-cli-rpc-commands.md) | confirmed | -| dcy-017_20260608_watchdog-ownership-visibility | 20260608 | 전략 이탈로 슬롯이 내부 청산 루틴(워치독) 소유로 넘어간 상태를 가시화 — CLI/RPC vs Redis projection (issue #12) | [Discovery](./dcy-017_20260608_watchdog-ownership-visibility.md) | confirmed | -| dcy-018_20260608_ledger-response-cash-position-writeback | 20260608 | ledger write 응답의 cash/quantity 를 portfolio 스냅샷에 write-back — option D 활성화 + 수량 source 통일 (운영 환류) | [Discovery](./dcy-018_20260608_ledger-response-cash-position-writeback.md) | confirmed | -| dcy-019_20260609_slot-entry-count-and-closed-reallocation | 20260609 | slot entry_count 추가 및 closed 슬롯 재할당 정책 (issue #18) | [Discovery](./dcy-019_20260609_slot-entry-count-and-closed-reallocation.md) | confirmed | -| dcy-020_20260610_asset-centric-slot-model | 20260610 | 자산 중심 슬롯 모델 재설계 — portfolio-hub universe 와 1:1 슬롯 + 활성 슬롯 개수 제어 (일반 슬롯 풀 모델 대체) | [Discovery](./dcy-020_20260610_asset-centric-slot-model.md) | confirmed | -| dcy-021_20260610_position-sizing-target-intent | 20260610 | 포지션 사이징 — 활성 자산 슬롯 보유 수량 변경(HOLDING→HOLDING)·부분 매도·선언형 target intent (issue #1, dcy-020 OQ-4 후속) | [Discovery](./dcy-021_20260610_position-sizing-target-intent.md) | confirmed | -| dcy-022_20260610_stuck-pending-order-recovery | 20260610 | 전략 발신 intent 실행 정책 — 주문 timeout(TIF)/슬리피지 허용 + 미체결 고착 백스톱 (issue #21, dcy-020/021 모델 전제) | [Discovery](./dcy-022_20260610_stuck-pending-order-recovery.md) | confirmed | -| dcy-023_20260610_allocated-capital-dead-field-removal | 20260610 | allocated_capital 죽은 필드 제거 — `enter --capital` 옵션 + slot/intent/snapshot 계약 정리 (issue #22) | [Discovery](./dcy-023_20260610_allocated-capital-dead-field-removal.md) | confirmed | -| dcy-024_20260611_safety-sell-disable-mode | 20260611 | safety-sell 자율 청산 비활성화 모드 — per-instance TOML `safety_sell_enabled=false` 시 워커 미기동 (issue #15) | [Discovery](./dcy-024_20260611_safety-sell-disable-mode.md) | confirmed | -| dcy-025_20260611_strategy-read-surface-slots-req | 20260611 | 전략 read 표면 재설계 — `trex.snapshot.req`→`trex.slots.req` + 슬롯 상태 필터 + 슬롯 단일 행(assets[]/available_slots/watchdog[] 흡수·제거) | [Discovery](./dcy-025_20260611_strategy-read-surface-slots-req.md) | confirmed | -| dcy-026_20260612_sell-transaction-tax-and-fee-policy | 20260612 | 매도 증권거래세 누락 + 수수료 정책 정정 — `tax=0.0` 하드코딩 제거 + 단일 정책 지점 (issue #24) | [Discovery](./dcy-026_20260612_sell-transaction-tax-and-fee-policy.md) | confirmed | -| dcy-027_20260612_long-term-strategy-slot-operation | 20260612 | 중장기 매매 전략용 슬롯 운영 도입 — 구현 위치 결정 + 멀티데이 리스크 규칙 + 운영 프로파일 (issue #20, dcy-020 Out-of-Scope 이월) | [Discovery](./dcy-027_20260612_long-term-strategy-slot-operation.md) | confirmed | -| dcy-028_20260615_slot-strategy-state-store | 20260615 | Slot Strategy-State 저장소 — per-strategy opaque state store (read/write CAS/close 삭제, issue #26 C-1/C-2/C-3) | [Discovery](./dcy-028_20260615_slot-strategy-state-store.md) | confirmed | -| dcy-029_20260616_operational-observability-logging-and-pnl-view | 20260616 | 운영 관측성 — 주문 결과 로깅 + 로그 타임스탬프(KST) + 청산사유 분류 + 슬롯/PnL 조회 뷰 + 운영자 런북 (issue #29) | [Discovery](./dcy-029_20260616_operational-observability-logging-and-pnl-view.md) | confirmed | -| dcy-030_20260616_slippage-limit-price-tick-size-snapping | 20260616 | slippage 지정가 KRX 호가단위 미반영 → KIS 거부 (tick-size 스냅, 상한 준수 방향, issue #28) | [Discovery](./dcy-030_20260616_slippage-limit-price-tick-size-snapping.md) | confirmed | -| dcy-031_20260619_event-publisher-connection-reliability | 20260619 | 이벤트 발행 연결 신뢰성 — idle heartbeat 사망 후 첫 발행 유실 + DLQ 가시성 (OPS-005) | [Discovery](./dcy-031_20260619_event-publisher-connection-reliability.md) | confirmed | -| dcy-032_20260621_fill-missing-price-cost-basis-backfill | 20260621 | 체결가 누락(브로커 평균가 0/None) → 슬롯 평단·원장 cost basis 0 오염(무경고·영구) → 지정가 백필 + 로깅 + 알람 검토 | [Discovery](./dcy-032_20260621_fill-missing-price-cost-basis-backfill.md) | confirmed | -| dcy-033_20260621_suite-alarm-delivery-notifier | 20260621 | 제품군 알람 전달 체계 — 모듈 발신 + 중앙 notifier(trex-notifier) → 텔레그램(P1 아웃바운드). envelope 계약+전송(trex.alerts AMQP 확정)+등급/상관억제/하트비트 | [Discovery](./dcy-033_20260621_suite-alarm-delivery-notifier.md) | confirmed | -| dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart | 20260625 | 슬롯↔실계좌 차단형 reconcile + sync/verify 명령 — 중장기 cold-start(기존 보유 onboarding). B/C 불일치→슬롯 차단(durable warning) + pause∨warning 게이트 + 운영자 sync/verify (M3) | [Discovery](./dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart.md) | confirmed | -| dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load | 20260628 | 그날-시작 KIS 인증정보 fetch(portfolio-hub) + fail-safe 거래 로드 — 진실원천 부재 시 신규 거래 미개시, 장중 무중단 (교차 발원 portfolio-hub dcy-010) | [Discovery](./dcy-036_20260628_daystart-credential-fetch-failsafe-trading-load.md) | confirmed | -| dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix | 20260714 | HOTFIX — fetched credential 운영자 broker truth 정합 및 managed non-holding divergence 분리 (#39, #40, #41) | [Discovery](./dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md) | confirmed | -| dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding | 20260716 | P1 — portfolio-hub cost-basis response contract 정합 및 안전한 sync 평균가 seed | [Discovery](./dcy-038_20260716_cost-basis-response-contract-and-safe-sync-seeding.md) | confirmed | -| dcy-039_20260727_intent-venue-end-to-end-propagation | 20260727 | 전략 intent venue를 슬롯·주문·취소까지 전달 | [Discovery](./dcy-039_20260727_intent-venue-end-to-end-propagation.md) | confirmed | -| dcy-041_20260729_broker-uncertainty-and-adapter-retirement | 20260729 | Core 브로커 불확실 상태 구분과 로컬 KIS 어댑터 축소 (#44, #45) | [Discovery](./dcy-041_20260729_broker-uncertainty-and-adapter-retirement.md) | confirmed | -| dcy-042_20260729_shared-account-capital-allocation-policy | 20260729 | 메가 스윙과 탑50 스윙 공통 계좌 자본배분 정책 | [Discovery](./dcy-042_20260729_shared-account-capital-allocation-policy.md) | confirmed | diff --git a/docs/releases/index.md b/docs/releases/index.md deleted file mode 100644 index b4a5833..0000000 --- a/docs/releases/index.md +++ /dev/null @@ -1,22 +0,0 @@ -# Release Index - -## 목적 -- 이 문서는 release 단위의 전역 인덱스다. -- 각 release는 하나 이상의 release-candidate batch를 묶고, profile(`docs-only` | `tooling` | `app-service`)에 맞는 배포와 후속 환류를 추적한다. - -## Release Register - -| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | -| --- | --- | --- | --- | --- | --- | -| REL-001 | app-service | feedback-captured | BAT-001~008 | Discovery: orb-v1 전략 모듈, 부분 매도(#5) / Change-req: REQ-030 옵션 D, 전략 필드 실데이터, 이벤트 publisher idle 견고성, paper execution_id Redis(#4) | [rel-001](rel-001_20260606_trex-core-slot-core-baseline.md) | -| REL-002 | tooling | feedback-captured | BAT-009 | Change-req: paper execution_id Redis(#4) | [rel-002](rel-002_20260606_paper-broker-env-tuning.md) | -| REL-003 | app-service | feedback-captured | BAT-010, BAT-011, BAT-012, BAT-013 | dcy-007 (자율 안전 매도) + dcy-008 (kill-switch) + dcy-009 (다중 인스턴스 토폴로지) — 통합 릴리즈 | [rel-003](rel-003_20260607_safety-killswitch-multi-instance.md) | -| REL-004 | app-service | feedback-captured | BAT-014, BAT-015, BAT-016, BAT-017, BAT-018, BAT-019, BAT-020, BAT-021, BAT-022, BAT-023 | dcy-010 (safety-sell 쿨다운) + dcy-011~017 (buy/sell 계약·KIS 취소·trading_date TZ·ledger 백오프 버그픽스 + 운영자 CLI + 워치독 가시화) + dcy-016 후속(슬롯 상태 가시화) + dcy-018 (ledger 현금/포지션 write-back, option D) — 통합 릴리즈 | [rel-004](rel-004_20260608_safety-cooldown-bugfix-hardening-cli.md) | -| REL-005 | app-service | feedback-captured | BAT-024, BAT-025 | dcy-016 후속/issue #17 (operator enter --forced-close-at) + dcy-019/issue #18 (slot entry_count + closed 슬롯 재할당) — 통합 릴리즈 | [rel-005](rel-005_20260609_operator-forced-close-and-slot-reallocation.md) | -| REL-006 | app-service | feedback-captured | BAT-026, BAT-027, BAT-028, BAT-029, BAT-030, BAT-031, BAT-032, BAT-033, BAT-034, BAT-035, BAT-036 | dcy-020 (자산 중심 영구 슬롯 α~δ cutover) + dcy-021 (포지션 사이징/issue #1·#5) + dcy-022 (실행 정책/issue #21) + dcy-023 (allocated_capital 제거/issue #22) + dcy-024 (safety_sell_enabled/issue #15) + dcy-025 (slots.req 교체·REQ-028 Deprecated) + REQ-048·010 CHG (config-dir) + REQ-032 CHG (enter --qty/issue #23) — 통합 릴리즈 | [rel-006](rel-006_20260611_asset-centric-sizing-execution-policy.md) | -| REL-007 | app-service | feedback-captured | BAT-037, BAT-038, BAT-039, BAT-040, BAT-041 | dcy-025 (REQ-073 CHG — available_slots hint) + dcy-026/issue #24 (매도세 정정) + issue #4 (paper seq Redis) + issue #25 (slots RPC DB 필터) + dcy-027/issue #20 (중장기 인스턴스 프로파일) — 통합 릴리즈 | [rel-007](rel-007_20260612_slots-rpc-fee-paper-longterm.md) | -| REL-008 | app-service | feedback-captured | BAT-042, BAT-043, BAT-044, BAT-045, BAT-046 | dcy-028/issue #26 (slot strategy-state) + issue #27 (sell retryable) + dcy-030/issue #28 (호가단위 스냅, P0 체결 복구) + dcy-029/issue #29 (운영 관측성 — 구조화 로깅·주문결과·거부ERROR·reason+actor·PnL CLI·런북) — 통합 릴리즈 | [rel-008](rel-008_20260616_tick-snap-and-observability.md) | -| REL-009 | app-service | feedback-captured | BAT-047, BAT-048, BAT-049 | dcy-031/OPS-005 (이벤트 발행 신뢰성 — idle keepalive·재시도·DLQ 경보) + dcy-032 (체결가 0/None 백필 가드·WARN 로깅) + dcy-033 (알람 emitter + trex.alerts exchange 1단계) + dcy-017 후속(무데이터/죽은 종목 위생 런북 docs-only) — M2 통합 릴리즈. 환류: OPS-005 해소 확인, slot_filled NO_ROUTE/DLQ 신규 발견(M3 Discovery 입력) | [rel-009](rel-009_20260625_event-reliability-fill-guard-alarm-runbook.md) | -| REL-010 | app-service | released | BAT-050, BAT-051, BAT-052, BAT-053, BAT-054 | dcy-034 (차단형 reconcile + sync/verify cold-start) + dcy-035 (WAC 실현손익 운영리포트) + dcy-036 (KIS 인증정보 휴면 배포 — credential_fetch_enabled=OFF) — M3 통합 릴리즈. 배포 revision `314047b`(2026-06-30). verify ImportError 핫픽스 `51cc3cc` 포함. | [rel-010](rel-010_20260629_reconcile-coldstart-wac-report-credential-dormant.md) | -| REL-011 | app-service | released | BAT-057 | dcy-037 (operator broker truth / managed non-holding divergence hotfix) — **M5 trex-core P0 release**. Justin Ko approval (`32675`, 2026-07-16 17:22:50 KST), target `6d90f89`, rollback SHA `7845fbe`, controlled daemon restart, and read-only/dry-run smoke PASS; live sync, config activation, DB migration remain out of scope. | [rel-011](rel-011_20260716_m5_operator-broker-truth-hotfix.md) | -| REL-012 | app-service | released | BAT-058 | dcy-038 / REQ-123 (cost-basis canonical parse + unavailable WAC safe sync seed) — **긴급 consumer hotfix**. Immutable daemon payload `81a853a` deployed; manifest `869e07f`; restart and read-only/dry-run acceptance PASS. `sync --live`, config activation, DB migration remain outside rollout scope. | [rel-012](rel-012_20260717_cost-basis-safe-sync-seed-hotfix.md) | diff --git a/docs/releases/rel-001_20260606_trex-core-slot-core-baseline.md b/docs/releases/rel-001_20260606_trex-core-slot-core-baseline.md deleted file mode 100644 index f88b85c..0000000 --- a/docs/releases/rel-001_20260606_trex-core-slot-core-baseline.md +++ /dev/null @@ -1,114 +0,0 @@ -# REL-001: trex-core slot-core baseline (daemon + broker + ledger + RabbitMQ transport + portfolio-hub snapshot read) - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-001, BAT-002, BAT-003, BAT-004, BAT-005, BAT-006, BAT-007, BAT-008 - -## Scope -- Included: - - BAT-001 (REQ-001~004): slot 도메인 seed — 슬롯 상태/전이/일일 preallocate 도메인. - - BAT-002 (REQ-005~009): slot-core 데몬 골격 + 슬롯 영속(PostgreSQL single-writer). (당시 HTTP API 는 BAT-007 에서 RabbitMQ transport 로 대체됨 — 아래 Excluded 참조) - - BAT-003 (REQ-010): 데몬 dotenv config 로딩. - - BAT-004 (REQ-011~015): 브로커 실행 루프 + KIS 주문 어댑터 + 내구성 outbox handoff. - - BAT-005 (REQ-016): KIS 토큰 Redis 캐시(재기동/멀티프로세스 생존). - - BAT-006 (REQ-017~020): portfolio-hub ledger 통합(fill→ledger 기록, outbox 내구성/idempotency, baseline 정합). - - BAT-007 (REQ-021~026): 전략 모듈 ↔ trex-core RabbitMQ(AMQP) 메시지 transport. snapshot/intent/pause RPC + 이벤트 토픽 + DLQ. **HTTP /v1 API 제거.** - - BAT-008 (REQ-027~029, 031~033): portfolio-hub 스냅샷 read 레이어 — universe read 포트/어댑터, 일일 스냅샷 스케줄러(baseline), KIS 잔고 기반 포지션 정합 + warning 마스킹, Postgres pause/resume, slot overlay·view·strategy_key 자산 선별, snapshot RPC envelope + degraded 신호, baseline 3문서 정합. -- Excluded: - - **REQ-030 AC-2/AC-3 (옵션 D — ledger write `*_after` 로 cash/position 갱신)**: portfolio-hub#8 (`ledger/trades` 응답에 `cash_balance_after`/`asset_quantity_after`) 배포 대기로 **이월**. REQ-030 은 AC-1(baseline 스케줄러)만 충족하여 status **Approved 유지(Implemented 아님)**. wiring point(`SnapshotStore.update_cash_position`)는 노출되어 있어 배포 후 1줄 활성화 예정. - - **portfolio-hub `/v1/portfolios` 의 전략 필드(active_strategy_id/strategy_params) 실데이터 채움**: 실 `PortfolioItem` 미제공으로 None/`{}` 통과(REQ-027 CHG-04, REQ-029 CHG-04). 단일 전략(orb-v1, strategy_key 미사용) 단계에서 무해. 2번째 전략 도입 시 detail `strategy_set` 보강 또는 별도 change-req 로 해소. - - **전략 모듈(orb-v1) 자체**: 본 릴리즈는 trex-core 코어만 포함. 전략 모듈은 별도 Discovery/배포. - - BAT-002 가 당시 제공한 HTTP /v1 API 표면: BAT-007 에서 제거됨 — 운영 배포 표면은 RabbitMQ 기반. - -## Rollout Plan -1. 사전 점검: PostgreSQL / RabbitMQ / portfolio-hub 도달성 확인. `.env` 구성 검증(`TREX_POSTGRESQL_DSN`, `TREX_RABBITMQ_URL`, `TREX_PORTFOLIO_HUB_BASE_URL`(스킴 포함)·`TREX_PORTFOLIO_HUB_TOKEN`, `TREX_BROKER`(+ kis 시 `TREX_KIS_*`), 선택 `TREX_KIS_REDIS_URL`). -2. DB 마이그레이션 적용: `trex_core/adapters/persistence/sql/001~003`(`003_create_snapshot_pause.sql` 포함). 모두 additive. -3. 배포 대상 commit SHA 로 trex-core 설치 후 데몬 기동(6 워커 스레드: execution / ledger / intent-rpc / snapshot-rpc / pause-rpc / snapshot-scheduler). -4. 기동 직후: 큐 declare 기반 readiness 확인 → snapshot RPC(view=all/buyable/held) → pause/resume RPC → 부팅 스냅샷 `degraded=false`(live portfolio-hub) 확인. - -## Rollback Plan -- 첫 릴리즈라 되돌릴 이전 가동 버전이 없음 → 롤백 = **데몬 중지(서비스 정지)**. -- DB 마이그레이션은 additive(신규 테이블/컬럼)라 데이터 파괴 없음. 필요 시 데몬 정지 상태 유지하고 `snapshot_pause` 등 신규 테이블은 보존(드롭 불필요). -- 트리거 조건 예: 기동 실패, snapshot/pause RPC 비정상, ledger outbox 누락, 정합 검증 오탐으로 과도한 warning 마스킹. - -## Verification Checklist -- 배포 전: - - 전체 테스트 통과(인프라 게이트 포함): `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=... pytest -q`. - - `ruff check trex_core tests` → All checks passed. - - REQ-027 CHG-04 정정 계약 재검증 evidence 확인(BAT-008 verification Re-verification Addendum). - - 마이그레이션 001~003 적용 확인. -- 배포 후: - - 데몬 기동 + 6 워커 스레드 + 큐(`trex.snapshot.req`/`trex.intent.req`/`trex.pause.req`)·exchange(`trex.events`)·DLQ 선언 확인. - - snapshot RPC envelope: `assets`(배열, null 금지) + meta(`view`/`available_slots`/`degraded`/`reason`), live portfolio-hub 대상 `degraded=false` + universe 수신. - - pause RPC: pause → 응답 마스킹, resume → 복귀, 재기동 후 영속 확인. - - 브로커 실행/ledger outbox flush 동작(paper 또는 kis 모의). - - 포지션 정합 1회 수행 + 불일치 시 warning 마스킹 동작. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-06T11:18:16Z (운영자 Hermes Agent 기동) -- Completed At: - - 2026-06-06T11:18:16Z (데몬 기동 + RabbitMQ 연결 + intent→KIS→rollback 흐름 검증 완료) -- Environment: - - Host: 86ba33f2997a / Workdir: /root/release/trex-core / Runtime: `.venv/bin/trex-core-daemon` (실 KIS 계좌 연동, `broker=kis` LIVE) -- Operator: - - Hermes Agent (운영자) — evidence 본 release 문서로 전달, Claude Code 세션이 문서화/검증 보강. -- Revision or Artifact: - - 71faa23 (현재 origin/main HEAD). REL-001 코드(BAT-001~008)는 이 revision의 부분집합이며 BAT-009(REL-002)까지 포함된 빌드로 배포됨. -- Commands or Procedure: - - `.env` 로드 후 `.venv/bin/trex-core-daemon` 기동 → RabbitMQ 연결 및 워커 실행 확인 → RabbitMQ RPC 로 intent 수신 → KIS 제출 → (휴장일) 거절 → slot rollback 흐름 검증. - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - HTTP health 없음(RabbitMQ transport). 운영자 확인: 데몬 기동 후 RabbitMQ 연결 성립 로그 확인 = 큐 declare readiness 동등 신호. 개별 큐/exchange declare 완료 로그는 부분 확인(코드상 `trex.snapshot.req`/`trex.intent.req`/`trex.pause.req`/`trex.events`/`trex.events.dlq` 선언, RabbitMQ 연결 로그 확인됨). - - Startup Log Fields: - - 6 워커 스레드 구성 확인(trex-execution / trex-ledger / trex-intent-rpc / trex-snapshot-rpc / trex-pause-rpc / trex-snapshot-scheduler). 데몬 정상 기동 + RabbitMQ 연결 성공 로그. - - Request Log Fields: - - intent RPC 가 admitted 처리됨(correlation_id 기반 RPC 동작 확인). 개별 request_id 로그 필드는 부분 확인. - - Smoke or Manual Verification: - - 배포 시점 운영자 검증: RabbitMQ → intent 수신 → execution loop → KIS 실제 제출 → KIS 거절 → slot `pending_buy→idle` 복구 흐름 의도대로 동작 확인(아래 Rollback Outcome). - - 보강(동일 revision 71faa23, Claude Code 세션): snapshot RPC `degraded=false` + universe 수신 확인(이전 검증), pause→resume RPC live 검증(이 세션), paper e2e 전체 그룹(admission/read + broker-behavior) 통과. **pre-deploy 테스트 게이트 재현**: 단위 166 passed / 23 skipped, 메시징 통합 16 passed, `ruff check` All passed. (KIS sandbox 1건은 `.env=LIVE` 산물로 deselect — 코드 결함 아님.) - -### Rollback Outcome -- Rollback Required: - - no (배포/서비스 롤백은 발생하지 않음 — 데몬은 계속 정상 가동) -- Rollback Trigger: - - none (배포 차원의 롤백 트리거 없음). 참고: 운영자가 보고한 "rollback yes"는 **배포 롤백이 아니라 slot 상태 머신의 정상 거동**이다 — KIS 가 휴장일(2026-06-06 현충일)로 주문을 거절("장운영일자가 주문일과 상이합니다")하자, intent 는 admitted 였던 슬롯이 `pending_buy→idle` 로 복구된 것. broker_order_id / ledger_outbox 체결 흔적 없음 = 정상(체결 안 됨). 이는 매수 거부 경로 smoke 의 **성공 관찰**이다. -- Rollback Action: - - none (배포 유지). slot-level rollback 은 설계대로 자동 수행됨(REQ-011~015 실행 루프). - -### Release Decision Evidence -- Released Decision: - - yes (confirmed → released) -- Rationale: - - 실 환경 배포 + 데몬 정상 기동(6 워커) + RabbitMQ 연결 성공 + 핵심 slot-core 경로(RabbitMQ → intent admitted → execution loop → KIS 실제 제출 → 거절 → slot rollback) live 동작 확인. app-service profile 검증 바(큐 declare readiness=health 동등, startup/RPC 로그, live RPC smoke) 충족. pre-deploy 테스트/ruff 게이트는 동일 revision(71faa23) 재현으로 보강. - - **잔여 항목(blocker 아님, 차기 거래일 확인 대상)**: (a) ledger outbox flush(fill→ledger 기록, BAT-006/REQ-017~020)는 휴장일 거절로 **실 체결이 없어 운영 미실증**(paper e2e 에서만 검증). (b) DB 마이그레이션 001~003 적용은 명시 로그 부재 — 단 데몬 boot `apply_migrations` + intent admitted(슬롯 테이블 기록 성공)로 스키마 존재 implicit 확인. (c) 포지션 정합/warning 마스킹 개별 결과 로그 부분 확인(루프는 동작). -- Recorded By: - - Hermes Agent (released 결정) / Claude Code 세션 (evidence 문서화·게이트 보강) -- Recorded At: - - 2026-06-06T11:18:16Z (운영자 결정) / 2026-06-07 (KST, 문서 반영) - -## Feedback Handoff -> 주의: 아래 Observation Summary 의 1~2번 항목은 **배포 전(pre-deployment) paper-broker e2e/smoke 관찰**이다. REL-001 은 2026-06-06 실 환경 배포 완료로 status `released` 전환됨(Release Execution Evidence 참조). 남은 거버넌스 단계는 `capture-release-feedback`(release `feedback-captured` 전환 + 포함 batch BAT-001~008 `released` 갱신 + 아래 후속 입력 정리)이다. -> -> 배포 후(post-deployment) 잔여 확인 대상(차기 거래일): (a) **ledger outbox flush(fill→ledger)** — 휴장일 거절로 실 체결이 없어 운영 미실증(paper e2e 에서만 검증). 다음 거래일 실 체결 1건으로 확인 필요. (b) DB 마이그레이션 001~003 명시 적용 로그 확보. (c) 포지션 정합/warning 마스킹 개별 결과 로그 확보. - -- Observation Summary: - - (배포 전) paper-broker e2e (2026-06-06): admission/read 그룹(멱등성·매수 거부 no_idle_slot·매도 거부 slot_not_found·pause/resume RPC·pause 는 intent 비차단·snapshot buyable 마스킹·happy-path 이벤트 캡처)과 broker-behavior 그룹(거부 롤백 `idle→pending_buy→idle`·지연 체결 `+3.7s`·부분 체결 qty 누적 `+2.5s`) 전부 통과. snapshot/pause RPC envelope·ledger outbox·슬롯 전이 정상. - - (배포 전) 견고성 관찰: 데몬 부팅 후 오래 idle 였던 이벤트 publisher pika 연결이 **첫 발행 시 `StreamLostError(EOF)` 로 1회 끊겼다가 자동 복구**(best-effort 경로, 롤백 없음, 슬롯/ledger 정합 영향 없음). 첫 이벤트 1건 유실 가능. - - (배포 후, 2026-06-06T11:18:16Z, 운영자 Hermes Agent) 실 환경(host 86ba33f2997a, `broker=kis` LIVE) 데몬 정상 기동(6 워커) + RabbitMQ 연결 성공. 실 KIS 계좌 대상 RabbitMQ → intent admitted → execution loop → KIS 실제 제출 → KIS 거절(휴장일 "장운영일자가 주문일과 상이합니다") → slot `pending_buy→idle` 자동 복구 흐름 의도대로 동작. broker_order_id/ledger 체결 흔적 없음(정상). - - (배포 후 잔여) **ledger outbox flush(fill→ledger)는 운영 미실증** — 현충일 휴장으로 실 체결이 발생하지 않음(paper e2e 에서만 검증). 차기 거래일 실 체결 1건으로 확인 필요. 마이그레이션 001~003 명시 적용 로그·포지션 정합/warning 마스킹 개별 결과 로그도 차기 거래일 보강 대상. -- Discovery Input: - - 전략 모듈(orb-v1) Discovery — REQ-029 CHG-04 소비 참고 사항 입력. - - 부분 매도(한 슬롯 수량 일부) 능력 — 현재 미구현(전량 매도만). 신규 Discovery 후보. (GitHub #5) -- REQ Input: - - 없음 (본 release 환류에서 신규 REQ 후보 없음; 후속 작업은 Discovery/Change Request 입력 및 차기 거래일 운영 실증으로 처리) -- Change Request Input: - - REQ-030 옵션 D 활성화(portfolio-hub#8 배포 후) — cash/position `*_after` 갱신. - - 전략 필드 실데이터 채움(2번째 전략 도입 시) — REQ-027/REQ-029 연계. - - 이벤트 publisher idle 연결 견고성(BAT-007 / REQ-022·024) — 발행 전 heartbeat/재연결 또는 idle 후 첫 발행 재시도로 첫 이벤트 유실 방지. (배포 전 관찰) - - paper 모드 마지막 execution_id Redis 영속 — 재기동 시 PAPER seq 리셋에 의한 correlation_id 충돌(ON CONFLICT dedup) 회피. paper 전용 테스트 견고성(운영 KIS odno 는 고유라 무해). (GitHub #4) diff --git a/docs/releases/rel-002_20260606_paper-broker-env-tuning.md b/docs/releases/rel-002_20260606_paper-broker-env-tuning.md deleted file mode 100644 index 5f252ae..0000000 --- a/docs/releases/rel-002_20260606_paper-broker-env-tuning.md +++ /dev/null @@ -1,86 +0,0 @@ -# REL-002: paper broker 튜닝 env 노출 (e2e 시나리오 재현) - -- Status: feedback-captured -- Profile: tooling -- Included Batch: BAT-009 - -## Scope -- Included: - - BAT-009 (REQ-010 CHG-20260606-01 / AC-5): paper broker 튜닝 knob(REQ-011 AC-2 지연/부분/거부 시나리오)을 데몬 env(`TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS`)로 노출. `PaperBrokerConfig` + `DaemonConfig.from_env` 파싱 + `_build_broker` 주입 + `.env.example` 문서화. -- Excluded: - - PaperBroker 내부 체결 로직(REQ-011 에서 구현·검증, 본 release 변경 아님). - - kis broker 경로(`broker=kis` 시 paper config 무시). - - REL-001(BAT-001~008) baseline — 별도 release. - -## Rollout Plan -1. 사전 점검: 본 변경은 `broker=paper` 일 때만 동작하며 미설정 env == 기존 `PaperBroker()` 동작(byte-for-byte). 운영(`broker=kis`) 데몬에는 런타임 영향 없음. -2. 대상 commit SHA(`bat-009` 브랜치)를 main 에 병합 후 배포 대상에 설치. -3. 별도 데몬 재기동/마이그레이션 불필요(코드/설정 표면 추가뿐, DB 스키마 변경 없음). e2e 환경에서 필요 시 `TREX_PAPER_*` env 로 시나리오 구성. - -## Rollback Plan -- 되돌릴 이전 동작 = paper 기본 동작이며, `TREX_PAPER_*` 를 미설정으로 두면 본 변경 이전과 동일. 코드 롤백 필요 시 BAT-009 커밋만 revert(타 batch 비의존). -- 트리거 조건: env 파싱 회귀, 미설정 시 기본 동작 변화 징후(있을 경우). 운영 broker=kis 경로는 본 변경 미참조라 트리거 대상 아님. - -## Verification Checklist -- 배포 전: - - 단위: `pytest tests/runtime/test_config_dotenv.py` 6 passed(기존 4 + paper 기본/파싱 2). 전체 `pytest --deselect ...::test_sandbox_token_and_submit` 167 passed / 23 skipped. - - `ruff check trex_core tests` All checks passed. - - BAT-009 verification.md(Ready for Release: true, blocker 없음) 확인. -- 배포 후: - - `broker=paper` 데몬에서 `TREX_PAPER_*` 미설정 → 즉시 전량 체결·거부 없음(기존 동작) 확인. - - 필요 시 e2e: 거부 롤백(`TREX_PAPER_REJECT_SYMBOLS`)·지연(`TREX_PAPER_FILL_AFTER_POLLS`)·부분(`TREX_PAPER_QTY_PER_POLL`) 재현(BAT-009 evidence 동일). - - `broker=kis` 데몬 동작 무변화(paper config 무시) 확인. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-06 (KST) — `bat-009` → main 병합 시작 -- Completed At: - - 2026-06-06 (KST) — origin/main push 완료 -- Environment: - - main @ origin (GitHub fourblocks2020/trex-core). 코드/패키지 변경 — 별도 서비스 배포 대상 없음(tooling). -- Operator: - - Justin Ko (Claude Code 세션) -- Revision or Artifact: - - 7b6ed55 (REL-002 docs) / 2f57aad (BAT-009 코드). push: `ca75700..7b6ed55 main -> main` -- Commands or Procedure: - - `git merge --ff-only bat-009-paper-broker-env-tuning` → `git push origin main`. DB 마이그레이션·데몬 재기동 없음. - -### Profile-Specific Checks -- tooling: - - Install or Upgrade Smoke: - - `DaemonConfig.from_env()` 정상 로드(단위 test_config_dotenv 6 passed). paper 기본값 미설정 시 정상. - - CLI or Script Execution: - - 데몬 `TREX_PAPER_*` env 재기동으로 거부 롤백 / 지연(+3.7s) / 부분(+2.5s) 시나리오 재현 확인(BAT-009 verification evidence). - - Compatibility Note: - - 미설정 시 기존 `PaperBroker()` 동작 byte-for-byte(회귀 없음, 전체 167 passed). `broker=kis` 경로 paper config 미참조 → 운영 데몬 무영향. - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none (필요 시 2f57aad revert; 타 batch 비의존) - -### Release Decision Evidence -- Released Decision: - - yes (confirmed → released) -- Rationale: - - 배포 전/후 검증 충족, blocker 없음, additive·하위호환·운영 런타임 무영향 tooling 변경. main 병합 + origin push 로 변경 게시 완료. -- Recorded By: - - Justin Ko (Claude Code 세션) -- Recorded At: - - 2026-06-06 (KST) - -## Feedback Handoff -- Observation Summary: - - 소규모 tooling 릴리즈(BAT-009, paper 브로커 env 튜닝 — REQ-010 CHG-20260606-01/AC-5). additive·하위호환·운영 런타임 무영향. 배포 후 별도 운영 관찰 이슈 없음. -- Discovery Input: - - 없음 -- REQ Input: - - 없음 -- Change Request Input: - - paper 모드 마지막 execution_id Redis 영속(재기동 PAPER seq 리셋 → correlation_id 충돌 회피, GitHub #4) — paper 전용 테스트 견고성. diff --git a/docs/releases/rel-003_20260607_safety-killswitch-multi-instance.md b/docs/releases/rel-003_20260607_safety-killswitch-multi-instance.md deleted file mode 100644 index 8903966..0000000 --- a/docs/releases/rel-003_20260607_safety-killswitch-multi-instance.md +++ /dev/null @@ -1,125 +0,0 @@ -# REL-003: trex-core 통합 app-service 릴리즈 — 자율 안전 매도 + kill-switch + 다중 인스턴스 토폴로지 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-010, BAT-011, BAT-012, BAT-013 - -> 통합 릴리즈: dcy-007(자율 안전 매도) + dcy-008(kill-switch, 구 REL-004) + dcy-009(다중 인스턴스 토폴로지, 구 REL-005)를 단일 app-service 릴리즈로 묶는다. 세 기능 모두 동일 데몬·동일 배포 대상이며 함께 배포한다. 구 REL-004/REL-005 draft 는 본 릴리즈로 통합·폐기됨. 포함 batch 가 확장됐으므로 confirm-release 재수행 필요(draft 로 되돌림). - -## Scope -- Included: - - **BAT-010 (REQ-034/035/036/041)**: safety-sell 기반 — per-slot WatchdogStore(snapshot RPC last_seen_at 갱신), `stop_loss_price`/`take_profit_price` 도메인 필드 + `004_add_price_thresholds.sql`, `SlotService.sell()`(idempotent), safety-sell env 5종. - - **BAT-011 (REQ-037/038/039/040/042)**: safety-sell 트리거 — `trex-safety-sell` worker(forced_close 만기 / stop-loss·take-profit KIS 현재가 polling `FHKST01010100` / 장 마감 청산) + `update_thresholds` intent RPC + baseline 정합. - - **BAT-012 (REQ-043~047)**: 비상 정지/kill-switch — `trex.pause.req`→`trex.control.req` 제어 채널 + action dispatch, day-scoped `killswitch_exclusion`(`005_*.sql`) + store, 범위별 holding 즉시 청산 + 이력, 당일 제외 masking + 자동복귀 + `resume_kill_switch`, baseline 정합. - - **BAT-013 (REQ-048~051)**: 다중 인스턴스 토폴로지 — per-instance TOML 로더 + `--config` 필수, `instance_key` 동적 큐명(default 리터럴 하위호환), `trex.direct`(topic)/`trex.broadcast`(fanout) exchange + 바인딩, baseline 정합. -- Excluded: - - REQ-030 AC-2/3(옵션 D) — portfolio-hub#8 대기(REL-001 이월). - - pending_buy 슬롯 롤백(dcy-008 OQ-3 Deferred), 부분 수량 청산(#5), strategy 모듈(orb-v1) publish 변경(계약만 정의), WatchdogStore Redis 외부화, 전체 env TOML 일원화. - -## Rollout Plan -1. 사전 점검: PostgreSQL / RabbitMQ 도달성. 인스턴스별 TOML 준비(`instance_key`, `portfolio_hub_token`). -2. DB 마이그레이션: `004_add_price_thresholds.sql` + `005_create_killswitch_exclusion.sql`(둘 다 additive, `IF NOT EXISTS`). 데몬 boot `apply_migrations` 자동 적용(사전 적용 권장). -3. **⚠️ 파괴적 기동 변경(BAT-013/REQ-048)**: 데몬 기동에 `--config .toml` 가 **필수**. 기존 인자 없는 배포 스크립트는 반드시 `--config` 추가(누락 시 즉시 오류 종료, fallback 없음). `portfolio_hub_token` 을 `.env`→TOML 이관 후 `.env` 에서 제거. 단일 인스턴스 유지 시 `instance_key="default"`(기존 리터럴 큐명 무회귀). -4. **제어 큐 전환(BAT-012/REQ-043)**: `trex.pause.req`→`trex.control.req`. CLI 무영향, RPC pause 클라이언트는 `trex.control.req` 로 이전. old 큐 자연 소멸 허용(admin console 수동 삭제). -5. 배포 대상 commit SHA(≥ 9f83c64; BAT-010~013 포함) 설치 → 데몬 재기동 → 7 워커 스레드(execution/ledger/intent-rpc/snapshot-rpc/control-rpc/snapshot-scheduler/safety-sell) + `trex.direct`/`trex.broadcast` exchange + 인스턴스 큐 선언 확인. -6. **운영 안전 주의(중요)**: - - safety-sell(BAT-011): LIVE 에서 워치독 만료 슬롯에 자동 매도. `TREX_WATCHDOG_TIMEOUT` 을 전략 snapshot 폴링 주기보다 크게. 영속 거부 시 재시도 반복(후속 백오프 후보). - - kill_switch(BAT-012): LIVE 에서 운영자 명령 시 범위 holding 실매도. 전역 발동 의도적으로만. resume 은 매매 차단만 해제(청산 비가역). - -## Rollback Plan -- 코드 롤백: 통합 단위. BAT-010~013 커밋(a30efa8/7e23648/9f83c64) revert. 단 의존 순서(010→011→012→013) 역순. **`--config` 의존이 추가됐으므로 BAT-013 롤백 시 기동 인자를 env-only 로 되돌려야 함.** -- 데이터: `004`/`005` additive — 데이터 파괴 없음, 롤백 시 컬럼/테이블 보존. -- exchanges/binding(trex.direct/broadcast)은 additive — 잔류 무해. default 큐명 불변. -- 즉시 무력화: safety-sell 은 임계값/만기 미설정 슬롯엔 무영향, kill_switch 는 운영자 명령 시에만 동작. 긴급 시 데몬 정지. -- 트리거: 정상 운용 슬롯 오발동, 의도치 않은 청산, `--config` 누락 기동 실패, 라우팅 오배달, 제어 큐 호환 문제. - -## Verification Checklist -- 배포 전: - - 단위 `pytest -q --deselect ...::test_sandbox_token_and_submit` → **271 passed / 30 skipped**(BAT-013 시점, 누적). - - 인프라 게이트(PG+RabbitMQ) → **301 passed**(마이그레이션 001~005 round-trip + killswitch PG + directed 라우팅 격리 S-2 + broadcast dual S-3 + default 무회귀 S-4). - - `ruff check trex_core tests` → All passed. - - 실 KIS LIVE(읽기 전용): stop-loss/take-profit 시세 `get_current_price('005930')=329000`(tr `FHKST01010100`, BAT-011 CHG-04 정정 검증). - - 실데몬 e2e(paper): forced_close 청산 / 장 마감 청산 / update_thresholds RPC / kill_switch global→청산→masking→resume — 전부 PASS. - - 실 KIS LIVE SL/TP e2e: 시세→stop_loss 트리거→실 KIS 매도 제출→휴장 거부→롤백 PASS. -- 배포 후: - - 데몬 `--config` 기동 + 7 워커 + 인스턴스 큐(`trex.*.req[.{instance_key}]`) + `trex.direct`/`trex.broadcast`/control 큐 선언. - - 마이그레이션 004/005 적용 확인. - - control RPC(pause/resume/list/kill_switch/resume_kill_switch) + update_thresholds RPC 동작. - - `instance_key="default"` 무회귀 + `portfolio_hub_token` TOML 주입(S-5). - - **차기 거래일 실 KIS**: safety-sell 트리거(SL/TP·forced_close·장 마감) + kill_switch 실매도 → 상황별 에러/체결 관찰. (다중 인스턴스 운영 시) directed 격리 + broadcast manual 확인. - -### 검증 한계 / 미검증 영역 (운영자 핸드오프 — 배포 후 차기 거래일/스테이징 실증 대상) - -> 아래는 배포 차단 사유가 아니라, 운영자가 무엇이 검증됐고 무엇이 안 됐는지 알고 배포하도록 명시한 한계다. 자동화(단위 271 + 인프라 게이트 301) + paper 실데몬 e2e + 읽기전용 LIVE 시세는 통과했으나, **실 KIS 쓰기(체결) 경로**는 아래가 미실증이다. - -1. **LIVE 체결 → ledger 기록 미실증**: 모든 LIVE KIS 주문이 휴장(현충일/주말) 거부라 실제 fill 이 발생하지 않음 → `fill → ledger outbox → portfolio-hub` 경로가 실 KIS 환경에서 한 번도 실행되지 않음(REL-001 이월 잔여와 동일 성격). paper e2e 에서만 검증됨. -2. **kill_switch LIVE 미실행**: kill-switch full cycle 은 paper 데몬 e2e 로만 검증. 실 KIS `kill_switch` 실매도 제출 경로는 (SL/TP LIVE e2e 와 달리) LIVE 로 미실행. — SL/TP 와 동일한 `SlotService.sell()`→execution→KIS submit 경로를 공유하므로 간접 신뢰는 있으나 직접 LIVE 관찰은 없음. -3. **실 2-인스턴스 동시 기동 미검증**: directed 라우팅 격리(S-2)/broadcast(S-3)는 실 RabbitMQ 토폴로지(raw pika)로 확인했으나, 실제 `trex-core-daemon --config` **데몬 2개 동시 구동**으로 split-brain 방지(NFR-3)를 실증하진 않음. 현재 배포는 단일 인스턴스(`instance_key="default"`) 기준. -4. **거래일 성공 LIVE 주문 0건 + 운영 환경 최초 적용**: LIVE 주문은 전부 휴장 거부라 실 거래 세션 full-stack 성공 사례 없음. `--config` 필수화(파괴적 기동 변경)와 `005_*.sql` 마이그레이션의 **운영 `trex_core` DB 적용**은 본 배포가 최초(테스트는 `trex_core_test`). - -권장 후속(배포 후): 차기 거래일에 (a) safety-sell/kill_switch 실 체결 1건 + ledger 기록 확인, (b) 운영 DB 마이그레이션 004/005 적용 로그 확보, (c) 다중 인스턴스 도입 시 2-인스턴스 라우팅 실증. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-07 (KST) — trex-core 운영자 배포 -- Completed At: - - 2026-06-07 (KST) — daemon RUNNING + snapshot RPC live 확인 -- Environment: - - 운영 대상 trex-core. config: `/root/release/trex-core/temp/config_trex-admin-daily-trading.toml` (`instance_key = "trex-admin-daily-trading"`, `portfolio_hub_token` .env→TOML 이관 완료). 실행 세션 ID: `proc_389fe66bd134`. -- Operator: - - trex-core 운영자 -- Revision or Artifact: - - origin/main ≥ `cc063af` (BAT-010~013 포함) -- Commands or Procedure: - - `.env`→TOML `portfolio_hub_token` 이관 → `trex-core-daemon --config /root/release/trex-core/temp/config_trex-admin-daily-trading.toml` 기동. 마이그레이션 001~005 는 daemon boot `apply_migrations` 로 적용(snapshot/RPC 정상 동작으로 스키마 존재 implicit 확인; 명시 적용 로그는 미회신). - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - HTTP health 없음(RabbitMQ transport). 운영자 확인: daemon **RUNNING / non-degraded**. snapshot RPC `degraded=false`, `reason=null` = 큐 declare readiness + portfolio-hub 도달 동등 신호. - - Startup Log Fields: - - daemon 기동 + `instance_key="trex-admin-daily-trading"` 로 인스턴스 큐 선언(아래 Request 참조). 7 워커/exchange 개별 기동 로그는 미회신(부분). - - Request Log Fields: - - **instance_key 접미 큐로 RPC 동작 확인**: `trex.snapshot.req.trex-admin-daily-trading` 호출 → 정상 응답. = BAT-013 `--config` 기동(REQ-048) + 동적 큐명(REQ-049) + token TOML 주입(REQ-048 S-5) LIVE 실증. - - Smoke or Manual Verification: - - snapshot RPC envelope: `degraded=false`, `reason=null`, `available_slots=10`, `view="all"`, `asset_count=45`. universe 종목 수신 확인(예: 207940, 034020, 009540). → portfolio-hub read + snapshot read 레이어(BAT-008/REQ-027~033) + token 주입 정상. - - 미실증(검증 한계 참조, 차기 거래일): 실 KIS 체결→ledger, kill_switch LIVE 실매도, safety-sell 실 트리거, 2-인스턴스 동시 라우팅. (스냅샷은 읽기전용이라 쓰기 경로 미관찰) - -### Rollback Outcome -- Rollback Required: - - no (배포/서비스 롤백 없음 — daemon RUNNING/non-degraded 유지) -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - yes (confirmed → released) -- Rationale: - - 실 환경 배포 + daemon RUNNING/non-degraded + `--config` TOML 기동·instance_key 동적 큐(`trex.snapshot.req.trex-admin-daily-trading`)·portfolio_hub_token TOML 주입·snapshot RPC(degraded=false, universe 45종) LIVE 동작 확인. app-service 운영 확인 바(health 동등 + RPC smoke + universe 수신) 충족. 배포 전 단위 271 + 인프라 게이트 301 + 전 e2e(paper/LIVE 읽기전용) 통과 보강. - - **잔여(차기 거래일 실증, blocker 아님)**: Verification Checklist 의 "검증 한계 / 미검증 영역" 4건 — 실 KIS 체결→ledger, kill_switch LIVE, safety-sell 실 트리거, 2-인스턴스 동시 split-brain. 읽기전용 스냅샷 배포 검증 단계에선 미관찰이며 차기 거래일/다중 인스턴스 도입 시 실증. -- Recorded By: - - trex-core 운영자 (배포·live 확인) / Claude Code 세션 (evidence 문서화) -- Recorded At: - - 2026-06-07 (KST) - -## Feedback Handoff -- Observation Summary: - - (배포 후, 2026-06-07) 운영자 실 배포 — `config_trex-admin-daily-trading.toml`(instance_key=`trex-admin-daily-trading`, 세션 `proc_389fe66bd134`)로 daemon RUNNING/non-degraded. instance_key 접미 큐 `trex.snapshot.req.trex-admin-daily-trading` snapshot RPC → degraded=false, available_slots=10, asset_count=45, universe 수신(207940/034020/009540 등). `--config` 기동·동적 큐명·token TOML 주입·portfolio-hub read LIVE 실증. (쓰기 경로 4건은 검증 한계대로 차기 거래일 실증 대상) - - (배포 전, 2026-06-07) 단위 271 + 인프라 게이트 301 passed, ruff clean. - - safety-sell: 실데몬 paper e2e(forced_close/장 마감/update_thresholds) PASS, 실 KIS LIVE SL/TP e2e(시세→트리거→실매도→휴장 거부→롤백) PASS. tr_id 결함 LIVE 검증으로 포착·정정(CHG-04/05). - - kill-switch: paper control RPC e2e(kill_switch global→2 청산→masking→resume) PASS. - - multi-instance: 인프라 게이트 directed 격리(S-2)/broadcast dual(S-3)/default 무회귀(S-4) 실 RabbitMQ 확인. -- Discovery Input: - - strategy 모듈(orb-v1) routing key 갱신(`{instance_key}.{channel_type}`) — 별도 모듈 범위. -- REQ Input: - - pending_buy 슬롯 롤백(dcy-008 OQ-3 Deferred). -- Change Request Input: - - safety-sell 영속 거부 재시도 백오프/쿨다운(BAT-011 LIVE e2e 발견, 우선순위↑). - - 제어 큐 `trex.pause.req` alias 유지 기간/제거 시점 정책. - - `--config` 필수화에 따른 배포 스크립트/문서 갱신. - - 전체 설정 TOML 일원화(KIS/DSN/RABBITMQ_URL) 후속 검토. diff --git a/docs/releases/rel-004_20260608_safety-cooldown-bugfix-hardening-cli.md b/docs/releases/rel-004_20260608_safety-cooldown-bugfix-hardening-cli.md deleted file mode 100644 index c624bcb..0000000 --- a/docs/releases/rel-004_20260608_safety-cooldown-bugfix-hardening-cli.md +++ /dev/null @@ -1,154 +0,0 @@ -# REL-004: safety-sell 쿨다운 + 운영 버그픽스/하드닝 + 운영자 CLI + 슬롯 가시화·현금 write-back 통합 릴리즈 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-014, BAT-015, BAT-016, BAT-017, BAT-018, BAT-019, BAT-020, BAT-021, BAT-022, BAT-023 - -## Scope -- Included: - - BAT-014 (dcy-010 / REQ-052~054): safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고 - - BAT-015 (dcy-011 / REQ-007·018·021 change-req + REQ-055): buy intent portfolio_id 필수화 + symbol 서버 resolve + ledger skip 가시화 + closed↔outbox anti-join 점검 - - BAT-016 (dcy-012 / REQ-002·003·006·007·013·015·036 change-req): 매도 가격 버그 정정 — 슬롯 `entry_price`→`limit_price` rename(+마이그레이션 006) + dispatch 단일 필드 + sell optional 가격 - - BAT-017 (dcy-013 / REQ-012·013·014·015·018·031 change-req): KIS 외부 취소 감지(cncl_yn) + 부분 체결 회계 + pending stale 워치독(Alert-only) - - BAT-018 (dcy-014 / REQ-005·009·048 change-req): trading_date 설정 타임존(KST) 분리 + 일자 경계 일일 preallocation - - BAT-019 (dcy-015 / REQ-017·018·019·020 change-req): ledger outbox 영속 오류 분류 + 지수 백오프/dead-letter(+마이그레이션 007) - - BAT-020 (dcy-016 / REQ-021·022·026·032·034 change-req): 운영자 CLI 확장(universe/slots/enter/exit, 데몬 RPC 통일) + 워치독 억제 읽기 - - BAT-021 (dcy-017 / REQ-021·032·034·037·038·039·042 change-req): 워치독 소유권 가시화(snapshot 워치독 상태 + safety-sell 발동 로깅) - - BAT-022 (dcy-016 후속 / REQ-028·032 change-req): `trex-core slots` 가 universe `assets[]` 대신 실제 슬롯 상태를 표시 — snapshot envelope 에 read-only `slots[]`(slot_id/status/symbol/qty/limit_price/avg_price/portfolio_id) additive 추가, `universe`/`slots` 표면 분리 - - BAT-023 (dcy-018 / REQ-017·019·029·030 change-req, REQ-030 Approved→Implemented): ledger write 응답 cash/position write-back(option D 활성화) — portfolio-hub#8 응답 `cash_balance_after`/`asset_quantity_after` 파싱 후 `SnapshotStore` write-back(best-effort), 체결~write-back 공백은 추정 현금 오버레이(`cash_estimated`)로 메움. "수량 live·현금 stale" 비대칭 제거 -- Excluded: - - BAT-001~013 (REL-001/002/003 에서 이미 released) - - dcy-018 이후 후속 주제, Redis projection·push 알림(dcy-017 OQ-1 미채택), dead-letter 재처리 트리거(dcy-015 후속), 슬롯 오버레이 제거(dcy-018 OQ-1 B 로 유지 확정) - -## Rollout Plan -1. 배포 전: 대상 인스턴스 데몬 정지(또는 거래 세션 외 시간), 현재 리비전 SHA 기록. -2. DB 마이그레이션 적용(멱등): `006_rename_entry_price_to_limit_price.sql`(daily_slot 컬럼 rename) + `007_ledger_outbox_delivery_failed_and_backoff.sql`(status enum/next_retry_at). 데몬 기동 시 `apply_migrations` 가 자동 적용하나, 운영 `trex_core` DB 에 최초 적용되므로 사전 백업 권장. -3. 신 리비전(`6d7bca1` 또는 그 이후 main HEAD) 배포 + 데몬 재기동(`--config .toml`). 기동 로그에서 RUNNING/비-degraded 확인. -4. 스냅샷 RPC degraded=false·available_slots·asset_count 확인. snapshot envelope 에 `slots[]`(per-slot 상태)·`watchdog[]` 노출 확인(BAT-022). trading_date 가 KST 당일과 일치하는지 확인. -5. 운영자 CLI 스모크: `trex-core universe`(=`assets[]`)/`slots`(=실제 슬롯 상태·status·워치독 표시, BAT-022) — paper 또는 비거래 시간. -6. (가능 시) paper buy/sell 1회 후 `trex-core universe` 에서 `cash_balance`·`asset_quantity` 가 **함께** 갱신(write-back) 또는 `cash_estimated` 추정 라벨 노출 확인(BAT-023). -7. 거래 세션 재개. - -## Rollback Plan -- 조건: 기동 실패, 마이그레이션 오류, buy/sell admission·집행 회귀, ledger 전송 비정상, safety-sell 오작동. -- 절차: 신 리비전 → 직전 released 리비전(REL-003 시점)으로 롤백 + 데몬 재기동. -- 주의: 마이그레이션 006(컬럼 rename)은 비가역적 rename 이다 — 롤백 시 구 코드는 `entry_price` 컬럼을 기대하므로, 코드 롤백과 함께 역마이그레이션(`limit_price`→`entry_price` rename) 또는 컬럼 alias 가 필요하다. 007 은 additive(컬럼/enum 추가)라 구 코드와 공존 가능. 배포 전 역마이그레이션 스크립트를 준비한다. -- 데이터: 마이그레이션은 순수 rename/additive 라 행 데이터 보존. ledger_outbox `delivery_failed` 행은 롤백 후 구 코드가 모르는 status 이므로 list_pending 에서 자연 제외(무해). - -## Verification Checklist -- 배포 전: - - 전체 인프라 게이트 테스트 green (PG `trex_core_test` + RabbitMQ): BAT-023 verification 시점(main HEAD `2bfecd5`, BAT-022 `63da87c` 포함) **`385 passed`**(pending_cash_delta SQL 포함) — 확장 범위 반영 완료. (참고: 최초 confirm 시점 BAT-014~021 범위는 `369 passed`) - - `ruff check trex_core tests` All passed (BAT-022/023 변경 파일 포함). - - 마이그레이션 006/007 idempotent 재적용 확인(컬럼 계약 가드 green). BAT-022/023 은 신규 마이그레이션 없음(snapshot 필드 additive + 코드-only write-back). - - 역마이그레이션(006 rename 되돌리기) 스크립트 준비. -- 배포 후: - - 데몬 RUNNING + 비-degraded, 8개 백그라운드 스레드 기동(`trex-preallocation` 포함). - - trading_date == KST 당일, 슬롯 preallocation 정상. - - 스냅샷 RPC envelope(assets/available_slots/degraded) 정상 + `watchdog`·`slots[]` 필드 노출(BAT-022). - - `trex-core universe`(assets[])/`slots`(실제 슬롯 상태·status·워치독 소유 표시) 응답 정상. - - (가능 시) paper buy/sell 1회 e2e: portfolio_id 기반 admission → 체결 → ledger 기록 → `cash_balance`·`asset_quantity` write-back(또는 `cash_estimated` 추정 라벨) 확인(BAT-023). - -## Pre-Release Run Evidence (operator-facing smoke) - -confirm-release 전 "운영자가 쓰는 프로그램이 실제로 동작한다"는 run-the-app 증거. 테스트 통과(369 passed)와 별개로 실 데몬 기동 + `trex-core` CLI 구동을 검증했다. - -> 주: 아래 base run 증거는 BAT-014~021 범위(REL-004 최초 confirm) 시점의 것이다. BAT-022·BAT-023 편입 범위의 run/test 증거는 바로 아래 "확장 범위 증거(BAT-022/023)"에 추가했다. - -### 확장 범위 증거 (BAT-022 / BAT-023, 재-confirm) - -- **BAT-022 (슬롯 상태 가시화) — 실 인프라 운영 재현 PASS**: 신규 코드 데몬 재기동(`trex-core-daemon --config config/test.toml`, PID 2965049) 후 `trex-core slots` → `instance=test status=all count=10` 으로 **10개 슬롯 각각 status(idle)·slot_id·qty·limit·avg·pid 표시**(이전 갭: universe 1개·status 없음 해소). `slots --status holding` → `count=0`(필터 동작). `trex-core universe` → `assets=1`(universe 유지, slots↔universe 분리 실증). DB `daily_slot (2026-06-08,'idle',10)` 와 정확히 일치. 요청은 `actor=operator`/`touch_watchdog=false`(비침습). 단위 회귀 `338 passed`. -- **BAT-023 (현금/포지션 write-back, option D) — 인프라 게이트 PASS**: PG `trex_core_test` + RabbitMQ 인프라 게이트 **`385 passed`**(option D write-back 11 + adapter `*_after` 파싱 + `pending_cash_delta` SQL 포함), 단위 `350 passed / 35 skipped`, `ruff` clean. write-back(new/duplicate 멱등)·`*_after` 누락 skip·best-effort 무차단·추정 현금 오버레이(`cash_estimated`) 수렴 전부 green. -- **한계(운영 배포 검증으로 이월)**: BAT-023 의 실 portfolio-hub#8 full-stack option D e2e(실 `*_after` 응답 → 스냅샷 cash/quantity 갱신)는 paper/단위로 커버했고, LIVE full-stack 은 아래 Release Execution Evidence(실 배포)에서 확인한다. BAT-022 의 pytest 인프라 게이트 미실행은 verification Human Approval Memo 로 수용된 잔여(실 인프라 운영 재현이 동등 이상 경로 커버). - -- 환경: paper 브로커 + 테스트 PostgreSQL(`trex_core_test`) + 테스트 RabbitMQ, portfolio-hub stub(universe 2종), `--config `(instance_key=`rel004-smoke`), revision = main HEAD(수정 후). -- 데몬 기동: `trex-core-daemon --config ...` → 마이그레이션 적용(006/007 포함), RabbitMQ 연결, snapshot scheduler boot(stub universe swap), preallocation, 8 스레드 기동, RUNNING. 기동 후 에러 로그 없음. -- 운영자 CLI(데몬 RPC 통일) 구동 결과: - - `trex-core universe` → snapshot RPC, assets 출력, degraded=false (BAT-020). - - `trex-core slots` / `slots --status held --json` → 보유 슬롯 + `watchdog` 필드(slot_id/expired/last_seen) 노출 (BAT-021/dcy-016). - - `trex-core enter pf-005930 --capital ... --yes` → `status=admitted slot_id=...` → paper 체결 → holding (BAT-015/018). - - `trex-core enter ... --dry-run` → 미제출 미리보기(안전장치), `--json`+enter 는 `--yes` 강제 (BAT-020). - - `trex-core exit --yes` → `status=admitted` → 시장가 매도 체결 → closed(held count 0) (BAT-016). - - `ledger_outbox`: buy/sell 모두 `status=completed`(outbox durability + writer drain, BAT-019 경로 정상). -- **검출·수정한 결함(이 run 이 아니었으면 release 에 포함됐을 버그)**: `Daemon._resolve_symbol` 이 `SnapshotAsset.symbol`(존재하지 않음)을 접근해 데몬 buy admission 이 매 요청 `AttributeError` → intent RPC 타임아웃. **실 데몬 buy 가 전면 불능**이었으나 단위 테스트는 stub resolver 를 주입해 미검출(369 passed). `asset.item.symbol` 로 수정 + 회귀 테스트(`tests/runtime/test_daemon_resolve_symbol.py`) 추가 → 재기동 후 buy/sell e2e 정상(REQ-007 CHG-20260608-03). 수정 후 단위 `337 passed`. -- 한계: 실계좌(LIVE KIS) 주문은 미검증(paper 한정) — LIVE full-stack 은 운영자 배포 evidence(아래)에서 확인. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-09 13:31:33 KST (+0900) -- Completed At: - - 2026-06-09 13:31:33 KST (+0900) -- Environment: - - 운영 trex_core DB + instance_key `trex-admin-daily-trading` -- Operator: - - 진환 고 -- Revision or Artifact: - - `fec9411d330d0722c5b7970ad33e651ee479fd30` (`fec9411`) -- Commands or Procedure: - - 배포 후 데몬 재기동 및 운영자 CLI 스모크 검증(`trex-core universe`, `trex-core slots`, buy/sell e2e, ledger outbox/portfolio-hub 반영 확인) - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - 2026-06-09 13:31:33 KST 기준 snapshot RPC `degraded=False`, `assets=45` 확인 - - Startup Log Fields: - - 데몬 기동 상태 양호; `trex-core-daemon --config temp/config_trex-admin-daily-trading.toml` 정상 실행 중, `watchdog_owned` 슬롯 존재 및 CLI 응답 정상 - - Request Log Fields: - - buy/sell intent RPC 및 safety-sell 처리 로그에서 correlation/transition 확인 가능 - - Smoke or Manual Verification: - - `trex-core universe --config temp/config_trex-admin-daily-trading.toml` 정상 응답 - - `trex-core slots --config temp/config_trex-admin-daily-trading.toml` 정상 응답 - - buy/sell e2e 확인: `holding -> pending_sell -> closed` via `take_profit` 및 `forced_close_at` 트리거 모두 확인 - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - released -- Rationale: - - REL-004 범위의 safety-sell 쿨다운/forced_close_at/take_profit/stop_loss, operator CLI, ledger write-back, slot 가시화가 실 운영 검증에서 동작함을 확인했고, 회귀를 유발하는 배포 장애나 롤백 트리거는 관측되지 않았다. -- Recorded By: - - 진환 고 -- Recorded At: - - 2026-06-09 13:31:33 KST (+0900) - -### Post-release Verification Evidence (2026-06-09) -- Trading flow evidence: - - buy intent admitted for portfolio `1c53b936-d69d-41c4-9ce2-4a2b5baa4301` - - sell intent admitted for the same slot - - final slot state: `closed` - - slot id: `73e7cfae-1a92-498a-bf3d-bfe347f7b5e4` - - symbol: `316140` - - qty: `1.0` - - avg price: `28950.0` - - take-profit verification: slot `3a95d0cb-0f4c-49da-88d5-5a709f671ccc` transitioned `holding -> pending_sell -> closed` with `reason=take_profit` and daemon log `safety-sell fired: phase=take_profit` -- Ledger/outbox evidence: - - daemon log recorded `POST http://localhost:8002/v1/portfolios/1c53b936-d69d-41c4-9ce2-4a2b5baa4301/ledger/trades` returning `HTTP/1.1 201 Created` for both buy and sell writes - - `ledger_outbox` rows for buy/sell were both `status=completed` and `last_outcome=completed` -- portfolio-hub visibility evidence: - - `GET /v1/portfolios` returned the portfolio row for `1c53b936-d69d-41c4-9ce2-4a2b5baa4301` - - visible state in portfolio-hub list: `symbol=316140`, `cash_balance=-208.65499999999884`, `asset_quantity=0.0`, `price=28750.0`, `updated_at=2026-06-09T00:11:29.252209Z` - - the openapi contract exposes `POST /v1/portfolios/{portfolio_id}/ledger/trades`; no read-only GET ledger endpoint was found, so visibility was confirmed through the portfolio summary row and outbox completion logs - -## Feedback Handoff -- Observation Summary: - - 2026-06-09 실 운영 배포(instance `trex-admin-daily-trading`, rev `fec9411`)에서 REL-004 범위가 정상 동작 확인: buy/sell e2e(`holding→pending_sell→closed`, slot `73e7cfae…` symbol 316140), `take_profit` 트리거(slot `3a95d0cb…`, daemon log `safety-sell fired: phase=take_profit`)·`forced_close_at` 트리거 발동, ledger `POST …/ledger/trades 201 Created`(buy/sell)·`ledger_outbox status=completed`, snapshot RPC `degraded=False assets=45`, portfolio-hub `GET /v1/portfolios` 가시성. 롤백 트리거 없음(Rollback Required=no). - - 관찰 포인트: 한 바퀴(buy→sell) 후 portfolio-hub 상태가 `cash_balance=-208.65…`(음수)·`asset_quantity=0.0`. 수수료/세금 반영의 정상 순값인지 vs 초기 cash seed 부재/현금 회계 갭인지 확인 가치 있음(option D write-back 경로는 동작). - - 별개 운영 환류: issue #16(00:00 KST 일자 경계 자동 preallocation 누락)이 동일 인스턴스에서 관측됨 — 데몬은 RPC 응답(살아 있음)하나 당일 슬롯 미생성. REL-004 BAT-018(일자 경계 일일 preallocation) 범위와 직접 관련. -- Discovery Input: - - issue #16: 00:00 KST 일자 경계 자동 preallocation 누락 — `trex-preallocation` 스레드 생존/일자 경계 발화 검증 + 견고화(boot run_once 예외 흡수 등). BAT-018 후속 운영 환류(High 우선). - - issue #18 → **dcy-019**(slot `entry_count` + closed 슬롯 재할당 정책) 이미 confirmed — REQ 초안 대기. - - (관찰) portfolio-hub 음수 `cash_balance` 정합성 — 필요 시 별도 Discovery 후보. -- REQ Input: - - issue #17 → **BAT-024**(operator `enter --forced-close-at`, REQ-032 CHG-20260609-01/02) 이미 release-candidate. **REL-004 미포함** → 차기 release(REL-005 또는 통합)로 별도 배포 필요. - - 신규 REQ 특이 후보 없음(이번 주기 변경은 대부분 change-req 로 처리됨). -- Change Request Input: - - `daily_symbol_entries`(심볼 재진입 카운터) 제거 — production 소비자 없음 확인 + 심볼 재진입 제한 미사용 결정(dcy-019). **REQ-002 change-req 트랙**(별도 batch-lite). diff --git a/docs/releases/rel-005_20260609_operator-forced-close-and-slot-reallocation.md b/docs/releases/rel-005_20260609_operator-forced-close-and-slot-reallocation.md deleted file mode 100644 index dbebb4f..0000000 --- a/docs/releases/rel-005_20260609_operator-forced-close-and-slot-reallocation.md +++ /dev/null @@ -1,119 +0,0 @@ -# REL-005: operator enter --forced-close-at + slot entry_count·closed 재할당 통합 릴리즈 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-024, BAT-025 - -## Scope -- Included: - - BAT-024 (dcy-016 후속 / issue #17 / REQ-032 CHG-20260609-01): 운영자 `trex-core enter` 에 `--forced-close-at `(절대시간) 옵션 추가 — 매수 진입 명령 하나로 시간 기반 자동청산(forced_close) 시각 지정. CLI 가 buy intent payload 의 기존 `forced_close_at` 필드에 매핑(계약·도메인·forced-close 루프 이미 구현·불변, CLI 노출 갭만 보완). 잘못된 형식 사전 거부, 미지정 시 미포함(하위호환). 상대시간(`--hold-minutes`) 미채택. - - BAT-025 (dcy-019 / issue #18 / REQ-002·003·006·007·009 CHG-20260609-01): slot-scoped `entry_count`(기본 0) 필드 + 멱등 마이그레이션 008, 상태 머신 `closed→pending_buy` 신규 전이(base 8→9), buy admission 후보를 idle∪closed 로 확장하고 `entry_count→updated_at→slot_id` 결정적 정렬로 선택(`find_allocatable_slot`) + closed 직접 재할당, `→pending_buy` 시 entry_count +1 / closed→pending_buy clean-slate 점유 초기화. 기존 `daily_symbol_entries`(symbol-scoped) 분리·미변경. -- Excluded: - - BAT-001~023 (REL-001/002/003/004 에서 이미 released) - - `daily_symbol_entries` 제거(dcy-019 Out of Scope — 별도 REQ-002 change-req 트랙) - - 운영자 CLI/스냅샷에 slot `entry_count` 노출 (dcy-019 §5 제외 — 후속 후보) - - snapshot `available_slots`(=idle_count) capacity 의미 변경 (dcy-019 범위 밖) - -## Rollout Plan -1. 배포 전: 대상 인스턴스 데몬 정지(또는 거래 세션 외 시간), 현재 리비전 SHA 기록. -2. DB 마이그레이션 적용(멱등): `008_add_slot_entry_count.sql`(daily_slot 에 `entry_count INTEGER NOT NULL DEFAULT 0` additive 추가). 데몬 기동 시 `apply_migrations` 가 자동 적용. additive 컬럼이라 기존 행은 0 으로 채워짐(무해). -3. 신 리비전(REL-005 배포 SHA) 배포 + 데몬 재기동(`--config .toml`). 기동 로그에서 RUNNING/비-degraded 확인. -4. 스냅샷 RPC degraded=false·available_slots·asset_count 확인. trading_date 가 KST 당일과 일치하는지 확인. -5. 운영자 CLI 스모크(paper 또는 비거래 시간): - - `trex-core enter --capital ... --forced-close-at 2026-06-09T15:00:00+09:00 --yes` → admitted + 슬롯 `forced_close_at` 설정 확인(BAT-024). 잘못된 형식(`--forced-close-at zzz`)은 제출 전 거부. - - idle 슬롯 소진 후(또는 일부 closed 상태에서) 추가 buy → closed 슬롯 재할당으로 admit 되고 `no_idle_slot` 거부가 사라지는지 확인(BAT-025). 슬롯 회전이 `entry_count` 기준 고르게 분산되는지 관측. -6. 거래 세션 재개. - -## Rollback Plan -- 조건: 기동 실패, 마이그레이션 오류, buy admission/슬롯 선택 회귀(잘못된 슬롯 재사용·상태 오염), CLI `--forced-close-at` 오작동. -- 절차: 신 리비전 → 직전 released 리비전(REL-004 시점, `fec9411`)으로 롤백 + 데몬 재기동. -- 마이그레이션: 008 은 **additive(ADD COLUMN)** 라 구 코드와 공존 가능(구 코드는 `entry_count` 컬럼을 읽지/쓰지 않고, DEFAULT 0 으로 INSERT 무해) — 역마이그레이션 불필요. 필요 시 `ALTER TABLE daily_slot DROP COLUMN IF EXISTS entry_count` 로 제거 가능(데이터 영향: entry_count 값만 소실, slot 핵심 상태 보존). -- 데이터: closed→pending_buy 재할당으로 전이한 슬롯은 롤백 후에도 구 코드 상태 머신과 호환(pending_buy/holding/closed 는 기존 상태) — 추가 조치 불필요. - -## Verification Checklist -- 배포 전: - - 단위 전체 회귀 green: BAT-025 verification 시점 `366 passed / 35 skipped / 1 deselected`(`::test_sandbox_token_and_submit` 은 LIVE `.env` KIS URL 환경 의존으로 deselect — 본 릴리즈 무관). BAT-024 단위 `353 passed`. - - `ruff check trex_core tests` All passed. - - 마이그레이션 008 idempotent 재적용 + 컬럼 계약 가드(`EXPECTED_SLOT_FIELDS`↔information_schema, entry_count 포함) green — test DSN 환경(`TREX_POSTGRESQL_TEST_DSN`)에서 확인(라이브 runtime DSN 은 `pg_clean` TRUNCATE 가 파괴적이라 미사용). -- 배포 후: - - 데몬 RUNNING + 비-degraded, 백그라운드 스레드 기동(`trex-preallocation` 포함). - - trading_date == KST 당일, 슬롯 preallocation 정상, daily_slot `entry_count` 컬럼 존재. - - `trex-core enter --forced-close-at --yes` → admitted + 슬롯 forced_close_at 설정. 잘못된 형식 사전 거부. - - closed 슬롯 재할당 e2e: idle 소진 상황에서 buy 가 closed 슬롯으로 admit(`entry_count` +1), 선택이 최저 진입 우선·결정적인지 확인. - -## Pre-Release Run Evidence (operator-facing smoke) - -confirm-release 전 "운영자가 쓰는 프로그램이 실제로 동작한다"는 run-the-app 증거. 단위 테스트(366 passed)와 별개로, 실 데몬(RabbitMQ + 실 PostgreSQL) 기동 + `trex-core` CLI 구동으로 REL-005 의 두 신규 동작(BAT-024 `enter --forced-close-at`, BAT-025 closed 슬롯 재할당)을 검증했다. (`run-trex-core-daemon` 프로젝트 skill 경로 사용.) - -- 환경: paper 브로커 + **테스트 PostgreSQL `trex_core_test`**(운영 DB 비사용 — `pg_clean` TRUNCATE 파괴성 회피) + 테스트 RabbitMQ, portfolio-hub **stub**(`pf-005930`/`pf-000660`), `TREX_TOTAL_SLOTS=1`(idle 1개만 두어 재할당 강제), instance_key `rel005smoke`(운영/기존 데몬과 큐 격리), 리비전 = 작업 트리(`941d324` + dcy-019 미커밋 변경). -- **마이그레이션 008 실 PG 적용**: `apply_migrations` 가 001~008 적용 후 `daily_slot.entry_count` 컬럼 존재 확인(실 PostgreSQL round-trip, additive·멱등). 즉, 앞서 단위에서 이월했던 PG 게이트 마이그레이션이 실 DB 에서 실제로 검증됨. -- 데몬 기동: `trex-core-daemon --config /tmp/rel005smoke.toml` → RabbitMQ 토폴로지 선언(`trex.intent.req.rel005smoke`/`trex.snapshot.req.rel005smoke`), 2026-06-09 idle 슬롯 1개(`entry_count=0`) preallocation, 에러 로그 없음, 종료 시 `Normal shutdown`. -- **BAT-024 (`enter --forced-close-at`) — PASS**: - - `trex-core enter pf-005930 --capital 1000000 --strategy-id STRAT-A --forced-close-at "2026-06-09T15:00:00+09:00" --yes --json` → `{"status":"admitted","slot_id":"4fc104cf-…"}`. DB 슬롯: `status=holding symbol=005930 entry_count=1 forced_close_at=2026-06-09 06:00:00+00`(=15:00 KST 정확 변환). - - `--forced-close-at "not-a-time"` → 제출 전 거부 `{"error":"invalid --forced-close-at (expected ISO8601): 'not-a-time'"}`, exit=1 (RPC 미발생 — 사전 검증). -- **BAT-025 (slot entry_count + closed 직접 재할당) — PASS**: - - 진입 시 `entry_count` 0→1 증가 확인(위 BAT-024 슬롯). - - `trex-core exit 4fc104cf… --yes` → `pending_sell` → `closed`(entry_count=1 보존, symbol 005930 audit 유지). - - idle 슬롯 0개 상태에서 **다른** 포트폴리오 진입 `trex-core enter pf-000660 --capital 500000 --strategy-id STRAT-B --yes` → `{"status":"admitted","slot_id":"4fc104cf-…"}` — **이전과 동일한 물리 슬롯**으로 admit(이전엔 `no_idle_slot` 거부됐을 상황). closed→pending_buy 직접 재할당 실증. - - 재할당 후 DB: `status=holding symbol=000660 entry_count=2 forced_close_at=NULL strategy_id=STRAT-B` — clean-slate(005930→000660 교체, 이전 forced_close_at 클리어)·entry_count 누적(1→2) 확인. - - 전이 로그(slot 4fc104cf): `idle→pending_buy → pending_buy→holding → holding→pending_sell → pending_sell→closed → closed→pending_buy → pending_buy→holding` — dcy-019 신규 전이 `closed→pending_buy` 가 실 데몬에서 발화. - - 운영자 RPC 정합: `trex-core slots --json` 가 재할당 슬롯(symbol 000660, STRAT-B, holding) 응답. `ledger_outbox` 3행(buy/sell/buy) 모두 `status=completed`. -- **한계(운영 배포 검증으로 이월)**: 실계좌(LIVE KIS) 주문은 미검증(paper 한정). portfolio-hub 는 stub(실 #8 연동은 본 릴리즈 변경 범위 아님 — 슬롯 도메인/CLI). LIVE full-stack 은 아래 Release Execution Evidence(실 배포)에서 확인한다. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-10 -- Completed At: - - 2026-06-10 -- Environment: - - paper 브로커 + 테스트 PostgreSQL(`trex_core_test`) + 테스트 RabbitMQ + portfolio-hub stub(`pf-005930`/`pf-000660`) + `TREX_TOTAL_SLOTS=1` + instance_key `rel005smoke`(운영/기존 데몬과 큐 격리). 실계좌(LIVE KIS) 미사용 — paper 한정. -- Operator: - - Justin Ko -- Revision or Artifact: - - 작업 트리(`941d324` + dcy-019 미커밋 변경 포함) -- Commands or Procedure: - - `trex-core-daemon --config /tmp/rel005smoke.toml` 기동 후 CLI 스모크(단계 3~5의 Rollout Plan 수행) - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - RabbitMQ 토폴로지 선언(`trex.intent.req.rel005smoke` / `trex.snapshot.req.rel005smoke`) 성공, idle 슬롯 1개 preallocation 완료, 에러 로그 없음, `Normal shutdown` 정상 종료 확인 - - Startup Log Fields: - - 마이그레이션 001~008 적용 후 `daily_slot.entry_count` 컬럼 존재 확인(실 PostgreSQL round-trip, additive·멱등 검증) - - Request Log Fields: - - 전이 로그(slot 4fc104cf): `idle→pending_buy → pending_buy→holding → holding→pending_sell → pending_sell→closed → closed→pending_buy → pending_buy→holding` — dcy-019 신규 전이 `closed→pending_buy` 실 데몬 발화 확인. `ledger_outbox` 3행(buy/sell/buy) 모두 `status=completed`. - - Smoke or Manual Verification: - - **BAT-024 PASS**: `trex-core enter pf-005930 --capital 1000000 --strategy-id STRAT-A --forced-close-at "2026-06-09T15:00:00+09:00" --yes --json` → `{"status":"admitted","slot_id":"4fc104cf-…"}`. DB: `status=holding symbol=005930 entry_count=1 forced_close_at=2026-06-09 06:00:00+00`(=KST 15:00 정확 변환). 잘못된 형식 `--forced-close-at "not-a-time"` → 제출 전 거부 `{"error":"invalid --forced-close-at (expected ISO8601): 'not-a-time'"}`, exit=1. - - **BAT-025 PASS**: 진입 시 `entry_count` 0→1 증가. `trex-core exit` → `pending_sell → closed`(entry_count=1 보존). idle 0개 상태에서 `trex-core enter pf-000660 --capital 500000 --strategy-id STRAT-B --yes` → 동일 물리 슬롯 admitted(closed→pending_buy 직접 재할당). 재할당 후 DB: `status=holding symbol=000660 entry_count=2 forced_close_at=NULL strategy_id=STRAT-B`(clean-slate·entry_count 누적 확인). - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - released (feedback-captured로 전환) -- Rationale: - - 배포 실패·회귀 없음. `--forced-close-at` 동작 정상(유효 → admitted·KST 변환, 잘못된 형식 → 사전 거부). `entry_count` 기반 closed 슬롯 재할당 실 데몬 발화 확인. 슬롯 상태 전이 완전. ledger 3행 completed. -- Recorded By: - - Justin Ko -- Recorded At: - - 2026-06-10 - -## Feedback Handoff -- Observation Summary: - - REL-005 동작 모두 confirmed. `--forced-close-at` 사전 검증·UTC 변환 정상. closed 슬롯 재할당(entry_count 최저 우선)이 실 데몬에서 발화됨. entry_count 누적·clean-slate 교체 정상. 전이 로그 완전. ledger 3행 completed. 배포 실패·롤백 없음. - - 한계 이월: 실계좌(LIVE KIS) 주문 미검증(paper 한정), portfolio-hub stub 사용(실 #8 연동 미검증 — 슬롯 도메인/CLI 범위 밖). -- Discovery Input: - - none (이번 릴리즈 관찰 범위에서 신규 후속 탐색 트리거 없음) -- REQ Input: - - none -- Change Request Input: - - `daily_symbol_entries`(심볼 재진입 카운터) 제거 — production 소비자 없음 + 심볼 재진입 제한 미사용 확정(dcy-019). REQ-002 change-req 트랙(별도 batch-lite) — REL-005 범위 밖. diff --git a/docs/releases/rel-006_20260611_asset-centric-sizing-execution-policy.md b/docs/releases/rel-006_20260611_asset-centric-sizing-execution-policy.md deleted file mode 100644 index 1bb300b..0000000 --- a/docs/releases/rel-006_20260611_asset-centric-sizing-execution-policy.md +++ /dev/null @@ -1,152 +0,0 @@ -# REL-006: 자산 중심 영구 슬롯 cutover + 포지션 사이징 + 실행 정책 + 운영 정리 통합 릴리즈 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-026, BAT-027, BAT-028, BAT-029, BAT-030, BAT-031, BAT-032, BAT-033, BAT-034, BAT-035, BAT-036 - -## Scope -- Included: - - BAT-026 (batch-lite / REQ-048·010 CHG): config 디렉터리 기반 설정 관리 — `--config ` + `--config-path ` + config-dir `.env` 결정적 해석(`--env-file` 우선), legacy 루트 `.env` 폴백 유지(완전 하위호환). 데몬·운영자 CLI 양쪽 적용. - - BAT-027 (dcy-020 BAT-α / REQ-056·057): **자산 1:1 영구 슬롯 기반** — `(instance_key, portfolio_id)` 키 영구 슬롯 + `inactive↔pending_buy↔holding↔pending_sell` 상태머신, 인스턴스 스코프 advisory lock. 마이그레이션 009(instance_key·retired_at)·010(literal inactive). - - BAT-028 (dcy-020 BAT-β / REQ-058·059·060): universe reconcile(슬롯 생성/retire, 대량 소멸 가드) + 자산 중심 buy admission(`max_active_slots` 동시 활성 상한). - - BAT-029 (dcy-020 BAT-γ / REQ-061·062·063): intent 계약 자산 키 통일(slot_id 정본 + portfolio_id 수용) + 수량 정본=hub·core 미러 divergence reconcile + slots 단일 뷰(universe_detached 표기). - - BAT-030 (dcy-020 BAT-δ / REQ-064·065 + 056·057·062 잔여): **pool 모델 폐기 cutover** — daily preallocation 제거(영구 슬롯은 universe reconcile 이 소유), 마이그레이션 011(`trading_date`·`entry_count` DROP, 비가역). - - BAT-031 (dcy-021 / REQ-066·067 + 057·061·062 CHG): 포지션 사이징 — 보유 슬롯 증액(holding→pending_buy, avg_price 가중평균), 부분 매도(집행 수량=min(intent,브로커잔고), 잔량 holding/전량 inactive, cost basis 유지), 부분 체결 실제만 반영. - - BAT-032 (dcy-022 / REQ-068~072 + 013·061 CHG): 주문 실행 정책 — intent `timeout_sec`(미체결 자동 취소·cancel-fill race 처리)·`slippage_pct`(보호 지정가 buy ceil/sell floor), per-instance TOML `default_order_timeout_sec`, 운영자 `trex-core cancel` 백스톱. 마이그레이션 012(execution policy 필드). - - BAT-033 (batch-lite / REQ-032 CHG-06, issue #23): `trex-core enter` `--qty` 필수 검증 — 미지정 시 RPC 전 즉시 오류(0주 주문 silent 실패 제거). - - BAT-034 (dcy-023 / REQ-002·006·007·028·032 CHG, issue #22): 죽은 필드 `allocated_capital` 전 경로 제거 — CLI `--capital` argparse 거부, BuyIntent·payload 수신 무시, Slot/`EXPECTED_SLOT_FIELDS`, snapshot `slots[]`. 마이그레이션 013(컬럼 DROP, 비가역). - - BAT-035 (dcy-025 / REQ-073·074·075 신규 + REQ-021·034·063 CHG): `trex.snapshot.req` → `trex.slots.req` 즉시 교체 — 요청 `{filter: all|inactive|pending|holding|error, spy: bool}`, 응답 `{slots[], degraded, reason, message}`. 슬롯 단일 행 스키마: `asset_quantity`(hub 정본)·`qty`(slot 미러)·`warned`·`cash_balance`·`cash_estimated`·`watchdog{expired,last_seen}`·`paused`; strategy 필드 제외. `spy=true` → watchdog 무터치. REQ-028(`build_snapshot_envelope`/`View`/`SnapshotEnvelope`) Deprecated + 코드 삭제. - - BAT-036 (dcy-024 / REQ-048·005 CHG, issue #15): per-instance TOML `safety_sell_enabled: bool = True` — `false` 시 `trex-safety-sell` 워커 미기동·물리 차단 + `no autonomous liquidation` WARNING 로그. `ExecutionService`·kill-switch·전략 sell intent 불변. -- Excluded: - - BAT-001~025 (REL-001~005 에서 released) - - trex-orb-v1 등 외부 전략 모듈 저장소 변경(발신 계약 전환·allocated_capital 송신 정리 — 별도 합의) - - `daily_symbol_entries` 잔재 제거(REL-005 환류 — 별도 change-req 트랙) - -## Rollout Plan - -- **배포 대상 커밋**: `f0ad37747b4e9d43dc798bfeda5b543cab048890` (main, 2026-06-11) - -1. 배포 전: 거래 세션 외 시간 확보, 현재 운영 리비전 SHA 기록, 위 배포 SHA(`f0ad377`) 확인. -2. **DB 백업(필수)**: 마이그레이션 011·013 은 비가역 컬럼 DROP — 적용 전 운영 `daily_slot`(권장: DB 전체) 백업. BAT-030 design §4 절차 준용. -3. 신 리비전 배포 + 데몬 재기동. `apply_migrations` 가 009~013 자동 적용(전부 멱등). 기동 로그에서 마이그레이션 적용·RUNNING·비-degraded 확인. -4. **cutover 확인**: daily preallocation 부재가 정상 — 영구 자산 슬롯은 snapshot scheduler 의 universe reconcile 이 생성/retire 한다. 기동 직후 universe fetch → 슬롯 생성 로그 확인, `trex-core slots` 로 자산 수 = 슬롯 수 확인. -5. 운영자 CLI 스모크(paper 또는 비거래 시간): - - `trex-core slots` — 응답 형식 `{slots[], degraded, reason, message}` 확인 (BAT-035: 구 `{assets[], available_slots}` 형식 부재); inactive/holding 상태·universe_detached 표기 (BAT-027~030) - - `trex-core slots --status holding` → `filter=holding` 서버 필터 작동 확인 (BAT-035 filter) - - `trex-core universe` → `trex.slots.req` 큐 사용, spy=true 확인 (BAT-035) - - `trex-core enter --yes` → `--qty` 누락 즉시 오류 (BAT-033); `--qty N --yes` → admitted (BAT-029 자산 키) - - `trex-core enter ... --capital ...` → argparse 거부 (BAT-034) - - 증액/부분 매도 intent e2e (BAT-031), timeout_sec/slippage_pct intent + `trex-core cancel` (BAT-032) -6. **BAT-035 외부 소비자 없음 확인**: `trex.snapshot.req` 구독자는 운영자 CLI 전용이며 CLI 는 이미 `trex.slots.req` 로 갱신됨. 전략 모듈(trex-orb-v1)은 `trex.intent.req` 생산자만이고 snapshot 소비자가 아니므로 차단 없음. -7. 전략 모듈(trex-orb-v1) 재연결 후 buy intent 정상 admit 확인 — `allocated_capital` 송신 잔존은 unknown key 로 무시되어 무해. -8. **BAT-036 확인(해당 시)**: TOML 에 `safety_sell_enabled = false` 설정 시 데몬 기동 로그에 `no autonomous liquidation` WARNING 출력·`trex-safety-sell` 워커 미기동 확인. 기본값 `true` 유지 시 변화 없음. -9. 거래 세션 재개. - -## Rollback Plan -- 조건: 기동 실패, 마이그레이션 오류, universe reconcile 슬롯 생성 실패(전 자산 미생성), buy admission 회귀, 집행 루프(timeout/slippage) 오작동, CLI 회귀. -- 절차: 직전 released 리비전(REL-005 시점)으로 롤백 + 데몬 재기동 + **백업 복원**. -- 마이그레이션: 011(`trading_date`·`entry_count` DROP)·013(`allocated_capital` DROP)은 구 코드의 SELECT/UPDATE 가 해당 컬럼을 참조하므로 **구 리비전과 공존 불가** — 롤백은 반드시 2번 백업 복원과 함께 수행한다. 009·010·012 는 additive/호환이라 단독으로는 무해. -- 데이터: cutover 후 생성된 영구 슬롯 행은 구 pool 모델 코드와 호환되지 않음 — 복원 시점 이후 거래가 없도록 비거래 시간 배포를 전제로 한다. - -## Verification Checklist -- 배포 전 (각 batch verification evidence): - - BAT-026: 단위 23 + 전체 375 passed, ruff clean, 라이브 스모크(name·path 동일 결과) - - BAT-027~030 (dcy-020): 단위 367 / PG 29 / MQ 21 passed + run-the-app 자산 중심 e2e(영구 슬롯 생성·reconcile·slot_id 매수) - - BAT-031 (dcy-021): 단위 378 / 통합 422 passed + 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) - - BAT-032 (dcy-022): 425 passed(신규 71 tests — policy lifecycle·timeout·slippage·cancel), 회귀 없음 - - BAT-033: 426 passed (AC-7 `--qty` guard + 기존 enter 회귀 보완) - - BAT-034 (dcy-023): 428 passed + PG 게이트 21 passed + ruff clean, 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재, `grep allocated_capital` 전수 0건 - - BAT-035 (dcy-025): 420 passed / 45 skipped — `test_slots_handler.py` 신규 13 cases(REQ-073 AC-1~5·REQ-074 AC-1~5 망라), 삭제 4 files(snapshot_handler/snapshot_selection/snapshot_rpc_watchdog/messaging_snapshot_consumer), ruff clean, REQ-028 코드 전수 제거 확인 - - BAT-036 (dcy-024): 420 passed / 45 skipped — `test_instance_config.py` 신규 3 cases(default True·false toml·true toml), ruff clean - - SRS: 포함 REQ 전부 Implemented(Approved/Proposed 0건), batch register 11건 release-candidate -- 배포 후 (app-service profile): - - 데몬 RUNNING + 비-degraded, 마이그레이션 009~013 적용 로그, 백그라운드 스레드(snapshot scheduler·execution loop) 기동 - - universe reconcile 슬롯 생성: 자산 수 = 슬롯 수, retire/tombstone 동작 - - `trex-core enter`(--qty 필수·--capital 거부)·`slots`·`cancel` 스모크 - - buy→fill→증액→부분 매도 e2e + timeout/slippage 정책 발화 확인 - - `ledger_outbox` completed, `daily_slot` 에 `trading_date`/`entry_count`/`allocated_capital` 컬럼 부재 확인 - -## Pre-Release Run Evidence (operator-facing smoke) - -confirm-release 전 run-the-app 증거 (2026-06-11, `run-trex-core-daemon` 프로젝트 skill 경로). 실 데몬(RabbitMQ + 실 PostgreSQL) 기동 + 메시징/CLI 구동으로 본 릴리즈의 신규 동작을 검증했다. - -- 환경: paper 브로커 + **테스트 PostgreSQL `trex_core_test`**(운영 DB 미사용) + 테스트 RabbitMQ, portfolio-hub **stub**(pf-005930/pf-000660), instance_key `rel006smoke`(큐 격리), `--config /tmp/rel006smoke/rel006smoke.toml --env-file /tmp/rel006smoke/.env`(BAT-026 config-dir 경로 실사용), `max_active_slots=5`. -- **기동 + cutover (BAT-027~030) — PASS**: 토폴로지 선언(`trex.intent.req.rel006smoke` 등) 후 데몬 ready. daily preallocation 없이 snapshot scheduler 의 universe reconcile 이 영구 자산 슬롯 생성: `slot reconcile: {'created': 2, 'retired': 0, 'tombstoned': 0}` — 자산 수(2) = 슬롯 수(2), status `inactive`. -- **e2e 메시징 smoke (`smoke.py`) — PASS**: snapshot RPC(slots=2, available_slots=5) → slot_id 키 buy admitted → 동일 intent_id 중복 재전송 identical(멱등) → paper fill 로 `holding qty=10 avg_price=1000` → sell admitted → `inactive`(점유 해제·자산 바인딩 유지). -- **BAT-033 (`--qty` 필수) — PASS**: `enter pf-005930 --yes`(qty 없음) → `error: --qty is required...`, rc=1, RPC 미발생. -- **BAT-034 (`--capital` 제거) — PASS**: `enter ... --capital 100000` → argparse `unrecognized arguments: --capital`, rc=2. `slots` detail 에 allocated_capital 행 없음. -- **BAT-031 (포지션 사이징) — PASS**: holding(10@1000) 슬롯에 증액 매수 `--qty 5 --entry-price 2000` → admitted(holding→pending_buy) → fill 후 `qty=15.0 avg_price=1333.3333`(정확한 가중평균). 전량 매도 → `inactive qty=None portfolio_id 유지`. -- **BAT-032 (운영자 cancel 백스톱) — PASS**: holding 슬롯에 `trex-core cancel` → `ignored: not_pending`, rc=0 (control RPC 경로 end-to-end). -- **DB 계약 (마이그레이션 009~013) — PASS**: `daily_slot` 에 `trading_date`/`entry_count`(011)·`allocated_capital`(013) 컬럼 **부재** 확인. `ledger_outbox` 5행 전부 `completed`. -- 종료: SIGTERM → `Normal shutdown`, 에러/traceback 0건(로그의 error-매칭 라인은 전부 pika INFO). -- **한계(운영 배포 검증으로 이월)**: LIVE KIS 주문 미검증(paper 한정), portfolio-hub stub(실 universe/ledger 연동은 배포 후 확인), timeout_sec/slippage_pct 정책 발화는 전략 intent 필드라 CLI 미노출 — BAT-032 단위(71 tests) evidence 로 갈음하고 배포 후 전략 모듈 경유로 확인. -- **BAT-035 (`trex.slots.req` 교체) — 단위 검증 갈음**: Pre-release 실 데몬 smoke 는 BAT-026~034 범위로 수행됐으며 BAT-035 라이브 RPC 응답은 미확인. 갈음 근거: `test_slots_handler.py` 13 cases(REQ-073 AC-1~5·REQ-074 AC-1~5) + `test_cli_operator.py` slots queue/spy=true 경로 + `test_topology.py` 상수 교체 전수. 외부 소비자 분석: `trex.snapshot.req` 구독은 운영자 CLI 전용이고 CLI 는 이미 갱신됨 — 차단 없음. 배포 후 첫 `trex-core slots` 호출로 즉시 확인 가능(Rollout 5단계). -- **BAT-036 (`safety_sell_enabled`) — 단위 검증 갈음**: `safety_sell_enabled=false` 라이브 데몬 smoke 미수행. 갈음 근거: `test_instance_config.py` 3 cases(default·false·true toml 경로) + `daemon.py` 코드 경로 검토. 기본값 `true` 유지 시 배포 직후 동작 변화 없음 — `false` 설정은 운영자 명시적 TOML 변경 시에만 활성화되므로 위험도 낮음. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-11 (최초 배포 `002340e`) -- Completed At: - - 2026-06-12 (긴급패치 포함 최종 `808a851`) -- Environment: - - 운영 — 인스턴스 `trex-admin-daily-trading` (KIS LIVE 브로커 + 운영 PostgreSQL `trex_core` + RabbitMQ + portfolio-hub) -- Operator: - - Justin Ko -- Revision or Artifact: - - 최초 배포 `002340e` → 긴급패치 후 최종 **`808a851`** (계획 SHA `f0ad377` 이후 main 진행분 포함 배포) -- Commands or Procedure: - - 데몬 중단 → `git pull` → `pip install -e .` → 데몬 재기동. `apply_migrations` 009~013 자동 적용(멱등). - -#### Hotfix 경위 (배포 중 결함 → 무롤백 패치) -- 최초 배포(`002340e`) 후 재기동 시 **슬롯 symbol 전부 `None`** 로딩 — universe reconcile 이 슬롯 생성 시 symbol 미저장 결함. -- `221a163` fix(smoke): topology API 변경 반영(snapshot_queue→slots_queue) / `24a6017` **fix(reconcile): symbol 저장 + NULL backfill** / `808a851` docs(req-058): CHG-20260611-01 반영. -- 패치 후 재기동: **45/45 슬롯 symbol 정상 로딩** 확인. - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - PASS — 데몬 RUNNING(PID 가동, RabbitMQ 3연결 OPEN), portfolio-hub HTTP 200, KIS inquire-balance HTTP 200, slots RPC degraded=None (2026-06-12) - - Startup Log Fields: - - PASS — 마이그레이션 009~013 적용(운영 DB information_schema 직접 확인: `trading_date`/`entry_count`/`allocated_capital` 부재, `instance_key` 존재, `ledger_outbox` 미완료 0건). universe reconcile 영구 슬롯 45개 생성(daily preallocation 부재 정상), universe_detached 0건. - - Request Log Fields: - - PASS — 실거래 e2e 전이 로그: ①장외 enter(2026-06-11 19:31): admitted → `inactive→pending_buy` → KIS order-cash 200 → 거부(장운영시간 아님) rollback `pending_buy→inactive`. ②장중 full e2e(2026-06-12, 005930 1주): 09:32 buy intent `inactive→pending_buy` → 09:32 체결 `pending_buy→holding`(qty=1, avg 334,000) → 14:37 **safety-sell stop_loss 자동 발화** `holding→pending_sell` → 14:37 체결 `pending_sell→inactive`. ledger buy/sell 2건 completed. slot qty(1.0)==hub asset_quantity(1.0) divergence 없음. - - Smoke or Manual Verification: - - PASS — BAT-035: `slots`(count=45, slots[] 형식·assets[] 부재)/`--status holding·buyable` 서버 필터/`universe`(slots.req 큐, spy=true)/JSON 스키마(allocated_capital 부재·watchdog·asset_quantity 포함). BAT-033: `--qty` 누락 즉시 오류 rc=1. BAT-034: `--capital` argparse 거부 rc=2 + 45개 전수 allocated_capital 부재. BAT-032: `cancel` dry-run rc=0. BAT-036: TOML 미설정 → default true 워커 기동 확인. - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none — 최초 배포의 symbol 누락 결함은 롤백 없이 긴급패치(`24a6017`)로 해소(REQ-058 CHG-20260611-01 로 문서 정합 완료). - -### Release Decision Evidence -- Released Decision: - - **released 전환** — 운영팀 배포 근거 종합 판정 전 항목 PASS(마이그레이션/기동·cutover/심볼 로딩/BAT-031~036 스모크/실거래 full e2e), 롤백 불요. -- Rationale: - - 운영 실거래(005930 매수→보유→stop_loss 자동 청산)로 자산 중심 슬롯 lifecycle·포지션 사이징·safety-sell·ledger 전 경로 무결 검증. 손익 −7,099.15원(−2.13%)은 손절 청산 결과이며 시스템 동작 관점 PASS. -- Recorded By: - - Justin Ko (운영팀 배포 근거) / 기록 정리: stage-pilot capture -- Recorded At: - - 2026-06-12 - -## Feedback Handoff -- Observation Summary (2026-06-12, 운영팀 배포 근거 §8 + 실거래 데이터): - - **배포 중 결함 1건(긴급패치 해소)**: universe reconcile 슬롯 생성 시 symbol 미저장 → 재기동 후 45슬롯 전부 symbol=None. `24a6017` 패치(생성 시 저장 + NULL backfill) 후 45/45 정상. REQ-058 CHG-20260611-01 로 AC-5 추가 완료 — 회귀 방지 테스트는 기존 reconcile 테스트(symbol 포함 시그니처)로 커버됨. - - **실거래 full e2e 검증**: 005930 매수(09:32)→보유→stop_loss 자동 청산(14:37) 완주. 포지션 사이징(avg_price/qty 반영, 브로커 잔고 기준 청산), safety-sell 자동 발화, slot↔hub 수량 일치(divergence 0), ledger buy/sell completed. - - **잔존 관찰 1 — watchdog_expired 상시**: 전략 모듈(trex-orb-v1) 미연결 상태라 watchdog expired 가 일상 — 전략 연결 시 해소. 수동 주문은 forced_close_at 기반 관리로 영향 없음. (중장기 인스턴스의 동일 문제는 dcy-027/BAT-041 `watchdog_timeout_sec` per-instance TOML 로 별도 해소됨 — 차기 릴리즈) - - **잔존 관찰 2 — 매도세 tax=0 왜곡 지속**: 본 릴리즈 리비전(`808a851`)은 BAT-038(매도 증권거래세, dcy-026/issue #24) 이전 — 2026-06-12 매도 체결도 `세금 0` 으로 기록됨(수수료 49.05 만). 수정은 BAT-038(release-candidate)에 있으며 **차기 릴리즈 배포 전까지 신규 매도 ledger 의 tax=0 왜곡이 계속됨**. 과거 행 소급 보정은 안 함(dcy-026 OQ-3). - - **운영 메모**: `trex-admin-daily-trading` 은 계좌 전체 cash 공유 모델(portfolio-hub 는 자산별 PnL 만 기록) — 슬롯 cash_balance 음수는 정상. -- Discovery Input: - - 없음 (자율 청산 OFF 모드 issue #15 → dcy-024 완료, 중장기 전략 슬롯 issue #20 → dcy-027 완료. watchdog 소유권 진입 외부 알림(슬랙·이메일)은 dcy-027 OQ-3 주석으로 이미 후속 Discovery 백로그에 등재) -- REQ Input: - - 없음 — 신규 요구사항 후보 없음(잔존 관찰은 전부 기존 트랙으로 커버) -- Change Request Input: - - `daily_symbol_entries` 잔재 제거(REL-005 환류 승계 — REQ-002/006 change-req 트랙, 미착수) - - ~~paper 브로커 order seq Redis 영속화(issue #4)~~ — **완료**(REQ-011 CHG-20260612-01, BAT-039 release-candidate) - - `default_order_timeout_sec` TOML 전파 누락(BAT-041 구현 중 발견 pre-existing — `from_toml` 파싱되나 `from_config` 미전파로 런타임 무시. REQ-048/068 change-req 트랙, 미착수) -- 차기 릴리즈 입력: BAT-037~041 5건 release-candidate 대기(REL-007 후보) — **BAT-038 매도세 수정 포함이므로 조기 배포 권장**(위 잔존 관찰 2). diff --git a/docs/releases/rel-007_20260612_slots-rpc-fee-paper-longterm.md b/docs/releases/rel-007_20260612_slots-rpc-fee-paper-longterm.md deleted file mode 100644 index a35ffca..0000000 --- a/docs/releases/rel-007_20260612_slots-rpc-fee-paper-longterm.md +++ /dev/null @@ -1,124 +0,0 @@ -# REL-007: slots RPC 개선 + 매도세 정정 + paper seq 영속화 + 중장기 인스턴스 프로파일 통합 릴리즈 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-037, BAT-038, BAT-039, BAT-040, BAT-041 - -## Scope - -- Included: - - BAT-037 (dcy-025 REQ-073 CHG-20260612-01): `trex.slots.req` 응답에 `available_slots: int|null` capacity hint 추가. `max_active_slots` 미설정 시 `null`, 설정 시 `max(0, M − active)`. orb-v1 의 불필요한 intent 왕복 제거. AC-3 반전(포함), AC-6 신규. - - BAT-038 (dcy-026 REQ-018 CHG-20260612-01): 매도 체결 `tax=0.0` 하드코딩 버그(issue #24) 수정. `calculate_tax(side, amount)` 신규, 수수료율(`TREX_KIS_FEE_BPS`=1.5)·세율(`TREX_KRX_TAX_SELL_BPS`=20.0) env 상수화. KIS 어댑터는 브로커 응답 fee/tax 정본 우선, 없으면 정책 폴백. - - BAT-039 (issue #4 REQ-011 CHG-20260612-01): paper 브로커 order seq Redis 영속화. 재시작 시 `PAPER-000001` 재발번 → `correlation_id` 충돌 → 매도 원장 조용히 누락되는 결정성 버그 해소. optional `SeqStore` 포트(Redis INCR / in-memory 폴백), `TREX_PAPER_REDIS_URL` 설정. - - BAT-040 (issue #25 REQ-002/006/073 CHG-20260612-01/02): `trex.slots.req` per-request full scan 제거. `list_slots(status_filter)` DB 레벨 `status = ANY(%s)` 필터 추가(포트 + PG 구현). watchdog 갱신 holding 조회는 응답 결과 재사용해 두 번째 커넥션 생략. RPC 계약(AC-1~6) 불변. - - BAT-041 (dcy-027 REQ-076/077/078): 중장기 인스턴스 운영 토대 완성. `DaemonInstanceConfig.watchdog_timeout_sec`(TOML) per-instance timeout 노출(env 폴백), `config/longterm.example.toml` 표준 프로파일 레시피(safety_sell off·market-close 미설정·watchdog 1800s), baseline 문서(`runtime-flows.md`) 중장기 운영 흐름 + 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱) + `forced_close_at` 중장기 미사용 명시. -- Excluded: - - 과거 누락 원장 행 소급 보정 (BAT-038/039 공통 — 보정 안 함) - - overlay(paused/warned) 서버측 필터 — 후속 - - watchdog 소유권 진입 외부 알림(슬랙·이메일) — 후속 Discovery - - 중장기 보호 규칙 코어 구현 — 전략 모듈 전적 책임으로 확정(dcy-027 OQ-2) - -## Rollout Plan - -1. 실행 중인 데몬 정지: `kill -TERM ` (정상 종료 대기, 마지막 로그 `Normal shutdown` 확인) -2. 코드 배포: `git pull` (또는 패키지 업데이트) -3. DB 스키마 변경 없음 — 마이그레이션 불필요 -4. 중장기 인스턴스를 운영 중이라면 per-instance TOML 에 `watchdog_timeout_sec = 1800` 추가 (선택 — 미설정 시 기존 env 폴백 유지) -5. paper 브로커 Redis 영속화를 사용할 경우 `TREX_PAPER_REDIS_URL` 환경 변수 설정 (미설정 시 in-memory 폴백, 재시작 시 seq 리셋) -6. 데몬 재기동: `.venv/bin/trex-core-daemon --config ` -7. 기동 후 slots RPC 응답 및 로그 확인 - -## Rollback Plan - -- 트리거 조건: 기동 실패, slots RPC 응답 이상, 브로커 체결 이상 -- 롤백 절차: - 1. 데몬 정지 - 2. `git revert` 또는 이전 커밋으로 체크아웃 후 재기동 - 3. DB 스키마 변경 없으므로 데이터 무손실 롤백 가능 -- 주의: 과거 paper seq 가 Redis 에 저장된 경우 롤백 후 seq 연속성은 깨질 수 있으나 기능 무영향 (seq 는 execution_id 고유성 보장용) - -## Verification Checklist - -### 배포 전 -- [ ] `pytest tests/` — 전체 통과 확인 (회귀 없음) -- [ ] BAT-037~041 각 `verification.md` Status: confirmed 확인 - -### 배포 후 -- [ ] 데몬 기동 로그 정상 (`Normal startup`, 워커 스레드 4개, RabbitMQ 토폴로지 선언) -- [ ] `trex.slots.req` 응답에 `available_slots` 필드 존재 확인 (BAT-037) -- [ ] `filter=inactive` 요청 시 inactive 슬롯만 반환 확인 (BAT-040 회귀) -- [ ] paper 브로커 매도 체결 후 ledger `tax > 0` 확인 (BAT-038) -- [ ] paper 브로커 `TREX_PAPER_REDIS_URL` 설정 시 재시작 후 seq 연속성 확인 (BAT-039) -- [ ] 중장기 TOML(`longterm.example.toml` 참고) 로 기동 시 `safety_sell_enabled=false` + `watchdog_timeout_sec` 반영 확인 (BAT-041) -- [ ] 기존 당일 인스턴스 동작 무회귀 확인 - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-06-12 22:28 KST (데몬 재기동, PID 802942) -- Completed At: - - 2026-06-12 22:35 KST -- Environment: - - 로컬 운영 환경, instance_key=test, PostgreSQL + RabbitMQ (amqp://localhost:5672) -- Operator: - - Justin Ko -- Revision or Artifact: - - `d30e915b75df3b44258cac04a60ce2baad9857f1` (main, 2026-06-12) -- Commands or Procedure: - - 기존 데몬(PID 765497) SIGTERM 종료 → `trex-core-daemon --config test` 재기동 → RabbitMQ 토폴로지 선언 확인(ready) → slots RPC 스모크 실행 - -### Profile-Specific Checks - -- app-service: - - Health Endpoint: - - RabbitMQ 토폴로지 선언 확인 — `trex.intent.req.test` + `trex.slots.req.test` 큐 수동 passive declare 성공 (2026-06-12 22:28 KST) - - Startup Log Fields: - - 데몬 기동 후 워커 스레드 4개 정상 기동, `trex.slots.req.test` consumer 선언, universe reconcile 슬롯 23개 확인 - - Request Log Fields: - - slots RPC `{filter: "all"}` 요청 → 응답 키 `['available_slots', 'degraded', 'message', 'reason', 'slots']` 정상 (available_slots=5, degraded=False, slots 23개) - - Smoke or Manual Verification: - - BAT-037: `available_slots=5` 응답 포함 확인 (max_active_slots=5, active=0 → 5) ✓ - - BAT-040: `filter=all` 응답 23개 슬롯, DB-level 필터 경로 정상 동작 확인 ✓ - - 데몬 로그 ERROR/CRITICAL 없음 ✓ - - BAT-038/039/041: 구조 변경(fee 정책·paper seq·TOML config) — 기동 경로 정상, 단위 테스트 confirmed evidence 유효 - -### Rollback Outcome - -- Rollback Required: - - no -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - yes -- Rationale: - - BAT-037~041 전체 verification confirmed (단위·통합 테스트 통과, smoke 근거 기록). DB 스키마 변경 없어 롤백 비용 낮음. slots RPC 응답 키·available_slots 값·슬롯 수 스모크 통과. 운영 중 데몬 정상 동작 확인. 비파괴적 additive 변경(fee/tax 정책·paper seq·TOML 필드·baseline 문서) 중심으로 회귀 위험 낮음. -- Recorded By: - - Justin Ko -- Recorded At: - - 2026-06-12 22:35 KST - -## Feedback Handoff - -- Observation Summary: - - (2026-06-16 13:10 KST, 배포 에이전트) REL-007이 운영 인스턴스 `trex-admin-daily-trading`(실거래 KIS 91254)에 반영됨을 실환경 검증. 현 HEAD `1f59322`가 REL-007 커밋 `d30e915`를 조상 포함(`git merge-base --is-ancestor`=YES), 데몬 PID 1778 약 2일 무중단, DB 스키마 변경 없음. - - **BAT-038(매도세 정정) 라이브 결정적 검증**: 2026-06-15 064400 매도 체결(`ledger_outbox`) 4주@97,200=388,800 KRW → tax 777.60 = **20.0bps**(=`TREX_KRX_TAX_SELL_BPS`), fee 58.32 = **1.5bps**(=`TREX_KIS_FEE_BPS`). 수정 전 `tax=0.0` 자리에 세금 정확 반영 → 실현 PnL 과대계상 버그 해소 확정. - - BAT-037/040(slots RPC) 라이브 정상: `--json` 응답·`watchdog{expired,last_seen}` 구조·`available_slots` 힌트(orb-v1 소비)·`filter=inactive` 정상. - - 본 실거래 당일매매 인스턴스에 BAT-039(paper seq)·BAT-041(중장기)는 N/A(기능 무영향). -- Discovery Input: - - watchdog 소유권 진입 시 외부 알림(슬랙·이메일·웹훅) — dcy-027 OQ-3 후속 Discovery 명시 (※ dcy-029 OQ-3 알림 전송 채널 Deferred와 합류 후보) - - 전략↔자산 할당 모델(다중 전략 per-instance, strategy 필드 복원) — dcy-025 Out of Scope 이월 - - **(2026-06-16 라이브 테스트 환류) 호가단위 주문 거부(P0)**: `slippage_pct` 한계가가 KRX 호가단위 미스냅으로 KIS 거부(issue #28) → **dcy-030 생성·confirmed, REQ-083/070(BAT-044) Implemented**로 처리 완료. - - **(2026-06-16 라이브 테스트 환류) 운영 관측성 부족**: 주문 거부가 로그에 없고 타임스탬프 부재로 진단 지연(issue #29) → **dcy-029 생성·confirmed, REQ-084~090(BAT-045/046) Implemented**로 처리 완료. -- REQ Input: - - none (라이브 환류는 위 dcy-030/dcy-029로 정규화되어 REQ-083/070/084~090로 이미 처리) -- Change Request Input: - - none (REQ-070 호가단위 정정은 dcy-030 CHG-20260616-01로 처리 완료) diff --git a/docs/releases/rel-008_20260616_tick-snap-and-observability.md b/docs/releases/rel-008_20260616_tick-snap-and-observability.md deleted file mode 100644 index 79d3fab..0000000 --- a/docs/releases/rel-008_20260616_tick-snap-and-observability.md +++ /dev/null @@ -1,122 +0,0 @@ -# REL-008: 호가단위 스냅(#28) + 운영 관측성(#29) + slot strategy-state(#26)·retryable(#27) 통합 릴리즈 - -- Status: feedback-captured -- Profile: app-service -- Included Batch: BAT-042, BAT-043, BAT-044, BAT-045, BAT-046 - -## Scope -- Included: - - **BAT-042** (dcy-028 / issue #26): slot strategy-state 저장소 — `slot_strategy_state` 테이블 + StrategyStateStore(occupancy_seq·version CAS) + write RPC `trex.slots.strategy_state.set` + occupancy_seq 가드/close 삭제 + slots.req strategy_state·occupancy_seq additive (REQ-079·080·081·082, REQ-073/074 CHG). **마이그레이션 014/015**. - - **BAT-043** (issue #27): rejected SellIntent 응답 additive `retryable: bool` (REQ-007 CHG-20260615-01). - - **BAT-044** (dcy-030 / issue #28, P0): slippage 보호 지정가 KRX 호가단위 스냅. `domain/pricing.py`(tick 유틸, REQ-083) + `execution.py` 스냅(REQ-070 CHG-20260616-01) — 상한 준수(매수 floor/매도 ceil) + band<1tick 하한/상한 보장 + 부동소수 epsilon 가드. slippage_pct>0 매수의 호가단위 위반 전량 거부 해소. - - **BAT-045** (dcy-029 P0): 구조화 로깅 기반 — ISO8601(KST) 타임스탬프·레벨, Loki JSON 출력(`TREX_LOG_FORMAT`), correlation id(slot_id)(REQ-084); 주문 결과 로깅 `event=order_submit|order_fill|order_reject`(REQ-085); 거부 ERROR 가시화(REQ-087). - - **BAT-046** (dcy-029 P1): 청산 reason+actor 동반 노출(REQ-086); 운영 조회 CLI `trex-core report`(보유·오늘 체결·실현 PnL net·대기, direct-DB)(REQ-088); 운영자 런북 `docs/operations.md`(REQ-089); baseline 정합(REQ-090). -- Excluded: - - 알림 전송 채널(dcy-029 OQ-3 Deferred — 후속 반복). - - ETF/ETN 비주식 호가체계(dcy-030 OQ-1 — 주식 전용). - - 실현 PnL의 cost-basis 매칭(net 매매 현금흐름 정의 채택). -- Scope Note: - - BAT-042/043은 단일 main 브랜치상 배포 HEAD(`00ce5d9`)의 조상으로 함께 배포되는 release-candidate 배치다. 초안 시 제외했으나, "남아있는 배치 전부를 REL-008로" 지시에 맞춰 본 릴리스에 포함하도록 정정(배포 현실 = main 통째 배포와 일치). 마이그레이션 014/015는 BAT-042 소속. - -## Rollout Plan -1. 배포 전: 전체 테스트 green 확인(537 passed/47 skipped), ruff clean, DB 스키마 변경 없음 확인(마이그레이션 불필요). -2. main 배포 커밋 체크아웃(REL-008 내용 포함) → 운영 인스턴스 데몬 재기동. -3. 배포 후: 로그에 ISO8601(KST)+레벨 prefix 확인, `event=order_submit|fill|reject` 라인 확인, slippage 매수 주문이 유효 호가로 제출(거부 0) 확인, `trex-core report` 동작 확인. -4. (옵션) `TREX_LOG_FORMAT=json` 설정 시 Loki 적재 확인. - -## Rollback Plan -- 트리거: 기동 실패 / 주문 제출 이상(호가단위·가격 오류) / 로그 포맷으로 인한 수집 파이프라인 장애 / report 오류. -- 절차: 데몬 정지 → 이전 커밋(REL-008 이전, REL-007 라인 = `1f59322`) 체크아웃 → 재기동. **DB 스키마 변경 없어 데이터 무손실**. -- 부분 롤백: `TREX_LOG_FORMAT`을 `text`(기본)로 두면 JSON/Loki 경로 비활성(비파괴적). 호가단위 스냅은 코드 경로라 부분 비활성 불가 — 전체 롤백. - -## Verification Checklist -- 배포 전: - - 전체 단위/통합 테스트 green (537 passed, 47 skipped), ruff clean. - - BAT-044/045/046 verification.md 모두 Ready for Release: true. - - DB 스키마 변경 없음(마이그레이션 불필요) 재확인. -- 배포 후: - - 로그 라인에 ISO8601(KST)+레벨 prefix 존재. - - slippage 적용 매수 주문이 유효 KRX 호가로 제출되고 `event=order_reject` 호가단위 거부가 사라짐(issue #28 해소). - - 거부 발생 시 `event=order_reject`가 ERROR 레벨로 가시화. - - `trex-core report`가 보유·오늘 체결·실현 PnL·대기 주문을 출력. - -## 배포 전 준비 근거 (Pre-Deployment Readiness — 운영자 확인용) - -운영자가 배포 직전 확인할 근거. 본 릴리스는 confirm 시점(2026-06-16) 기준 아래를 모두 충족한다(실 운영 배포·환류는 capture-release-feedback 단계). - -| 항목 | 근거 | 상태 | -| --- | --- | --- | -| 전체 테스트 green | `.venv/bin/python -m pytest -q` → **537 passed, 47 skipped**(47 skip = 실 DB/RabbitMQ 게이트), 회귀 0 | ✅ | -| Lint | `.venv/bin/ruff check trex_core/ tests/` → All checks passed | ✅ | -| Batch 검증 완료 | BAT-044/045/046 `verification.md` 모두 Ready for Release: true, Blocking Issues 없음 | ✅ | -| DB 스키마 변경 | **마이그레이션 014/015**(BAT-042 소속, 본 릴리스 포함) — 기동 시 자동 적용(additive·idempotent: `daily_slot.occupancy_seq`, `slot_strategy_state` 테이블). 구버전은 두 객체 미참조라 롤백 무손실 | ✅ | -| 호가단위 스냅(#28) | issue #28 케이스 단위 검증(147,700→148,400 / 54,500→54,700), 상한 미초과·유효 호가 불변식 테스트 | ✅ | -| 로깅 포맷(REQ-084) | text/json·ISO8601(KST)·correlation id 포맷터 단위 검증, `configure_logging` idempotent | ✅ | -| 주문 결과·거부 ERROR | `event=order_submit\|fill\|reject` + 거부 ERROR 레벨 단위 검증(caplog) | ✅ | -| 실현 PnL 집계(REQ-088) | `operator_report` net 현금흐름·KST 경계·렌더 단위 검증 | ✅ | -| 비파괴성/하위호환 | slippage 미사용·시장가·0% 경로 회귀 없음; 로그 포맷 `text` 기본; report·reason+actor additive | ✅ | -| 롤백 경로 | 이전 커밋 `1f59322`(REL-007 라인) 체크아웃 + 재기동, DB 무변경 | ✅ | - -배포 후 운영자 확인 항목은 아래 `Verification Checklist > 배포 후` 및 `Profile-Specific Checks(app-service)` 참조. - -## Release Execution Evidence -- Release Status Transition Target: released - -### Deployment Facts -- Executed At: - - 2026-06-16 13:41:22 KST -- Completed At: - - 2026-06-16 13:41:26 KST -- Environment: - - `/root/release` 운영, instance_key=`trex-admin-daily-trading` (실거래 KIS 계좌 91254) -- Operator: - - 배포 에이전트 -- Revision or Artifact: - - `1f59322`(REL-007) → **`00ce5d9`(REL-008)**. PID 1778 → 193867. -- Commands or Procedure: - - `scripts/deploy-trex-core.sh --update-only`(git pull) → `--restart-only`(데몬 재기동). 마이그레이션 014/015는 데몬 기동 시 `apply_migrations` 자동 적용. 배포 전 포지션 0(재시작 안전). - -### Profile-Specific Checks -- app-service: - - Health Endpoint: - - N/A(데몬은 HTTP 표면 없음 — BAT-007). "ready" = 4 워커 스레드 + RabbitMQ 토폴로지 선언, 로그 ERROR/traceback 0. (배포 후 확인) - - Startup Log Fields: - - ✅ 배포 후 확인: 기동 로그가 `2026-06-16T13:41:24.044+09:00 INFO pika...` 형태로 **ISO8601(KST)+레벨** prefix 전환(이전 `INFO:pika:` 타임스탬프 없음). ERROR/CRITICAL/Traceback 0. - - Request Log Fields: - - 배포 후 포지션 0이라 실거래 주문 미발생 — 단위 검증(`TestOrderResultLogging`, cid=slot_id submit→fill)으로 보장. 다음 거래일(06-17) 실거래 주문에서 재확인. - - Smoke or Manual Verification: - - ✅ #28 호가단위 스냅: 배포된 `domain/pricing.py`로 어제 거부 2건 재계산 — 000720 entry 147,700 → 148,400(유효), 006800 entry 54,500 → 54,700(유효). ✅ `trex-core report` 동작(보유 0/대기 0/오늘 체결 0/실현 PnL 0). ✅ slots RPC 145개 정상, RabbitMQ ESTABLISHED 5. 실거래 slippage 매수 거부 0 체결은 06-17 거래일 확인. - -### Rollback Outcome -- Rollback Required: - - no -- Rollback Trigger: - - none (기동 정상, #28 스냅·로깅·report 검증 통과) -- Rollback Action: - - none - -### Release Decision Evidence -- Released Decision: - - **released** (2026-06-16 13:41 KST 운영 배포 완료, `00ce5d9`, PID 193867) -- Rationale: - - BAT-044/045/046 전 verification confirmed. 운영 배포 후 #28 호가단위 스냅(147,700→148,400 / 54,500→54,700)·구조화 로깅(ISO8601 KST prefix)·`trex-core report`·데몬 건강(ERROR 0, slots RPC 145) 실환경 검증. 마이그레이션 014/015 자동 적용(additive·idempotent). 롤백 불요. -- Recorded By: - - Justin Ko (confirm) / 배포 에이전트 (운영 배포 근거) -- Recorded At: - - 2026-06-16 13:25 KST (confirm) / 2026-06-16 13:43 KST (배포 완료 근거) - -## Feedback Handoff -- Observation Summary: - - (2026-06-16 13:43 KST, 배포 에이전트) REL-008 운영 배포 완료(`00ce5d9`, PID 193867, `--restart-only`). **#28 호가단위 스냅 라이브 검증**(어제 거부 2건 재계산: 147,700→148,400 / 54,500→54,700, 유효 호가·상한 준수), **구조화 로깅 전환**(`...+09:00 INFO` KST prefix), **`trex-core report` 동작**, 마이그레이션 014/015 자동 적용 확인, 데몬 ERROR 0·slots RPC 145 정상. - - 사전 테스트: 537 passed/46 skipped/1 failed. 1 failed = `test_sandbox_token_and_submit` — 운영 `.env`(:9443)가 샌드박스 테스트에 새어든 **환경 아티팩트, REL-008 회귀 아님**. - - **거버넌스 정정(§4)**: main 단일 브랜치라 HEAD 배포 시 dcy-028(BAT-042/043, #26/#27) 코드가 동반 배포됨. 초안 시 본 릴리스에서 제외했으나 사용자 지시("남아있는 배치 전부")에 맞춰 **BAT-042/043을 REL-008에 정식 포함하도록 정정** → 추적 갭 해소(운영 실체 = 배포 HEAD와 일치). `occupancy_seq` 코어 활성, `slot_strategy_state` 핸들러는 전략 호출 시만 동작(orb-v1 미사용)→저위험. -- Discovery Input: - - **#30 Loki push 핸들러**: 현재 구조화 로그 text/json stdout만. Loki 적재(push)는 후속(이슈 #30 등록됨) — dcy-029 OQ-2/A-4(배포측 수집기) 연장선. -- REQ Input: - - none (관측성·호가단위는 REQ-070/083/084~090로 처리 완료) -- Change Request Input: - - none -- Follow-up Actions (비차단): - - **BAT-042/043(dcy-028 #26/#27) 운영 동반 배포 정식화** — 별도 release(REL-009 후보)로 retroactive 추적 권장(아래 사용자 결정 필요). - - 실거래 #28 검증: 06-17 거래일 slippage 매수가 호가단위 거부 0으로 체결되는지 확인. - - GitHub #28(수정 반영)·#29(기반 반영) close — 사용자 승인 후. diff --git a/docs/releases/rel-009_20260625_event-reliability-fill-guard-alarm-runbook.md b/docs/releases/rel-009_20260625_event-reliability-fill-guard-alarm-runbook.md deleted file mode 100644 index 2b06d6b..0000000 --- a/docs/releases/rel-009_20260625_event-reliability-fill-guard-alarm-runbook.md +++ /dev/null @@ -1,194 +0,0 @@ -# REL-009: OPS-005 이벤트 발행 신뢰성 + 체결가 가드 + 알람 emitter + 무데이터 런북 (M2 통합 릴리즈) - -- Status: confirmed -- Profile: app-service -- Included Batch: BAT-047, BAT-048, BAT-049 - -## Scope - -- Included: - - **BAT-047** (dcy-031 / OPS-005): 이벤트 발행 연결 신뢰성 — `publish_event` 1회 실패 시 강제 재연결+재시도(REQ-091), 발행 전용 연결 idle keepalive `process_data_events` 틱(REQ-092), DLQ 유입/emit 최종 실패 ERROR 경보(REQ-093), baseline 문서 정합(REQ-094). **M1 프로덕션(9e35467) 미반영 결함 해소 — idle 60초 후 첫 전이 이벤트 유실 제거.** - - **BAT-048** (dcy-032): 체결가 0/None 백필 가드 + WARN 로깅 — `_on_filled`/`_on_partial_cancel`에서 avg_price=0/None 시 지정가 백필(REQ-095), 백필/unresolvable WARN 로그(reason 필드 포함)(REQ-096), 알람 능력 스파이크 산출물·후보 A/B/C 분석(REQ-097 — 알람 실제 구현은 dcy-033 이월). **슬롯 avg_price·원장 cost basis의 무경고 0오염 제거.** - - **BAT-049** (dcy-033): 알람 시스템 1단계 — 알람 envelope 표준 JSON 스키마·`trex.alerts`(topic, durable=True) exchange 신규 선언(REQ-098/099), application `AlarmEmitter` + CRITICAL code 8종 배선(REQ-100), `runtime-flows.md` 알람 발신 흐름 기술(REQ-101). **기존 wire 계약(exchange/큐/라우팅) 불변 — 가산·격리 추가.** - - **dcy-017 후속 운영자 런북** (docs-only, 2026-06-22): 지속 무데이터/죽은 종목 위생 절차 `docs/operations.md §7` 신규 기재 — pause/resume 시맨틱 검증·Step 1~3b 절차·고아 방지 순서 명시. 코드 변경 없음. - -- Excluded: - - 이벤트 발행 영속 outbox(옵션 C) — M3 후보 - - consumer(orb/tsl) idempotency 구현 — OQ-2(각 repo 추적) - - 알람 수신·텔레그램 전달(trex-notifier) — **배포 의존성(아래 참조)** - - INFO·WARN 알람 code 배선 — M3 이후 - - 과거 0 기록 원장/슬롯 소급 정정 - -- Scope Note: - - BAT-047은 origin/main(`b2165ea`) 기준 이미 푸시됨 — 이번 배포는 현재 local main HEAD(`5bdd720`) 전체를 싣는 체크아웃 갱신이다. - - dcy-017 런북은 별도 Discovery/Batch를 생성하지 않은 docs-only 패치(커밋 `5bdd720`)이며 코드 변경 없음. - -## 배포 의존성 및 순서 (필독) - -### trex-notifier 선행 조건 - -`trex.alerts` exchange 선언은 데몬 기동 시 `declare_topology()`가 자동 수행한다. 알람 발행(`mandatory=False`)은 notifier 미연결 시 silent drop — **core 기동 자체는 notifier 없이도 안전**하다. - -그러나 알람 수신·텔레그램 전달(trex-notifier)이 연결되지 않은 상태에서 CRITICAL 이벤트가 발생하면 알람이 유실(drop)된다. 따라서: - -1. **trex-notifier 배포·바인딩 선행**: `trex.alerts`에 바인딩된 notifier 큐가 없으면 알람이 RabbitMQ에서 곧바로 drop된다. trex-notifier를 먼저 띄우고 `trex.alerts` 바인딩을 확인한 뒤 trex-core를 재시작한다. -2. **trex-core는 orb/tsl 이벤트 발행 경로의 중심**이다. trex-core 재시작 시간 동안 orb/tsl이 AMQP를 통해 trex-core로 전송하는 slot 의도(intent)가 일시 지연될 수 있다. 가능하면 거래 비활성 시간대(장 외)에 배포한다. - -### 배포 순서 요약 - -``` -1. (장 외 시간) trex-notifier 배포 및 trex.alerts 바인딩 확인 -2. trex-core main 갱신 → pip install -e . (의존성 변경 없음 — 확인 후 생략 가능) → 데몬 재시작 -3. (선택) trex-orb-v1 / trex-tsl-v1 재시작 — 코드 변경 없어 강제 아님, keepalive/재연결은 core 측 개선임 -``` - -## Rollout Plan - -1. **배포 전 준비** - - 전체 테스트 green 확인: `python3 -m pytest -q` → **608 passed, 47 skipped** (현재 HEAD `5bdd720` 기준 검증 완료). - - ruff clean 확인: `ruff check trex_core/ tests/` → `All checks passed!`. - - BAT-047/048/049 `verification.md` 상태: BAT-047 approved, BAT-048 verified(팀장 confirm 완료), BAT-049 release-candidate(팀장 실인프라 e2e 포함 confirmed). - - DB 스키마 변경 없음 — 신규 마이그레이션 없음. `apply_migrations` 기존 001~015 idempotent 재실행. - - trex-notifier 배포·`trex.alerts` 바인딩 완료 확인 (선행 조건). - -2. **배포 실행** (운영 환경 — bare .venv, instance_key=`trex-admin-daily-trading`) - - `git pull` (또는 `git fetch && git checkout 5bdd720`) — local main을 프로덕션 체크아웃 경로에 반영. - - 의존성 변경 여부 확인: `git diff 9e35467 HEAD -- pyproject.toml` → 변경 없으면 `pip install -e .` 생략 가능. - - `trex-core-daemon` 재시작 (기존 PID 종료 후 신규 기동). - - 기동 로그에서 `trex.alerts` exchange 선언 성공(`declare_topology` 완료) 확인. - -3. **배포 후 검증** (아래 Verification Checklist 참조) - -## Rollback Plan - -- **트리거**: 데몬 기동 실패 / `trex.alerts` exchange 선언 오류 / 이벤트 발행 경로 이상(재시도 루프 과다·DLQ 급증) / 체결 처리 이상(백필 WARN 폭증·슬롯 PENDING 고착) / 기존 서비스 연동 장애. -- **절차**: - 1. 데몬 정지. - 2. 이전 커밋 체크아웃: `git checkout 9e35467` (REL-008 배포본 = M1 프로덕션 라인). - 3. `trex-core-daemon` 재기동. - 4. **DB 스키마 변경 없음** — 마이그레이션 없으므로 롤백 시 데이터 무손실. - 5. `trex.alerts` exchange는 롤백 후에도 RabbitMQ에 남아있으나 발행자가 없어 무해. 필요 시 관리 UI에서 수동 삭제 가능(비필수). -- **부분 완화**: `AlarmEmitter`는 best-effort 삼킴 패턴으로 기동·발행 경로가 알람 실패에 비차단. 알람 발행 오류만 발생하면 롤백 대신 notifier 재바인딩을 먼저 시도한다. - -## Verification Checklist - -### 배포 전 - -- [ ] 전체 테스트 green: `python3 -m pytest -q` → 608 passed, 47 skipped, 0 fail (HEAD `5bdd720`). -- [ ] ruff clean: `ruff check trex_core/ tests/` → `All checks passed!`. -- [ ] BAT-047 verification: Status=approved (confirmed 2026-06-19). -- [ ] BAT-048 verification: Status=verified (confirmed 2026-06-21). -- [ ] BAT-049 verification: Status=release-candidate (팀장 실인프라 e2e confirmed 2026-06-21). -- [ ] trex-notifier 배포·`trex.alerts` 바인딩 완료 (선행). -- [ ] DB 스키마 변경 없음 재확인 (마이그레이션 없음). -- [ ] `docs/interface-contract.md` 변경 범위 확인: BAT-049 `trex.alerts` 섹션 추가(기존 섹션 diff=0). -- [ ] **OPS-005 배포갭 인지**: 현재 프로덕션 M1(`9e35467`)은 idle keepalive·재시도 미포함 — 이번 배포가 해소. - -### 배포 후 - -- [ ] **OPS-005 해소 확인 (BAT-047 핵심)**: 장 시작 후 orb/tsl → core idle 60초 이상 경과 후 첫 slot 전이 이벤트가 정상 수신되는지 확인. `ConnectionReset` / `StreamLostError` 로그 무재발 확인. `slot_filled` / `slot_entered` 이벤트가 orb/tsl에 도달하는지 확인. -- [ ] 데몬 기동 로그: `declare_topology` 정상 완료, `trex.alerts` exchange 선언 포함, ERROR/CRITICAL/Traceback 0. -- [ ] keepalive 동작 확인: 기동 후 60초 경과 시 `keepalive_tick` 호출 로그(또는 무오류) 확인. -- [ ] 알람 발행 경로 확인: CRITICAL 이벤트 발생 시 `alarm.critical.trex-core` routing key로 `trex.alerts`에 발행되고 trex-notifier가 텔레그램 전달하는지 확인 (e2e 경로). -- [ ] 체결 처리: 정상 체결 시 avg_price 백필 WARN 미발생 확인. KIS avg_price 이상 케이스 발생 시 WARN 로그 필드(slot_id/symbol/backfill_price/reason) 확인. -- [ ] **AMQP wire 무회귀**: `trex.events`, `trex.intent.req`, `trex.slots.req`, `trex.control.req` exchange/큐/라우팅 형식 불변 확인. orb/tsl 연동 장애 없음 확인. -- [ ] `trex-core report` 정상 동작 (REL-008 기능 회귀 없음). - -## AMQP Wire 계약 무회귀 체크포인트 - -이번 릴리즈의 AMQP 변경 범위는 아래로 한정된다. orb/tsl 연동에 영향 없음을 명시한다. - -| 항목 | 변경 여부 | 근거 | -|---|---|---| -| `trex.events` exchange/토폴로지 | 불변 | BAT-047 Out of Scope 명시, verification NFR-1 PASS | -| `trex.intent.req` 큐/라우팅 | 불변 | BAT-047/048/049 모두 불변 확인 | -| `trex.slots.req` 큐/스키마 | 불변 | BAT-047/048/049 모두 불변 확인 | -| `trex.control.req` 큐 | 불변 | 변경 없음 | -| 슬롯 이벤트 페이로드 스키마 | 불변 | BAT-047 wire 무회귀 PASS | -| `trex.alerts` exchange | **신규 추가** (가산·격리) | BAT-049 REQ-099. 기존 exchange/큐에 무영향. mandatory=False — unroutable silent drop | -| `docs/interface-contract.md` | **trex.alerts 섹션 신규** (기존 섹션 diff=0) | BAT-049 REQ-098 AC-3 PASS | - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-06-25 -- Completed At: - - 2026-06-25 PASS (34700f3 on 5bdd720, PID 849009) -- Environment: - - 운영 (`/root/release` 또는 해당 경로, instance_key=`trex-admin-daily-trading`, 실거래 KIS 계좌 91254), bare .venv -- Operator: - - 사장님 -- Revision or Artifact: - - `9e35467`(REL-008 M1 현재 프로덕션) → **`34700f3` on `5bdd720`(REL-009 배포본)**. - - BAT-047: `d182464`, BAT-048: `f1b649c`, BAT-049: `d4c5fcb`, dcy-017 런북: `5bdd720`. -- Commands or Procedure: - - `git pull` (또는 체크아웃 `5bdd720`) → `pip install -e .` (의존성 변경 시) → `trex-core-daemon` 재시작. - - 마이그레이션: 기동 시 `apply_migrations` 자동 실행 (001~015 idempotent, 신규 없음). - -### Profile-Specific Checks - -- app-service: - - Health Endpoint: - - N/A (데몬은 HTTP 표면 없음). "ready" = 워커 스레드 + RabbitMQ 토폴로지 선언(`trex.alerts` 포함) 완료, 로그 ERROR/Traceback 0. - - Startup Log Fields: - - PASS. `declare_topology` 완료 로그, `trex.alerts` exchange 선언 확인(AMQP 5연결 정상), ERROR/CRITICAL/Traceback 0. - - Request Log Fields: - - PASS. `slot_filled` 이벤트 정상 발행 확인. OPS-005 ConnectionReset 무재발 확인. 12:02 sell_filled 집행 정상. - - Smoke or Manual Verification: - - PASS. idle keepalive 동작, `slot_filled` 이벤트 발행 경로 정상. `trex.alerts` exchange 선언 5연결 확인. - -### Rollback Outcome - -- Rollback Required: - - No -- Rollback Trigger: - - none -- Rollback Action: - - none - -### Release Decision Evidence - -- Released Decision: - - released -- Rationale: - - 2026-06-25 배포 PASS. 2026-06-26 첫 거래일 OPS-005(RabbitMQ ConnectionReset) 무재발(0건) 확인. slot_filled 이벤트 정상 발행. 12:02 sell_filled 집행 정상. AMQP 5연결 정상. 롤백 없음. -- Recorded By: - - 팀장 -- Recorded At: - - 2026-06-26 - -## Feedback Handoff - -### M2 배포 + 첫 거래일 환류 (2026-06-26) - -- Observation Summary: - - **배포**: 2026-06-25 PASS (34700f3 on 5bdd720, PID 849009). trex.alerts 선언, AMQP 5연결 정상. - - **첫 거래일 검증**: OPS-005(RabbitMQ ConnectionReset) 무재발(0건) — 해소 확인. slot_filled 이벤트 정상 발행. 12:02 sell_filled 집행 정상. - - **신규 발견**: slot_filled 이벤트 NO_ROUTE → DLQ 유입 확인. 라우팅키 `slot_filled_*._.{symbol}`(strategy_id 자리 `_`). 원인: `_emit_event`(execution.py:345/398)가 자산중심 슬롯(dcy-020, portfolio_id 단위, strategy_id 미보유) 기반 발행 → orb/tsl의 `trex.events.strategy.{strategy_id}` 바인딩과 불일치. 체결이벤트 채널(REQ-003) 미전달(전략은 slots RPC 폴링 주력이라 비치명적이나 채널 깨짐+DLQ 누적). - -- Discovery Input: - - **★ slot_filled NO_ROUTE / fill→strategy 라우팅 재설계** (trex-core/P1/M3): 자산중심 모델에서 fill 이벤트를 strategy 바인딩으로 전달하는 라우팅 매핑이 없음. `_emit_event`의 라우팅키 생성 로직이 strategy_id를 참조하지 않아 orb/tsl의 `trex.events.strategy.{strategy_id}` 바인딩과 불일치 → NO_ROUTE → DLQ 누적. 자산중심 슬롯(portfolio_id 단위)에서 fill→strategy 라우팅 재설계 필요. 보드 등록됨. **교차 영향 사안**: wire 계약(exchange/라우팅키/바인딩) 변경 — 팀장 검토 후 Discovery 개시 필요. - - **이벤트 발행 영속 outbox**: BAT-047 Out of Scope으로 이월됨(M3 후보). at-least-once 중복 이벤트 소비자 멱등성도 함께 추적. - - **실 broker 통합 CI**: half-open/idle 60초/broker 재시작 시나리오는 현재 mock unit으로만 검증. `TREX_RABBITMQ_TEST_URL` 포함 통합 환경 구성(M3 후보). - - **INFO·WARN 알람 code 배선**: CRITICAL 8종 이후 후속 범위(dcy-033 범위 경계 밖, M3 후보). - -- REQ Input: - - none - -- Change Request Input: - - none - -## 미해결 / 잔여 위험 - -| 항목 | 수준 | 설명 | 해소 방안 | -|---|---|---|---| -| trex-notifier 미선행 시 알람 유실 | Medium | notifier 없이 core 배포 시 CRITICAL 알람 silent drop | 배포 순서 준수: notifier → core | -| 지정가 백필 추정치 오차 | Medium | BAT-048 R-1: 실제 체결가 ≠ 지정가 가능. reason=avg_price_backfill WARN으로 추적 가능 | 운영자 WARN 발생 시 operations.md §5 절차 참조 | -| fill_price_unresolvable 슬롯 고착 | Low | BAT-048 R-2: limit_price=None 시 PENDING 고착 → watchdog 탐지 필요 | 기존 stale_pending watchdog 동작, 운영자 개입 | -| half-open/idle 실인프라 검증 | Low | BAT-047 mock unit 검증 — 실 broker 통합 skip (환경 미구성) | 배포 후 OPS-005 재발 모니터링으로 실증, M3 통합 CI | -| NFR-2 publish 블록 최악 케이스 | Low | BAT-047: 1차 실패 시 원 socket_timeout(5.0s) 초과 가능 | 배포 후 모니터링, 필요 시 후속 REQ(publish timeout 강화) | -| BAT-049 통합(실 RabbitMQ) 3건 | Low | declare_topology/mandatory=False/기존 topology 불변은 팀장 실인프라 e2e에서 확인됨(2026-06-21) | 배포 후 Smoke 체크포인트로 재확인 | diff --git a/docs/releases/rel-010_20260629_reconcile-coldstart-wac-report-credential-dormant.md b/docs/releases/rel-010_20260629_reconcile-coldstart-wac-report-credential-dormant.md deleted file mode 100644 index d0e3d29..0000000 --- a/docs/releases/rel-010_20260629_reconcile-coldstart-wac-report-credential-dormant.md +++ /dev/null @@ -1,204 +0,0 @@ -# REL-010: 차단형 reconcile + cold-start + WAC 운영리포트 + KIS 인증정보 휴면 배포 (M3 통합 릴리즈) - -- Status: released -- Profile: app-service -- Included Batch: BAT-050, BAT-051, BAT-052, BAT-053, BAT-054 - -## Scope - -- Included: - - **BAT-050** (dcy-034 / core-A): 차단형 reconcile 게이트 — reconcile-warning durable store(`slot_reconcile_warning` 테이블, REQ-102), B/C 불일치 슬롯 → warning 전이(REQ-103), 전략 차단 게이트(pause ∨ reconcile-warning 중 하나 → buy/외부sell 차단, 안전망 우회 보장, REQ-104), 차단/해제 전이 구조화 로깅 + 고영향 알람(REQ-107), 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습(REQ-108). - - **BAT-051** (dcy-034 / core-B): sync/verify CLI cold-start onboarding — `trex-core sync` 명령(INACTIVE→HOLDING 전이 + WAC seed + reconcile-warning 해제, REQ-105), `trex-core verify` 명령(C-case 슬롯 재대조 + warning 해제, REQ-106). INACTIVE→HOLDING 전이 엣지 도메인 추가. - - **BAT-052** (dcy-035): WAC 실현손익 운영리포트 통합 — portfolio-hub `/pnl/realized` read 포트+어댑터+stub(REQ-109), `trex-core report`에 WAC 실현손익 섹션 통합(REQ-110), portfolio-hub 미가용 시 degrade(폴백 없음, REQ-111). **매수 수수료를 WAC에 편입(CHG-20260626-02)**. 전일매수+당일매도 시나리오에서 운영리포트가 portfolio-hub WAC 정확값을 표시. - - **BAT-053** (dcy-036): 그날-시작 KIS 인증정보 fetch + fail-safe 로드 스캐폴드 — portfolio-hub broker credential read 포트+어댑터+stub(REQ-112), KISConfig 소스 우선순위 opt-in(`credential_fetch_enabled`) + break-glass 환경변수(REQ-113), fail-closed 신규 진입 차단 + CRITICAL 알람(REQ-114), 그날 고정 캐시 + portfolio-hub 장애 비중단(REQ-115). **`credential_fetch_enabled` 기본값 = `false` — opt-in OFF 시 기존 동작 byte-for-byte 보존.** - - **BAT-054** (dcy-036): 그날-시작 KIS 인증정보 양의 경로 주입 — KISBroker `reinitialize(cred)` 메서드(REQ-113 AC-2, REQ-115 AC-1), break-glass 로그 content assert 보강(REQ-113 AC-4). **`credential_fetch_enabled = false`(기본) 경로 완전 보존 — 코드는 배포되나 비활성(휴면). 실거래 KIS 인증정보 경로 미전환.** - -- Excluded: - - **dcy-036 실거래 활성화(credential_fetch_enabled = true 플립)**: 이번 릴리즈에서 비활성 배포. 플립 전환은 별도 절차(사장님 결재 + pf-hub dcy-010 배포 선행 + 실 KIS 통합검증 포함) 필요 — 아래 "후속 플립 선결 조건" 참조. - - reconcile 비차단 거버넌스 후속(AC-9 change-req 등): M3 백로그. - - 실 portfolio-hub 통합 E2E 검증(dcy-010 배포 후 가능): REL-010 go 조건 아님. - - INFO·WARN 알람 code 배선: dcy-033 범위 경계 밖, M3 이후. - -- Scope Note: - - **dcy-036 휴면 배포**: BAT-053/BAT-054 코드는 `credential_fetch_enabled = false`(기본값) 경로에서 완전히 비활성. QC가 opt-in OFF 경로에서 byte-for-byte 회귀 보존 확인. 라이브 실거래 무영향 보장. - - **BAT-054 PR #35 머지 완료**: 배포 선결 조건 충족. - - **배포 대상 revision**: `314047b`(origin/main). BAT-050~054 전체 + slot_filled 라우팅 수정 #36 + verify ImportError 핫픽스 `51cc3cc` 포함. - - **REL-010 구성**: dcy-034(BAT-050+051 = core-A+B) + dcy-035(BAT-052) + dcy-036(BAT-053+054 = 휴면). - - **dcy-036 양의경로 휴면 배포**: `credential_fetch_enabled=false`(기본값) 유지. 코드는 `314047b`에 포함되나 비활성. 실 KIS 인증정보 경로 미전환 — 실거래 무영향 확인. 플립 활성화는 중장기 인스턴스 후속(portfolio-hub dcy-010 배포 + 실 KIS 통합검증 선행 필요) — 이번 릴리즈 go 조건 아님. - -## 배포 선결 조건 (충족 완료) - -1. **PR #35 사장님 결재·머지** — 완료. BAT-054 `feat/dcy-036-kis-credential-inject` → main 머지 완료. -2. **QC confirm-batch-verification** (BAT-054) — 완료. release-candidate 전환 확인. -3. **전체 테스트 green** — 완료. main HEAD `314047b` 기준 0 fail (757 passed, 47 skipped 기준). - -## 후속 플립 선결 조건 (이번 릴리즈 go 조건 아님, 별도 기재) - -`credential_fetch_enabled = true` 전환(실거래 KIS 인증정보 경로 활성화)은 이 릴리즈와 **분리**된 별도 절차다. 활성화 전 아래 조건이 모두 충족되어야 한다. - -- portfolio-hub dcy-010 배포 완료 및 `/v1/user_info` broker credential 엔드포인트 정상 응답 확인. -- 실 portfolio-hub + 실 KIS mock(또는 openapivts) 환경에서 그날-시작 fetch → KISBroker reinitialize → 장중 주문 처리까지 end-to-end 통합 검증(REQ-115 AC-1 완전 충족). -- REQ-113 AC-1 실 KIS 통합검증 통과. -- 사장님 결재. - -## Rollout Plan - -1. **배포 전 준비** - - PR #35 머지 확인 — main HEAD가 BAT-054 구현 커밋 포함인지 git log로 검증. - - 전체 테스트 green 확인: PR #35 머지 후 main에서 `python3 -m pytest -q` → 0 fail (기준: 757 passed, 47 skipped). - - BAT-050 Status = release-candidate / BAT-051 Status = release-candidate / BAT-052 Status = release-candidate / BAT-053 Status = release-candidate 확인. - - BAT-054 Status = release-candidate 전환 확인 (QC confirm 완료 후). - - DB 마이그레이션 확인: BAT-050이 `slot_reconcile_warning` 테이블 신규 마이그레이션(`016_...sql`) 추가. 기동 시 `apply_migrations` 자동 실행 — 신규 테이블 생성 정상 완료 확인 필요. - - 의존성 변경 확인: `git diff 5bdd720 HEAD -- pyproject.toml` → 변경 없으면 `pip install -e .` 생략 가능(BAT-050~054 범위 기준 의존성 추가 없음). - -2. **배포 실행** (운영 환경 — `/root/release/trex-core`, instance_key=`trex-admin-daily-trading`, PID 849009 기준) - - 대상 커밋으로 체크아웃: `git fetch origin && git checkout main && git pull` (PR #35 머지 완료 후). - - 의존성 변경 시: `pip install -e .` - - 마이그레이션: `trex-core-daemon` 기동 시 `apply_migrations` 자동 실행 (001~016 idempotent, `slot_reconcile_warning` 신규 생성 포함). - - `trex-core-daemon` 재시작 (기존 PID 종료 → 신규 기동). - - 기동 로그에서 마이그레이션 완료(`apply_migrations` success) 및 워커 스레드 정상 기동 확인. - - tsl instance_key 데몬 동일 절차 반복 (동일 코드베이스 공유). - -3. **배포 후 검증** (아래 Verification Checklist 참조) - -4. **저녁 장외 윈도우 메모**: 장외 배포이므로 orb/tsl 전략 데몬 일시 연결 단절 허용. 무중단 절차 불필요. 배포 완료 후 reconcile sync/verify 동작 스모크 먼저 수행. - -## Rollback Plan - -- **트리거**: 데몬 기동 실패 / `slot_reconcile_warning` 마이그레이션 오류 / reconcile 차단 오발동(정상 슬롯 차단) / INACTIVE→HOLDING 전이 에러 / 운영리포트 이상(WAC 섹션 오류로 리포트 전체 불렌더) / 기존 AMQP wire 연동 장애 / credential_fetch_enabled OFF 경로에서 예상치 못한 fetch 시도 로그. -- **절차**: - 1. 데몬 정지. - 2. 이전 커밋 체크아웃: `git checkout 5bdd720` (REL-009 배포본). - 3. `pip install -e .` (필요 시). - 4. `trex-core-daemon` 재기동. - 5. **마이그레이션 롤백 주의**: `slot_reconcile_warning` 테이블은 자동 롤백 없음. 롤백 후에도 테이블이 DB에 잔존하나, 이전 코드에서 참조하지 않아 무해. 수동 삭제 필요 시: - ```sql - DROP TABLE IF EXISTS slot_reconcile_warning; - ``` - 6. **데이터 무손실**: 기존 slots / pause_store 스키마 불변. reconcile_warning 테이블만 가산. 롤백 후 운영 상태 정합 유지됨. -- **부분 완화**: credential_fetch_enabled=OFF(기본값) 배포이므로 BAT-053/054 코드 경로가 실행되지 않아, 해당 코드 관련 트리거는 이 릴리즈에서 사실상 발생 불가. - -## Verification Checklist - -### 배포 전 - -- [x] PR #35 머지 완료 확인 (BAT-054 `feat/dcy-036-kis-credential-inject` → main) — PASS. -- [x] BAT-054 confirm-batch-verification 완료 확인 (Status = release-candidate) — PASS. -- [x] 전체 테스트 green: main HEAD `314047b`에서 `python3 -m pytest -q` → 0 fail (기준: 757 passed, 47 skipped) — PASS. -- [x] BAT-050 / BAT-051 / BAT-052 / BAT-053 / BAT-054 모두 Status = release-candidate 확인 — PASS. -- [x] DB 마이그레이션 파일 확인: `trex_core/adapters/persistence/migrations/016_*.sql` 존재 여부 — PASS. -- [x] `pyproject.toml` 의존성 변경 유무 확인 — 의존성 추가 없음, `pip install -e .` 생략 가능. -- [x] `docs/interface-contract.md` 변경 범위 확인: BAT-050~054 범위에서 AMQP wire 계약 변경 없음(기존 섹션 diff=0) — PASS. -- [x] **credential_fetch_enabled 기본값 = false 확인** — PASS. 기본값 false 유지 확인. - -### 배포 후 — 스모크 - -- [x] **데몬 기동 로그**: `apply_migrations` 완료(016번 포함) / 워커 스레드 정상 / ERROR/CRITICAL/Traceback 0 — PASS. -- [x] **`slot_reconcile_warning` 테이블 생성 확인**: `SELECT COUNT(*) FROM slot_reconcile_warning;` → 0 rows (초기 상태, 에러 없음) — PASS. -- [x] **dcy-034 reconcile sync 동작 확인 (BAT-051 핵심)**: `trex-core sync --all --dry-run` exit0 (A-case guard skip 포함) — PASS. -- [x] **dcy-034 reconcile verify 동작 확인 (BAT-051 핵심)**: `trex-core verify` exit0 (no C-warned slots) — PASS. 초기 기동 시 `ImportError: make_broker` 발생 → 핫픽스 `51cc3cc`(make_broker→_build_broker) 반영 후 PASS. -- [x] **dcy-035 WAC 운영리포트 정합 확인 (BAT-052 핵심)**: `trex-core report --json` exit0 — PASS. -- [x] **dcy-036 휴면 확인 (BAT-053/054 핵심)**: `credential_fetch_enabled=false` 경로 동작 확인. portfolio-hub fetch 시도 로그 미발생 — PASS. -- [x] **AMQP wire 무회귀**: `slot_filled_sell..` 라우팅 synthetic RabbitMQ probe — NO_ROUTE/DLQ 증가 없음 PASS. `trex.events`, `trex.intent.req`, `trex.slots.req`, `trex.control.req` 정상 동작 확인 — PASS. -- [x] **reconcile 차단 오발동 없음**: 재기동 후 ERROR/CRITICAL/Traceback 미발생 확인 — PASS. -- [x] `trex-core report` 정상 동작 (REL-009 기능 회귀 없음) — PASS. - -## AMQP Wire 계약 무회귀 체크포인트 - -| 항목 | 변경 여부 | 근거 | -|---|---|---| -| `trex.events` exchange/토폴로지 | 불변 | BAT-050~054 전체 Out of Scope 명시 | -| `trex.intent.req` 큐/라우팅 | 불변 | BAT-050 REQ-108 NFR-4 PASS | -| `trex.slots.req` 큐/스키마 | `reconcile_warned` 필드 **additive 추가** | BAT-050 REQ-104 AC-4~8. 기존 클라이언트 무시 가능 (JSON extra key). orb/tsl 영향 없음. | -| `trex.control.req` 큐 | 불변 | 변경 없음 | -| `trex.alerts` exchange | 불변(REL-009에서 이미 추가됨) | BAT-053 CRITICAL 알람 사용(기존 경로 활용) | -| 슬롯 이벤트 페이로드 스키마 | 불변 | BAT-050 `test_slot_state_enum_unchanged` PASS | -| SlotState enum 값 | 불변(5개) | BAT-050 `test_slot_state_enum_unchanged` PASS | -| ALLOWED_TRANSITIONS | **14개** (13→14: INACTIVE→HOLDING 추가) | BAT-051 sync-only 엣지. 일반 전략 경로(INACTIVE→PENDING_BUY) 무영향. `test_normal_strategy_path_unchanged` PASS | - -## Release Execution Evidence - -- Release Status Transition Target: released - -### Deployment Facts - -- Executed At: - - 2026-06-30 (M3 배포 윈도우, 장외) -- Completed At: - - 2026-06-30 -- Environment: - - 운영 (`/root/release/trex-core`, instance_key=`trex-admin-daily-trading`, 실거래 KIS 계좌 91254), bare .venv -- Operator: - - 운영팀 확인 -- Revision or Artifact: - - `5bdd720`(REL-009 배포본 = 이전 프로덕션) → **`314047b`** (origin/main; BAT-050~054 전체 + slot_filled 라우팅 수정 #36 + verify ImportError 핫픽스 `51cc3cc` 포함) - - BAT-050: commit `c193388`, BAT-051: commit `07b393d`, BAT-052: commit `73d3b35`+`d3e4af5`, BAT-053: commit `e4e5c70`, BAT-054: commit `6b066e1` - - 핫픽스: `51cc3cc` (verify ImportError `make_broker` → `_build_broker` 수정, 배포 중 검출·즉시 반영) -- Commands or Procedure: - - `git fetch origin && git checkout main && git pull` (→ `314047b`) - - 의존성 추가 없음 — `pip install -e .` 생략 - - 마이그레이션: `trex-core-daemon` 기동 시 `apply_migrations` 자동 실행 (001~016 idempotent, 016번 신규 `slot_reconcile_warning` 테이블 생성) - - `trex-core-daemon` 재시작 (기존 PID 종료 → 신규 기동) - -### Profile-Specific Checks - -- app-service: - - Health Endpoint: - - N/A (데몬은 HTTP 표면 없음). "ready" = 워커 스레드 + RabbitMQ 토폴로지 선언 완료 + 016번 마이그레이션 완료, 로그 ERROR/Traceback 0. - - Startup Log Fields: - - `apply_migrations` 완료 (016번 신규 포함), 워커 스레드 정상 기동, ERROR/CRITICAL/Traceback 0 확인. - - Request Log Fields: - - N/A (데몬 구조 — AMQP 메시지 처리, HTTP 없음). - - Smoke or Manual Verification: - - `trex-core report --json` exit0 PASS. - - `trex-core sync --all --dry-run` exit0 (A-case guard skip) PASS. - - `trex-core verify` exit0 (no C-warned slots) PASS — 초기 `ImportError: make_broker` 핫픽스 `51cc3cc` 반영 후 PASS. - - `slot_filled_sell..` synthetic RabbitMQ probe: NO_ROUTE/DLQ 증가 없음 PASS. - - 재기동 후 ERROR/CRITICAL/Traceback 미발생 PASS. - - `credential_fetch_enabled=false` 유지 — portfolio-hub fetch 시도 로그 미발생 PASS. - -### Rollback Outcome - -- Rollback Required: - - 불필요. 스모크 전 항목 PASS. -- Rollback Trigger: - - N/A -- Rollback Action: - - N/A. 배포 중 verify `ImportError: make_broker` 발생 시 핫픽스 `51cc3cc`(make_broker→_build_broker)를 즉시 반영 후 재기동으로 해소 — 전체 롤백 미실행. - -### Release Decision Evidence - -- Released Decision: - - GO. 모든 스모크 항목 PASS. 핫픽스 `51cc3cc` 포함 배포 revision `314047b` 운영 적용 완료. -- Rationale: - - BAT-050~054 전 항목 release-candidate 확인 완료. 스모크(report/sync/verify/wire/credential) 전 항목 PASS. verify ImportError는 배포 중 핫픽스로 즉시 해소됨. `credential_fetch_enabled=false` 유지로 dcy-036 양의경로 비활성 — 실거래 무영향 확인. 플립 활성화는 중장기 인스턴스 후속에서 별도 처리. -- Recorded By: - - 운영팀 (M3 배포 윈도우, 2026-06-30) -- Recorded At: - - 2026-06-30 - -## Feedback Handoff - -- Observation Summary: - - M3 배포 완료(2026-06-30). 스모크 전 항목 PASS. 배포 중 verify `ImportError: make_broker` 1건 검출 → 핫픽스 `51cc3cc`(make_broker→_build_broker) 즉시 반영으로 해소. 이후 재기동·스모크 전 PASS. `credential_fetch_enabled=false`(dcy-036 휴면) 유지 — 실거래 무영향. slot_filled 라우팅(#36) PASS 확인. -- Discovery Input: - - **dcy-036 실거래 활성화**: `credential_fetch_enabled = true` 플립 — portfolio-hub dcy-010 배포 + 실 KIS 통합검증 선행 후 별도 릴리즈. REQ-115 AC-1 완전 충족 선결. - - **slot_filled NO_ROUTE / fill→strategy 라우팅 재설계** (REL-009 환류 이월): M3 P1. fill 이벤트 라우팅키 strategy_id 미참조 문제. - - **reconcile 비차단 거버넌스 후속**: AC-9 change-req 등 M3 백로그. -- REQ Input: - - none -- Change Request Input: - - none - -## 알려진 리스크 - -| 항목 | 수준 | 설명 | 해소 방안 | -|---|---|---|---| -| ~~BAT-054 PR #35 미머지~~ | ~~Critical~~ | ~~배포 선결 조건 미충족~~ | **해소됨** — PR #35 머지 완료, 배포 revision `314047b` 반영 | -| ~~BAT-054 confirm-batch-verification 미완료~~ | ~~High~~ | ~~verification.md Status=draft~~ | **해소됨** — release-candidate 전환 완료 | -| slot_reconcile_warning 마이그레이션 실패 | Medium | DB 권한 부족 또는 기존 테이블 충돌 시 기동 실패 | **배포 중 미발생** — 016번 마이그레이션 정상 완료 확인 | -| reconcile 차단 오발동 | Medium | B/C 케이스 오탐 시 정상 슬롯 차단 가능 | 배포 후 장 시작 1 reconcile 주기 집중 모니터링 유지. 현재 미발생. | -| WAC 리포트 degrade | Low | portfolio-hub BASE_URL 미설정 또는 미가용 시 WAC 섹션만 에러 표시 | 타 섹션 정상 — 비치명적. `trex-core report --json` exit0 PASS 확인 | -| dcy-036 휴면 — byte-for-byte 회귀 보존 의존 | Low | opt-in OFF 경로에서 KIS fetch 로직 완전 비활성 의존 | **배포 중 확인 완료** — `credential_fetch_enabled=false` 유지, portfolio-hub fetch 시도 로그 미발생 PASS | -| REQ-115 AC-1 통합 검증 미완 | Low | 실 portfolio-hub + KIS mock 환경 미확보 — stub 기반만 검증. 이번 릴리즈 go 조건 아님 | 후속 플립 절차(중장기 인스턴스)에서 선결 조건으로 관리 | -| verify ImportError(배포 중 검출·해소) | Low | 초기 기동 시 `ImportError: make_broker` 발생 | **해소됨** — 핫픽스 `51cc3cc`(make_broker→_build_broker) 즉시 반영, 재기동 후 PASS | diff --git a/docs/releases/rel-011_20260716_m5_operator-broker-truth-hotfix.md b/docs/releases/rel-011_20260716_m5_operator-broker-truth-hotfix.md deleted file mode 100644 index a2bbbf3..0000000 --- a/docs/releases/rel-011_20260716_m5_operator-broker-truth-hotfix.md +++ /dev/null @@ -1,90 +0,0 @@ -# REL-011: M5 trex-core operator broker truth hotfix - -- Status: **Released** -- Profile: `app-service` -- Target window: M5 장외 배포 윈도우 (승인 후) -- Owner: 팀장 / 운영팀 -- Included batch: **BAT-057** (`REQ-119`, `REQ-120`, `REQ-121`, `REQ-122`) -- Source discovery: `dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` -- Project control card: `★M5 core release: operator broker truth hotfix (BAT-057 / REL-011)` — Project #7, `P0`, `Release-Pending` -- M5 coordination: [M5 coordinated release train readiness](../runbooks/m5-coordinated-release-train-readiness.md) - -## Release intent - -`credential_fetch_enabled=true` KIS 인스턴스에서 daemon과 운영자 `sync`/`verify`가 동일한 broker credential source와 holding truth를 사용하게 한다. KIS balance/token/auth/transport 실패를 성공한 빈 balance로 축소하지 않고 fail-loud로 종료하며, managed slot이 없는 broker holding과 managed non-holding divergence를 구분해 자동 보정 없이 보존한다. - -이 릴리즈는 M5의 trex-core 선행 배포다. `portfolio-hub` 및 `trex-web`의 M5 배포 게이트와는 별도 release artifact지만, M5 release train의 필수 구성요소로 함께 추적한다. - -## Source and baseline - -| Item | Evidence | -| --- | --- | -| Release source pin | `6d90f8951dbf354eb41301492d0bcaf0af24d882` (`main`; BAT-057 requirement-implementation confirmation 포함) | -| Runtime implementation | PR #42 merge `94e148940adb008db0bbd215052a1e984f7c566b` | -| Prior operational core baseline | Project #7 M4 evidence: `7845fbe` (배포 당일 실제 checkout SHA를 다시 캡처해야 하며, 이를 현 운영 상태의 추정값으로 사용하지 않는다) | -| Batch gate | BAT-057 `confirmed` (`confirm-batch-verification`, 2026-07-15); independent QC `PASS-WITH-NOTES`, acceptance blocker 0 | -| DB/dependency delta from M4 baseline | `pyproject.toml` 및 persistence migration 변경 없음 | -| Wire delta | AMQP 및 portfolio-hub external wire contract 변경 없음; `docs/interface-contract.md`에는 broker-read failure/reconciliation boundary만 명문화 | - -## Included scope - -1. daemon, `sync`, `verify`의 공통 broker provider를 통한 fetched credential source parity. -2. fetched credential read 실패 시 무음 env/config fallback 금지(명시 break-glass만 예외), env-only 및 Paper 경로 회귀 보존. -3. KIS balance 인증·권한·transport·malformed response·broker rejection을 `BrokerBalanceError`로 fail-loud 처리. 실패는 CLI exit `1`이며 정상 empty balance와 구별한다. -4. `unmanaged_position`과 instance-scoped `managed_non_holding_divergence` 분류. 이 분류는 자동 주문, 자동 청산, slot 생성, slot/warning write를 하지 않는다. -5. 안전한 source mode/label 관찰성 및 remediation은 별도 release gate라는 운영 문서 정합. - -## Explicitly excluded - -- `sync --live --yes` 실행, target portfolio 선택, 자동 remediation 및 실제 slot/ledger/broker write. -- `credential_fetch_enabled` 또는 break-glass 설정의 변경·활성화. 배포 전후 해당 인스턴스 설정은 동일해야 한다. -- KIS key/token, account identifier, DSN, portfolio-hub token의 출력·기록·수집. -- PostgreSQL schema migration, dependency upgrade, AMQP topology/wire 변경. -- TVR 및 전략 데몬 배포 범위. - -## Draft verification evidence - -| Check | Result | Evidence / boundary | -| --- | --- | --- | -| Full regression at target source | **PASS** — `807 passed, 47 skipped` | 2026-07-16, repository `.venv/bin/python -m pytest -q` | -| Diff hygiene | **PASS** | `git diff --check 7845fbe..origin/main` | -| CLI surface inspection | **PASS** | `trex-core sync --help`, `trex-core verify --help`; both support per-instance `--config` and `--dry-run` where applicable | -| Batch focused suite | **PASS** — `69 passed, 1 skipped, 1 deselected` | BAT-057 verification evidence; in-process `pika` stub | -| Full Ruff | **Known baseline debt** | target 21 findings; M4 baseline has 22. BAT-057 introduced no new full-suite Ruff failure. Track cleanup separately; do not claim lint-green. | -| Live broker / live PostgreSQL / deployment | **PASS (release scope)** | Controlled production daemon rollout, startup acceptance, and both approved read-only smoke commands are recorded in [REL-011 execution evidence](evidence/rel-011_20260717_execution-evidence.md). No DB migration, live sync, or broker mutation occurred. | - -BAT-057 retains one verification note: fake-driver parity covered the PostgreSQL adapter query/row mapping, but an actual PostgreSQL engine integration fixture was not executed. This is documented evidence debt, not a passed live-database claim. - -## Release approval and completion record - -All functional release gates are confirmed in the [REL-011 execution evidence](evidence/rel-011_20260717_execution-evidence.md): - -- [x] Lead reviewed BAT-057 status, target `6d90f895…`, source lineage, and no-migration/no-wire-delta boundary. -- [x] Operations provided the captured pre-deploy `ROLLBACK_REVISION` `7845fbe856e125e712ac481e36ac582c1a2fe54b`, source checkout, Supervisor restart, instance, and startup-health records. -- [x] Justin Ko authorized the release execution in Mattermost message `32675` at `2026-07-16 17:22:50 KST`; the read-only broker smoke did not authorize any `sync --live` action. -- [x] Operations recorded unchanged `credential_fetch_enabled` / break-glass semantics. -- [x] Target checkout, restart, runtime health, and both read-only smoke results are evidenced. -- [x] Release owner directed closure after the evidence intake; no `sync --live` authorization is implied. - -## Rollout summary - -1. Capture current revision and existing instance configuration fingerprint (without secrets). -2. Fetch and check out the pinned target source; confirm no migration or dependency action is required by source diff. -3. Stop the existing trex-core daemon using the host’s approved supervisor procedure, then start the same approved instance configuration with the target source. -4. Confirm startup completion, RabbitMQ topology readiness, and no `ERROR`, `CRITICAL`, or traceback in the deployment-window logs. -5. Run only the read/dry-run smoke steps in `docs/runbooks/rel-011-m5-operator-broker-truth-hotfix-deployment.md`. -6. If a failure occurs, stop immediately, restore the captured pre-deploy revision, restart with the unchanged configuration, and collect redacted evidence. - -## Rollback boundary - -Rollback target is the **actual pre-deploy revision captured in the operational preflight**, not an assumed SHA. Since this release adds no migration or dependency change, rollback is source restoration plus daemon restart using the unchanged configuration. Do not delete or alter database data as a rollback action. - -## `confirm-release` decision - -**Released.** The release-specific evidence records Justin Ko’s approval reference, immutable target revision, pre-deploy rollback SHA `7845fbe…`, controlled restart, startup health, both required read-only smoke checks, no-write boundary, and no-rollback disposition. This decision does not authorize live sync, configuration activation, remediation, DB changes, or deployment of other M5 services. - -## Release feedback - -- **Operational outcome:** the reconstructed record shows a clean controlled restart, no startup fatal log, and successful read-only smoke for both approved instances; no release-scope rollback or incident was recorded. -- **Evidence recovery outcome:** the final secret-free record now includes the approval reference and exact pre-deploy rollback revision, alongside the operational smoke evidence. -- **Next Discovery input:** standardize first-pass release evidence capture: a redacted approval reference, exact rollback revision, instance identity, per-command timestamp/exit/result/no-write classification, and rollback disposition must be recorded before a future release gate is closed. diff --git a/docs/runbooks/m5-coordinated-release-train-readiness.md b/docs/runbooks/m5-coordinated-release-train-readiness.md deleted file mode 100644 index d91d715..0000000 --- a/docs/runbooks/m5-coordinated-release-train-readiness.md +++ /dev/null @@ -1,79 +0,0 @@ -# M5 coordinated release train — deployment readiness and execution control - -- Status: **Released** -- Train confirmation: `2026-07-17 KST` — lead reconciliation of the M5 common release evidence and individual release evidence -- Date: 2026-07-16 -- Coordinator: TREX lead -- Scope: `trex-core` REL-011, `portfolio-hub` REL-004, `trex-web` REL-002 -- Authority: This document coordinates release order and shared stop criteria. It **does not replace** each service's release document, runbook, release approval, or operator-owned deployment procedure. - -## 1. Decision statement - -The M5 coordinated train is **Released**. The lead reconciled the M5 common evidence handoff with the individual, release-specific records: `trex-core REL-011` is released; `portfolio-hub REL-004` is released with a documented non-blocking test-fixture follow-up; and `trex-web REL-002` is released. The later `trex-core REL-012` is a separately released post-M5 hotfix and is not a prerequisite for this M5 decision. - -No release is inferred merely from source publication: this closure relies on the recorded service-specific operational evidence and decisions cited in the inventory below. Deferred follow-ups remain owned by their respective services and do not reopen the completed M5 train. - -## 2. Release inventory and evidence - -| Order | Service / release | Pinned application source | Evidence supporting an execution request | Non-negotiable outstanding gate | -| --- | --- | --- | --- | --- | -| 1 | `trex-core` REL-011 / BAT-057 | `6d90f8951dbf354eb41301492d0bcaf0af24d882` (runtime PR #42 merge `94e148940adb008db0bbd215052a1e984f7c566b`) | **Released** — Justin Ko approval `32675`, rollback SHA `7845fbe`, controlled daemon restart, startup health, and read-only smoke captured in [REL-011 execution evidence](../releases/evidence/rel-011_20260717_execution-evidence.md) | No remaining core release gate; this does not authorize live sync | -| 2 | `portfolio-hub` REL-004 / BAT-024, BAT-025 | `914b3dc6e6b9e2cbd7068fb30435c83f0581eab5` or reviewed descendant | **Released with deferred non-blocking test-fixture follow-up** — backup PASS; migration `20260715_0028`, schema invariants, `73 → 73` row-count preservation, Supervisor restart, `/health` 200, `db_enabled: true`, owner read PASS, non-owner HTTP 403 | No train-blocking gate; `owner_user_id` fixture follow-up remains service-owned | -| 3 | `trex-web` REL-002 / BAT-010 | `1ec2cf4345310cab0d3468e36b31e0613668b482` or approved descendant | **Released** — build/runtime install PASS; Supervisor RUNNING on `8004`; external `/trex/` and sign-in HTTP 200; authenticated portfolio UI/session checks PASS; browser-console JS errors 0 | No train-blocking gate; npm audit low/moderate notice remains service-owned | - -Supporting service documents: - -- core: `docs/releases/rel-011_20260716_m5_operator-broker-truth-hotfix.md`, `docs/runbooks/rel-011-m5-operator-broker-truth-hotfix-deployment.md` -- portfolio-hub: `docs/releases/rel-004_20260715_manual-ledger-api-and-metadata-migration.md`, `docs/runbooks/rel-004-m5-manual-ledger-deployment-runbook.md` -- trex-web: `docs/releases/rel-002_20260715_m5-portfolio-ui-theme.md`, `docs/runbooks/rel-002-m5-portfolio-ui-theme-deployment.md` - -## 3. Shared prerequisites before any execution - -1. Record an explicit release-window approval for the specific REL; an approved document push is not deployment approval. -2. Record operator, window, target host/environment, source/artifact pin, and the currently working rollback source/artifact **before** changing a process or database. -3. Confirm the release's source is exactly pinned or a separately reviewed descendant. Never deploy an unpinned branch head. -4. Keep credentials, `.env` values, tokens, backup contents, bearer headers, and customer/portfolio data out of chat, terminal capture, and release documents. -5. Run only the service-owned runbook commands or the real operator-owned platform procedure. Local test Compose files, guessed systemd commands, and ad-hoc production command substitutions are prohibited. -6. Capture secret-free command exit status, timestamps, PID/rollout state, health/status result, and rollback decision in the corresponding REL evidence table. - -## 4. Coordinated execution order - -### Step 1 — trex-core REL-011 (independent, urgent broker-truth hotfix) - -Execute only after REL-011 approval. Capture the current deployed SHA, update to the pin, use the existing supervisor to restart the core daemon, then perform only the runbook's read-only checks (`sync --all --dry-run` and `verify`) against the approved instance. `sync --live --yes`, slot/order mutation, automatic remediation, and changing `credential_fetch_enabled` are outside this release. - -**Proceed to Step 2 only when:** the daemon remains healthy, logs show no startup/fatal error, the read-only checks have their expected no-write classification, and no rollback trigger occurs. - -### Step 2 — portfolio-hub REL-004 (highest-risk: production DB migration) - -Execute only after independent REL-004 approval and the backup/DB gates pass. Perform the approved backup and capture Alembic `heads/current`, active workload/locks, schema state, and row counts. Upgrade exactly to `20260715_0028`, prove the four metadata columns/default/not-null/row-count invariants, then use the existing operator-owned rolling deployment procedure. Start with read-only health and existing-read checks; a manual-ledger mutation smoke needs separate explicit approval for a designated non-sensitive test portfolio. - -**Proceed to Step 3 only when:** migration postchecks pass, portfolio-hub health reports `db_enabled: true`, existing read paths work, authorization checks are correct, and the agreed observation period is clean. - -### Step 3 — trex-web REL-002 (presentation release after dependencies are healthy) - -Before stopping the process, reconcile the actual reverse-proxy upstream with the active deploy script and explicitly record the chosen app port. Stop if `/trex` is stripped or if the proxy target differs from the selected port. After portfolio-hub is healthy, build and start through `scripts/deploy-trex-web.sh`, then smoke the reverse-proxy route—not only localhost—for sign-in, portfolio list, safe existing portfolio detail, selected state/table readability, refresh/session, and logs. - -## 5. Stop and rollback boundaries - -| Release | Stop immediately when | Permitted recovery | -| --- | --- | --- | -| core REL-011 | missing approval/pin, daemon startup failure, error/no-write barrier failure, unexpected broker mutation risk | use captured pre-release source with the normal supervisor restart; retain all evidence; no live sync as a diagnostic shortcut | -| portfolio REL-004 | missing backup, unknown/branched Alembic state, lock/workload not accepted, migration/schema/null/row-count mismatch, health or authorization anomaly | before success: do not deploy new code; after migration success: roll back only to compatible code and retain additive schema `20260715_0028`; no downgrade, column drop, or restore without separate incident approval | -| web REL-002 | unresolved 3000/8004 proxy decision, `/trex` stripped, build/start failure, sign-in/portfolio regression, material readability regression | stop new process, restore captured known-good revision with the approved script, and recheck `/trex`, sign-in, portfolio list; no DB rollback applies | - -## 6. Release completion rule - -A service becomes `released` only after its own runbook evidence is complete and the lead records `confirm-release`. Do not mark the M5 train released because a source commit was pushed, a build passed, or another service succeeded. - -The coordinated train closes only when all intended service releases have individual evidence and decisions, or when deferred/excluded services are explicitly recorded with rationale and follow-up ownership. - -## 7. `confirm-release` — M5 train - -**Released.** The intended M5 scope is closed with recorded individual release decisions: - -1. `trex-core REL-011`: released; approval, pin, rollback revision, restart, and dry-run/verify evidence complete. -2. `portfolio-hub REL-004`: released; its `owner_user_id` test-fixture item is explicitly deferred and non-blocking. -3. `trex-web REL-002`: released; proxy port `8004`, external `/trex` route, sign-in, authenticated portfolio flow, and browser-console checks passed. - -`trex-core REL-012` is recorded separately as a post-M5 hotfix. It does not change M5 scope or the train’s completion decision. diff --git a/docs/runbooks/rel-011-m5-operator-broker-truth-hotfix-deployment.md b/docs/runbooks/rel-011-m5-operator-broker-truth-hotfix-deployment.md deleted file mode 100644 index 1adb46d..0000000 --- a/docs/runbooks/rel-011-m5-operator-broker-truth-hotfix-deployment.md +++ /dev/null @@ -1,144 +0,0 @@ -# REL-011 M5 trex-core operator broker truth hotfix — deployment runbook - -- Release: `REL-011` -- Target source: `6d90f8951dbf354eb41301492d0bcaf0af24d882` -- Runtime implementation: BAT-057 / PR #42 merge `94e148940adb008db0bbd215052a1e984f7c566b` -- Execution owner: 운영팀 -- Approval required: team lead review + user release approval -- Safety classification: production daemon restart; no DB migration; broker-read smoke only - -> Do not paste credentials, tokens, account numbers, DSNs, `.env` contents, raw KIS responses, or full command lines containing secrets into the evidence log. Use `[REDACTED]`. - -## 1. Scope and hard stops - -Allowed after release approval: - -- Pinned source checkout, existing trex-core daemon stop/start, startup log inspection. -- Existing approved instance configuration used without semantic changes. -- `sync --all --dry-run` and `verify` as broker-read/CLI smoke checks. - -Not allowed in this runbook: - -- `sync --live`, `--yes` with a live write, `enter`, `exit`, `cancel`, or any automated remediation. -- Changing `credential_fetch_enabled`, `TREX_KIS_BROKER_BREAK_GLASS`, broker mode, instance key, portfolio-hub endpoint, or secrets. -- Database schema migration, manual DB update/delete, AMQP topology change, or strategy daemon deployment. - -If any prohibited action appears necessary, stop and return to the release approver. - -## 2. Preflight — record before modifying the host - -1. Confirm the approved release, target SHA, and approved 장외 window. -2. From the operating checkout, capture only redacted metadata: - - ```bash - pwd - git status --short - git rev-parse HEAD - git rev-parse --abbrev-ref HEAD - ``` - - - Record the exact pre-deploy revision as `ROLLBACK_REVISION`. - - If the checkout is dirty or target source cannot be verified, **stop**. - -3. Identify the active instance configuration and process supervisor without printing secrets. Confirm the existing config’s `credential_fetch_enabled` and break-glass state will remain unchanged. -4. Confirm the expected process command remains the established runtime form: - - ```bash - .venv/bin/trex-core-daemon --config - ``` - -5. Confirm target/source characteristics from a clean checkout: - - ```bash - git fetch origin - git show -s --format='%H %s' 6d90f8951dbf354eb41301492d0bcaf0af24d882 - git diff --name-only "$ROLLBACK_REVISION" 6d90f8951dbf354eb41301492d0bcaf0af24d882 -- pyproject.toml trex_core/adapters/persistence/migrations - ``` - - Expected for the reviewed M4 baseline: no dependency or migration path. If actual output differs, **stop** and request lead review; do not infer safety from this draft. - -6. Ensure the deployment host’s virtual environment has the existing runtime dependencies. Do not run dependency upgrades as an implicit part of this release. - -## 3. Deploy source and restart daemon - -Use the host’s approved supervisor procedure. The following expresses the source boundary, not a replacement for host-specific service management: - -```bash -git fetch origin -git checkout --detach 6d90f8951dbf354eb41301492d0bcaf0af24d882 -# Stop the existing trex-core daemon through the approved host supervisor. -# Start exactly one daemon with the unchanged approved instance config. -.venv/bin/trex-core-daemon --config -``` - -Record, with sensitive values redacted: - -- checkout SHA; -- prior process stop confirmation; -- new process identifier or supervisor status; -- selected instance key (not account details); -- startup log interval and log location. - -## 4. Startup acceptance - -Before any CLI smoke, confirm: - -- the daemon completed startup and RabbitMQ topology declaration; -- worker threads are running according to the instance profile; -- no `ERROR`, `CRITICAL`, or traceback appears in the post-restart interval; -- the daemon is using the unchanged configuration mode. Source evidence may show only safe mode/label (`fetched`, `env`, or `break_glass`), never credential payload or account number. - -Any KIS credential-resolution or balance-read failure must be treated as a failure; BAT-057 intentionally prevents treating it as a normal empty broker balance. - -## 5. Read-only/dry-run smoke - -Run against the same explicit approved configuration. These calls may read the configured broker and database but must not write a slot, warning, broker order, or ledger record. - -```bash -.venv/bin/trex-core sync --all --dry-run --config --json -.venv/bin/trex-core verify --config --json -``` - -Acceptance: - -- Commands either succeed with sanitized source/balance evidence, or fail loudly with safe error code/message. -- A failure must not be reinterpreted as an empty balance or “zero quantity” condition. -- For `unmanaged_position` or `managed_non_holding_divergence`, record classification only and stop for triage; no remediation is performed. -- Never add `--live` or `--yes` in this runbook. - -## 6. Rollback - -Trigger rollback for any daemon startup failure, unexpected source-mode change, unexpected credential fetch/fallback behavior, broker-read failure not safely surfaced, AMQP connectivity regression, or smoke behavior inconsistent with BAT-057’s fail-loud/no-auto-remediation contract. - -```bash -# Stop the new daemon via the approved host supervisor. -git checkout --detach "$ROLLBACK_REVISION" -# Restart with the same unchanged approved configuration via the supervisor. -``` - -Then confirm the previous daemon revision is running and collect redacted logs. Do not run database rollback commands; REL-011 introduces no migration. - -## 7. Evidence record template - -```text -Release: REL-011 -Approved by / timestamp: [REDACTED AS NEEDED] -Window: [REDACTED] -Host identity: [REDACTED] -Instance key: [REDACTED] -Pre-deploy revision (ROLLBACK_REVISION): -Target revision: 6d90f8951dbf354eb41301492d0bcaf0af24d882 -Configuration mode unchanged: yes/no (credential_fetch_enabled / break-glass values not recorded) -Daemon stop/start evidence: -Startup log interval: -Startup acceptance: pass/fail -sync --dry-run result: pass/fail + sanitized exit code/output summary -verify result: pass/fail + sanitized exit code/output summary -Unexpected divergence classification: none / unmanaged_position / managed_non_holding_divergence -Rollback performed: no / yes (reason and restored SHA) -Operator: [REDACTED] -``` - -## 8. Completion criteria - -REL-011 may move from `Release-Pending` only after the exact target revision, restart evidence, startup acceptance, dry-run smoke result, verify result, and rollback disposition are reviewed by the team lead. Production live sync remains a separate approval gate even after REL-011 is released. diff --git a/docs/srs/Configuration/req-010_daemon-dotenv-loading.md b/docs/srs/Configuration/req-010_daemon-dotenv-loading.md deleted file mode 100644 index 2addaa4..0000000 --- a/docs/srs/Configuration/req-010_daemon-dotenv-loading.md +++ /dev/null @@ -1,318 +0,0 @@ -# req-010: 데몬 설정 .env 파일 로딩 - -- Status: Implemented -- Type: Configuration -- Priority: Medium -- Owner: project maintainers - -## Intent -- 운영자가 데몬 환경변수(`TREX_POSTGRESQL_DSN` 등)를 `.env` 파일로 관리할 수 있도록, 데몬 기동 시 `.env` 를 로드한다. 실제 OS 환경변수는 `.env` 보다 우선한다(12-factor 호환). - -## Requirement -- `DaemonConfig.from_env()` 가 `os.environ` 을 읽기 전에 `.env`(있으면)를 로드한다. python-dotenv 를 의존성에 추가하고, `.env` 는 `.gitignore` 에 등록해 커밋되지 않게 하며, `.env.example` 를 참고용으로 제공한다. `.env` 가 없거나 dotenv 미설치여도 기존 동작(os.environ 단독)을 유지한다(회귀 없음). -- (CHG-20260609-01, trex-orb-v1 정합) `.env` 해석은 **config 디렉터리 기준 결정적**이다 — `--env-file ` 우선, 없으면 `/.env`(TOML 옆). CWD 의존 상향 탐색(`find_dotenv(usecwd=True)`)을 사용하지 않는다. `.env` 는 `config/.env` 로 공용화하고 `config/.env.example` 를 제공하며, `.gitignore` 는 `config/*` 를 무시하되 `*.example.toml`/`.env.example` 만 추적한다. `override=False`(실제 OS env 우선)는 유지한다. - -## Acceptance Criteria -1. (CHG-20260609-01 갱신) Given `/.env`(또는 `--env-file`)에 `TREX_POSTGRESQL_DSN=...` 가 있고 해당 OS env 가 미설정, When 설정 로드, Then `.env` 값이 로드되어 `dsn` 에 반영된다. -2. Given OS env 와 `.env` 에 같은 키가 다른 값으로 존재, When 로드, Then OS env 값이 우선한다(.env 가 덮어쓰지 않음, `override=False`). -3. Given `.env` 가 없음, When 로드, Then 기존처럼 `os.environ` 만으로 동작한다(회귀 없음). -4. Given 저장소, When `.gitignore` 확인, Then `config/*` 가 무시 대상이며 `config/*.example.toml`·`config/.env.example` 은 추적된다. -5. Given `broker=paper` 이고 `.env`/OS env 에 `TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS` 가 설정, When `DaemonConfig.from_env()` 후 `_build_broker`, Then paper 더블이 해당 시나리오(지연/부분/거부, REQ-011 AC-2)로 구성된다. 미설정이면 `PaperBroker()` 기본과 동일(회귀 없음). -6. (CHG-20260609-01) Given CWD 를 바꿔 실행, When 설정 로드, Then `/.env` 가 CWD 와 무관하게 결정적으로 로드된다(상향 탐색 없음). -7. (CHG-20260609-01) Given `--env-file `, When 로드, Then `/.env` 대신 그 파일이 로드된다(우선). - -## Impacted Area -- Docs: (CHG-20260609-01) `config/.env.example`(신규, 공유 인프라 키), `.env`→`config/.env` 이동 -- Modules: pyproject.toml(python-dotenv), trex_core/runtime/config.py(load_dotenv→`_load_dotenv(config_path, env_file)` config-dir 해석, PaperBrokerConfig), trex_core/runtime/daemon.py(_build_broker paper 주입, `--env-file`), cli.py(`--env-file`), .gitignore(config/* 허용목록), .env.example(paper 튜닝 키) -- Tests: tests/runtime/test_config_dotenv.py (로드/우선순위/부재 회귀, paper 튜닝 기본/파싱, config-dir·`--env-file`·CWD 무관) - -## Notes -- minor-change fast path: 단일 저위험 Configuration REQ, 구조/인터페이스/런타임 흐름 영향 없음(기동 시 env 소스 추가뿐). batch-lite 로 처리, design 불요. -- dotenv 로드는 best-effort(미설치 시 graceful no-op) — 도메인/테스트 경로에 영향 없음. -- 맥락: dcy-002 데몬(REQ-005)의 설정 편의 개선. trex-orbit 도 `.env` 사용. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- 최초 생성(Proposed) — 데몬 `.env` 로딩 Configuration 요구사항. - -#### Intent -- 환경변수의 파일 기반 관리(.env) 지원을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - .env 로드, OS env 우선, 부재 회귀, gitignore 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - 없음 -- Modules: - - pyproject.toml, runtime/config.py, .gitignore, .env.example -- Tests: - - tests/runtime/test_config_dotenv.py - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: minor-change batch-lite 편성 - -#### Validation Plan -- 설정 로드/우선순위/부재 회귀 단위테스트로 검증. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (minor-change 저위험 단일 REQ, 구조/인터페이스/런타임 흐름 영향 없음). - -#### Intent -- 승인 게이트 충족(필수 필드·구체 AC·blocker 없음). batch-lite 로 즉시 delivery 가능. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch-lite 편성 예정) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-batch 로 batch-lite 생성 - -#### Validation Plan -- batch-lite verification 에서 AC 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-003 release-candidate). - -#### Intent -- BAT-003 verification.md 의 AC↔evidence(로드/우선순위/부재 회귀/gitignore, 78 pass)로 모든 AC 충족 → 구현 완료. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-003 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~003) - -#### Validation Plan -- BAT-003 verification evidence 재참조로 충분. - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- paper broker 튜닝 knob(REQ-011 AC-2 의 지연/부분/거부 시나리오)을 데몬 env 로 노출하는 신규 scope 추가. `DaemonConfig.from_env()` 가 `TREX_PAPER_FILL_AFTER_POLLS`/`TREX_PAPER_QTY_PER_POLL`/`TREX_PAPER_REJECT_SYMBOLS` 를 `PaperBrokerConfig` 로 파싱하고 `_build_broker` 가 paper 더블에 주입한다. 미설정이면 기존 `PaperBroker()`(즉시 전량 체결·거부 없음)와 byte-for-byte 동일(회귀 없음). - -#### Intent -- e2e 시나리오(주문 거부 롤백/지연 체결/부분 체결)를 데몬 재기동만으로 재현할 수 있도록 REQ-011 의 결정적 더블 knob 을 운영 설정 표면(REQ-010 .env 로딩)에 연결. REQ-011 본문은 이미 시나리오를 요구(AC-2)하므로 broker 요구사항 변경이 아니라 Configuration 노출. - -#### Acceptance Criteria Delta -- Added: - - AC-5: paper 튜닝 env 파싱 + `_build_broker` 주입, 미설정 시 기본 동작 byte-for-byte 동일(회귀 없음) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태·Recent Change Log Summary) -- Modules: - - trex_core/runtime/config.py (PaperBrokerConfig dataclass + from_env 파싱), trex_core/runtime/daemon.py (_build_broker 주입), .env.example (paper 튜닝 키 3개 문서화) -- Tests: - - tests/runtime/test_config_dotenv.py (test_paper_broker_defaults, test_paper_broker_env_tuning) - -#### Delivery Trace Delta -- Batches: - - BAT-009 (batch-lite, tooling — 신설 예정; AC-5 delivery/verification 담당) -- Releases: - - REL-001 영향 없음: REL-001 이 포함한 REQ-010 coverage 는 AC-1~4(.env 로딩 메커니즘, BAT-003)로 그대로 유효. AC-5 는 REL-001 이후 신규 scope 이며 별도 release 로 배포. - -#### Revalidation Impact -- Reverification Needed: yes (신규 AC-5 한정; 기존 AC-1~4 evidence(BAT-003)는 무효화되지 않음) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented → Approved (신규 AC-5 는 BAT-009 verification 전까지 미확정) -- Follow-up Action: draft-batch 로 BAT-009(batch-lite, tooling) 생성 → verification → confirm-req-implemented 로 Approved → Implemented 재전환 - -#### Validation Plan -- 단위: test_paper_broker_defaults(미설정=기본), test_paper_broker_env_tuning(3 env 파싱) — 이미 작성·통과(166 pass). -- e2e: 데몬 env 재기동으로 거부 롤백(`idle→pending_buy→idle`), 지연 체결(`+3.7s`), 부분 체결(qty 누적 `+2.5s`) 재현 — BAT-009 verification 에 evidence 기록. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-009 release-candidate). - -#### Intent -- BAT-009 verification.md 의 AC↔evidence(AC-1~4 BAT-003 회귀 재확인 + AC-5 단위 2개·e2e 거부/지연/부분)로 전체 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-009 (release-candidate) -- Releases: - - none (REL-001 과 분리된 후속 release 로 배포 예정) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-009 배포 편성(원하는 시점에). - -#### Validation Plan -- BAT-009 verification evidence 재참조로 충분. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, trex-orb-v1 설정 관리 정합) `.env` 해석을 **config 디렉터리 기준 결정적**으로 변경한다 — `--env-file ` 가 우선, 없으면 **`/.env`**(TOML 옆). 기존 `find_dotenv(usecwd=True)`(CWD 의존 상향 탐색)를 제거해 실행 위치와 무관하게 동일 `.env` 를 로드한다. `.env` 를 repo 루트 → **`config/.env`** 로 이동(공용화), `config/.env.example` 신설, `.gitignore` 를 `config/*` 무시 + `*.example.toml`/`.env.example` 만 추적하는 형태로 전환. `override=False`(실제 OS env 우선)는 유지. `config_dir` 는 REQ-048 CHG-20260609-01 의 `--config-path` 로 제공된다. - -#### Intent - -- 어디서 실행하든 동일한 **공유 인프라 `.env`**(DSN/RabbitMQ/KIS/portfolio-hub 등)를 결정적으로 로드한다. 다중 인스턴스가 같은 broker/DSN/hub 를 공유하도록 `.env` 를 인스턴스 TOML 과 분리해 config 디렉터리에 공용화(12-factor). - -#### Acceptance Criteria Delta - -- Added: - - AC-6: `.env` 는 `/.env`(TOML 옆)에서 **CWD 와 무관하게** 결정적으로 로드된다. dotenv 미설치/`.env` 부재 시 no-op(회귀 없음). - - AC-7: `--env-file ` 가 주어지면 `/.env` 대신 그 파일을 로드한다(우선). - - AC-8: `.gitignore` 는 `config/*` 를 무시하되 `config/*.example.toml` 과 `config/.env.example` 은 추적한다(실제 `.env`/인스턴스 TOML 비커밋). -- Updated: - - AC-1: "CWD 의 `.env`" → "`/.env`"(또는 `--env-file`) 로 로드 소스 변경. -- Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/config.py`(`_load_dotenv(config_path, env_file)` — config-dir 결정적 해석, `find_dotenv(usecwd=True)` 제거), `trex_core/runtime/daemon.py`·`cli.py`(`--env-file` 인자 전달) -- Docs: `config/.env.example`(신규, 공유 인프라 키), `.gitignore`(config/* 허용목록), `.env`(루트→`config/.env` 이동) -- Tests: `tests/runtime/test_config_dotenv.py`(config-dir 로드/`--env-file` 우선/CWD 무관/`override=False` 회귀/부재 no-op) - -#### Delivery Trace Delta - -- Batches: 신규 batch-lite(REQ-048 CHG-20260609-01 과 동일 배치) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 AC-6/7/8 + AC-1 변경 검증) -- Existing Implementation Invalidated: yes (`.env` 해석 위치가 CWD → config-dir 로 변경되어 기존 루트 `.env` 의존 동작이 바뀜 — 운영 배포 시 `.env` 이동 필요) -- Status Recommendation: Implemented → Approved (신규/변경 AC 미구현 — batch 재검증 후 Implemented 복귀) -- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verification → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: `/.env` 결정적 로드(CWD 변경해도 동일), `--env-file` 우선, OS env 우선(`override=False`) 회귀, `.env` 부재 no-op 단위. `.gitignore` config/* 허용목록 가드. - -### CHG-20260609-02 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- Status 변경: Approved → Implemented. BAT-026 release-candidate verification evidence 기반(CHG-20260609-01 AC-1/2/3/4/6/7). - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta - -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta - -- Batches: BAT-026 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (BAT-026 에서 완료) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 운영 배포 시 `.env`→`config/.env` 이동(루트 `.env` 폴백으로 무중단). release 단계 별도 진행. - -#### Validation Plan - -- tests/runtime/test_config_resolution.py(config-dir `.env`/`--env-file`/CWD 무관/override) + test_config_dotenv.py 6건 무회귀 + `.gitignore` 가드. 전체 375 passed, ruff clean. diff --git a/docs/srs/Configuration/req-041_safety-sell-env-config.md b/docs/srs/Configuration/req-041_safety-sell-env-config.md deleted file mode 100644 index ff04240..0000000 --- a/docs/srs/Configuration/req-041_safety-sell-env-config.md +++ /dev/null @@ -1,115 +0,0 @@ -# req-041: safety-sell 환경 설정 — 5종 env var - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: project maintainers - -## Intent - -자율 안전 매도 기능(REQ-034~039)에서 사용하는 5종의 환경 변수를 `trex_core/runtime/config.py`의 `DaemonConfig.from_env()`에 추가한다. 모든 값은 env에서 파싱되며 기본값을 제공해야 한다(dcy-007 NFR-1·3, A-4). - -## Requirement - -- 시스템은 아래 5종 env var를 `DaemonConfig.from_env()`에서 파싱해야 한다. - -| 환경 변수 | 타입 | 기본값 | 설명 | -|---|---|---|---| -| `TREX_WATCHDOG_TIMEOUT` | int (초) | 120 | 워치독 만료 임계값 (REQ-034) | -| `TREX_WATCHDOG_GRACE_PERIOD` | int (초) | 60 | 데몬 재시작 후 만료 판정 유예 시간 (REQ-034) | -| `TREX_FORCED_CLOSE_CHECK_INTERVAL` | int (초) | 60 | forced_close_at 만기 체크 주기 (REQ-037) | -| `TREX_STOP_LOSS_POLL_INTERVAL` | int (초) | 30 | stop-loss·take-profit 가격 polling 주기 (REQ-038) | -| `TREX_MARKET_CLOSE_LIQUIDATION_TIME` | str (HH:MM KST) | None (미설정) | 장 마감 강제 청산 시각 (REQ-039). 미설정 시 동작 없음. | - -## Acceptance Criteria - -1. Given 환경 변수가 설정되지 않았을 때, When `DaemonConfig.from_env()`를 호출하면, Then `watchdog_timeout=120`, `watchdog_grace_period=60`, `forced_close_check_interval=60`, `stop_loss_poll_interval=30`, `market_close_liquidation_time=None`이 반환된다. -2. Given `TREX_WATCHDOG_TIMEOUT=180`이 설정됐을 때, When `from_env()`를 호출하면, Then `watchdog_timeout=180`이 반환된다. -3. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이 설정됐을 때, When `from_env()`를 호출하면, Then `market_close_liquidation_time`이 파싱되어 `datetime.time(15, 10)`(또는 동등한 형식)으로 반환된다. -4. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않았을 때, When `from_env()`를 호출하면, Then `market_close_liquidation_time=None`이 반환된다. -5. Given 잘못된 형식의 `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(예: `"25:99"`)이 설정됐을 때, When `from_env()`를 호출하면, Then 명확한 오류(ValueError 또는 ConfigError)가 발생한다. - -## Impacted Area - -- Docs: - - `docs/project-structure.md` — Primary Runtime env var 목록에 5종 추가 (REQ-042 참조) -- Modules: - - `trex_core/runtime/config.py` — `DaemonConfig`에 5종 env var 파싱 추가 -- Tests: - - `DaemonConfig.from_env()` 단위 테스트 — 기본값 확인, 개별 설정 확인, 잘못된 형식 오류 확인 - -## Notes - -- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`은 HH:MM KST 형식으로 파싱한다. `datetime.time` 또는 `(hour, minute)` tuple로 저장한다. 배치에서 결정. -- `TREX_WATCHDOG_TIMEOUT`과 `TREX_WATCHDOG_GRACE_PERIOD`의 단위는 초(int)다. -- 기존 `DaemonConfig`에는 `TREX_SNAPSHOT_TICK_SEC`, `TREX_SNAPSHOT_SCHEDULE_KST` 등 유사 env var가 이미 있으므로 동일 파싱 패턴을 따른다. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-041 초안 생성 - -#### Intent - -- safety-sell 기능에 필요한 5종 env var를 Configuration REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~5 전체 - -#### Impacted Area Delta - -- Docs: project-structure.md (REQ-042에서 처리) -- Modules: config.py -- Tests: DaemonConfig 단위 테스트 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 diff --git a/docs/srs/Configuration/req-048_per-instance-toml-config.md b/docs/srs/Configuration/req-048_per-instance-toml-config.md deleted file mode 100644 index 4e0d447..0000000 --- a/docs/srs/Configuration/req-048_per-instance-toml-config.md +++ /dev/null @@ -1,313 +0,0 @@ -# req-048: per-instance TOML 설정 로더 + `--config` 필수 - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: project maintainers - -## Intent - -다중 인스턴스 운영을 위해 인스턴스별 TOML 설정 파일을 도입한다. 데몬 기동 시 `--config .toml` 인자를 **필수**로 받고, TOML 에서 `instance_key` 와 `portfolio_hub_token` 을 읽는다. 이 두 값은 `.env` 에 넣지 않는 것을 원칙으로 하며(민감값 분리), 기존 `DaemonConfig.from_env()` 와 병합해 최종 설정을 구성한다(dcy-009 FR-1, NFR-4, OQ-2/A-5). - -## Requirement - -- `config.py` 에 `DaemonInstanceConfig` dataclass(`instance_key: str`, `portfolio_hub_token: str`)와 `from_toml(path)` 로더를 추가한다(stdlib `tomllib` 사용). -- `DaemonConfig` 구성 시 `--config .toml` 의 TOML 값을 `from_env()` 결과와 병합하는 팩토리(예: `from_config(toml_path)`)를 추가한다. `portfolio_hub_token` 은 TOML 값을 사용해 `PortfolioHubConfig.token` 에 주입한다. -- 데몬 기동 진입점은 `--config` 를 **필수 인자**로 받는다. 인자 없이 기동하면 오류 메시지를 출력하고 비정상 종료(exit code ≠ 0)한다. 기본값 fallback 없음(OQ-2 Answered). -- (CHG-20260609-01, trex-orb-v1 정합) `--config` 는 **이름**으로 받고 `--config-path `(기본 `./config`)로 config 디렉터리를 분리한다. `resolve_config_toml(name, config_path)` 가 `/.toml` 로 해석하며 `.toml` 을 자동 부착한다. **하위호환**: 슬래시 포함(경로형) `--config` 값은 명시 경로로 그대로 사용한다. 운영자 CLI(`trex-core`)도 `--config-path` 를 동일 규칙으로 수용한다. -- `instance_key` 와 `portfolio_hub_token` 은 `.env`/OS env 에서 읽지 않는다. TOML 미존재/필수 필드 누락 시 명확한 오류로 종료한다. -- `.gitignore` 에 인스턴스 TOML 경로 패턴(예: `*.toml` 또는 `config/*.toml`)을 추가해 설정 파일이 커밋되지 않게 한다. 토큰/instance_key 는 로그에 평문 출력하지 않는다(NFR-4). - -## Acceptance Criteria - -1. Given `inst-1.toml`(`instance_key="inst-1"`, `portfolio_hub_token="tok"`), When `DaemonInstanceConfig.from_toml("inst-1.toml")`, Then `instance_key="inst-1"`, `portfolio_hub_token="tok"` 로 파싱된다. -2. Given `--config` 인자 없이 데몬 기동, When 진입점 실행, Then 오류 메시지 출력 + exit code ≠ 0 으로 종료한다(기본값 fallback 없음). -3. Given TOML 에 `portfolio_hub_token` 이 있고 `.env`/OS env 에 `TREX_PORTFOLIO_HUB_TOKEN` 미설정, When 최종 설정 구성, Then `PortfolioHubConfig.token` 에 TOML 값이 주입된다(S-5). -4. Given TOML 에 필수 필드(`instance_key`) 누락, When `from_toml`/`from_config`, Then 명확한 오류로 실패한다(조용한 기본값 금지). -5. Given 저장소, When `.gitignore` 확인, Then 인스턴스 TOML 경로 패턴이 무시 대상이다. -6. Given 데몬 기동 로그, When 출력 확인, Then `portfolio_hub_token` 평문이 로그에 노출되지 않는다. -7. (CHG-20260609-01) Given `--config [--config-path ]`, When `resolve_config_toml`, Then `/.toml` 로 해석되고 `.toml` 이 없으면 자동 부착된다. -8. (CHG-20260609-01) Given 슬래시 포함(경로형) `--config` 값, When 해석, Then `--config-path` 와 무관하게 명시 경로로 그대로 사용된다(하위호환). -9. (CHG-20260609-01) Given 운영자 CLI 가 `--config-path` 와 함께 호출, When `_resolve_instance`, Then 동일 `resolve_config_toml` 규칙으로 TOML 을 찾아 instance_key 를 해석한다. -10. (CHG-20260611-01) Given per-instance TOML `safety_sell_enabled`(부재 시 `true`), When `from_toml`/`from_config`, Then 값이 파싱되어 `DaemonConfig` 로 전파된다. - -## Impacted Area - -- Docs: - - `docs/project-structure.md` — 설정 파일 계층(TOML + env) (REQ-051 처리) -- Modules: - - `trex_core/runtime/config.py` — `DaemonInstanceConfig` + `from_toml` + `from_config` 병합, (CHG-20260609-01) `resolve_config_toml`/`DEFAULT_CONFIG_DIR`, (CHG-20260611-01) `safety_sell_enabled` 필드 - - 데몬 진입점(`trex_core/runtime/daemon.py:main` 또는 `__main__`) — `--config` 필수 파싱(argparse), (CHG-20260609-01) `--config-path` - - 운영자 CLI `trex_core/runtime/cli.py` — (CHG-20260609-01) `--config-path` + `_resolve_instance` 통일 - - `.gitignore` — 인스턴스 TOML 패턴 -- Tests: - - `tests/runtime/` — TOML 로더 파싱/필수 인자/토큰 주입/누락 오류 - -## Notes - -- dcy-009 OQ-2 Answered: `--config` 필수, fallback 없음(split-brain 위험 회피). A-5. -- NFR-4(민감값 비노출)는 본 REQ 의 gitignore + 로그 미노출 AC 로 흡수. -- DATA_TOML_SCHEMA_FULL(dcy-009 §10): 최소 필드 `instance_key`/`portfolio_hub_token`. 추가 필드는 본 REQ 구현 스키마에서 확장 가능. -- KIS 자격증명/DSN/RABBITMQ_URL 등 나머지 env 는 이번 범위에서 `.env` 유지(CONFIRM_ENV_RESIDUAL). - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-009(confirmed) → REQ-048 초안 생성. - -#### Intent - -- per-instance TOML 로더 + `--config` 필수(FR-1, NFR-4)를 Configuration REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: project-structure.md (REQ-051) -- Modules: config.py, daemon 진입점, .gitignore -- Tests: tests/runtime TOML 로더 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-009(confirmed) 범위·결정(OQ-2 TOML 필수)과 모순 없음. TOML 추가 필드 스키마는 구현 단계 확장 가능 비핵심. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-013 release-candidate). - -#### Intent - -- BAT-013 verification 의 AC↔evidence(로더 6 단위: from_toml/필수 누락/token 주입 S-5/default ik/`--config` 누락 subprocess 종료 AC-2/gitignore)로 AC-1~6 충족. NFR-4(민감값) 흡수. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-013 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-013 배포 편성. token .env 제거는 배포 rollout. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-014) per-instance TOML 설정에 `trading_timezone`(기본 `Asia/Seoul`)·`daily_preallocation_time`(기본 `00:00`) 키 추가. `DaemonInstanceConfig.from_toml` 파싱 + `from_config` → `DaemonConfig` 전파. 본 기능 운영 설정을 TOML 로 일원화(dcy-014 FR-4). BAT-018 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 거래일 타임존·일일 트리거 시각을 신규 설정 표면 없이 기존 `--config` TOML 에 편입(운영자 명시 지정 가능). - -#### Acceptance Criteria Delta - -- Added: - - TOML optional 키 `trading_timezone`/`daily_preallocation_time`(부재 시 KST/00:00 기본). from_config 가 DaemonConfig 로 전파. - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/config.py`(DaemonInstanceConfig/DaemonConfig/from_config) -- Docs: `config/instance.example.toml` -- Tests: tests/runtime/test_instance_config.py(기본값/전파) - -#### Delivery Trace Delta - -- Batches: BAT-018 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-018 에서 완료) -- Existing Implementation Invalidated: no (optional 키 추가, 기존 TOML 호환) -- Status Recommendation: Implemented (change-req → BAT-018 재검증) -- Follow-up Action: confirm-req-implemented(BAT-018). - -#### Validation Plan - -- BAT-018: from_toml 기본값(KST/00:00) + TOML 값 from_config 전파 단위. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, trex-orb-v1 설정 관리 정합) `--config` 를 **경로 대신 이름**으로 받고 `--config-path `(기본 `./config`)로 config 디렉터리를 분리한다. `resolve_config_toml(name, config_path)` 추가 — `/.toml`(`.toml` 자동 부착). **하위호환**: 슬래시 포함(경로형) `--config` 값은 명시 경로로 그대로 사용(기존 운영 기동 스크립트 무중단). 운영자 CLI(`trex-core`)에도 `--config-path` 를 적용해 인스턴스 라우팅을 동일 규칙으로 통일. 데몬·CLI 진입점 argparse 갱신. - -#### Intent - -- trex-orb-v1 과 동일한 config 디렉터리 기반 설정 관리로 통일 — CWD 독립·이름 기반 인스턴스 선택. 공유 `.env`(REQ-010 CHG-20260609-01)의 해석 기준 디렉터리(`config_dir`)를 `--config-path` 로 제공한다. - -#### Acceptance Criteria Delta - -- Added: - - AC-7: `--config [--config-path ]` → `/.toml` 로 해석되고 `.toml` 확장자가 없으면 자동 부착된다(`resolve_config_toml`). - - AC-8: 슬래시를 포함한(경로형) `--config` 값은 `--config-path` 와 무관하게 **명시 경로**로 그대로 사용된다(하위호환 — 기존 `--config config/test.toml` 무중단). - - AC-9: 운영자 CLI(`trex-core`)도 `--config-path` 를 수용하며, `_resolve_instance` 가 동일 `resolve_config_toml` 규칙으로 TOML 을 찾는다. -- Updated: - - AC-2(필수 인자)는 유지 — `--config` 는 여전히 필수다(이름 또는 경로). -- Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/config.py`(`resolve_config_toml`, `DEFAULT_CONFIG_DIR`), `trex_core/runtime/daemon.py`(`main` argparse `--config-path`), `trex_core/runtime/cli.py`(`--config-path` + `_resolve_instance` 통일) -- Docs: `docs/project-structure.md`(설정 파일 계층 — config 디렉터리/이름 해석), `config/instance.example.toml` -- Tests: `tests/runtime/`(resolve_config_toml: 이름/.toml 부착/config-path/경로형 폴백, 데몬·CLI `--config-path` 라우팅) - -#### Delivery Trace Delta - -- Batches: 신규 batch-lite(REQ-010 CHG-20260609-01 과 동일 배치) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 AC-7/8/9 검증) -- Existing Implementation Invalidated: no (경로형 `--config` 는 하위호환 유지, 이름 해석은 additive) — 단 신규 AC 미구현 -- Status Recommendation: Implemented → Approved (신규 AC-7/8/9 미구현 — batch 재검증 후 Implemented 복귀) -- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verification → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: `resolve_config_toml("test")`→`config/test.toml`, `("test.toml")`→`config/test.toml`, `("prod","/etc/trex")`→`/etc/trex/prod.toml`, 경로형(`config/test.toml`)→그대로 단위. 데몬·CLI `--config-path` 기동/라우팅 단위. 기존 경로형 `--config` 회귀. - -### CHG-20260609-02 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- Status 변경: Approved → Implemented. BAT-026 release-candidate verification evidence 기반(CHG-20260609-01 AC-7/8/9). - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta - -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta - -- Batches: BAT-026 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (BAT-026 에서 완료) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계 별도 진행 - -#### Validation Plan - -- tests/runtime/test_config_resolution.py(resolve_config_toml/경로형 폴백/CLI `--config-path` 라우팅) + 라이브 스모크(name·path 형태 동일 결과). 전체 375 passed, ruff clean. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-024) per-instance TOML 에 **`safety_sell_enabled: bool = True`** 키 추가 — 자율 청산(safety-sell 워커) 인스턴스별 ON/OFF. `DaemonInstanceConfig.from_toml` 파싱 + `from_config` → `DaemonConfig` 전파. 기본값 `true`(미지정 시 기존 동작). env 미노출(`max_active_slots`/`default_order_timeout_sec` 선례, dcy-024 OQ-2). 실제 워커 게이팅은 REQ-005 CHG-20260611-01. SafetySellConfig(env) 변경 없음. - -#### Intent - -- 당일 매매 인스턴스(enabled=true)와 중장기 보유 인스턴스(enabled=false)가 인스턴스마다 다른 자율 청산 모드를 갖도록 운영 모드를 TOML 로 일원화(dcy-024 FR-1, issue #15). - -#### Acceptance Criteria Delta - -- Added: - - AC-10: per-instance TOML `safety_sell_enabled` optional 키(부재 시 `true`)를 `DaemonInstanceConfig.from_toml` 가 파싱하고 `from_config` 가 `DaemonConfig` 로 전파한다. -- Updated/Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/config.py`(`DaemonInstanceConfig.safety_sell_enabled` + from_toml/from_config 전파) -- Docs: `config/instance.example.toml` -- Tests: tests/runtime/test_instance_config.py(기본값 true / TOML false 전파) - -#### Delivery Trace Delta - -- Batches: 신규 batch(REQ-005 CHG-20260611-01 과 동일 배치, dcy-024) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 AC-10 검증) -- Existing Implementation Invalidated: no (optional 키 additive, 기존 TOML 호환) — 단 신규 AC 미구현 -- Status Recommendation: Implemented → Approved (신규 AC-10 미구현, batch 재검증 후 Implemented 복귀) -- Follow-up Action: draft-batch → run-batch-implementation → verification → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: `from_toml` 기본값(true) + TOML `safety_sell_enabled = false` from_config 전파 단위. diff --git a/docs/srs/Configuration/req-053_safety-sell-retry-cooldown-env.md b/docs/srs/Configuration/req-053_safety-sell-retry-cooldown-env.md deleted file mode 100644 index 02248a9..0000000 --- a/docs/srs/Configuration/req-053_safety-sell-retry-cooldown-env.md +++ /dev/null @@ -1,141 +0,0 @@ -# req-053: safety-sell 재시도 쿨다운 env 설정 2종 - -- Status: Implemented -- Type: Configuration -- Priority: Medium -- Owner: project maintainers - -## Intent - -safety-sell per-slot 쿨다운(REQ-052)의 튜닝 값을 운영 설정으로 노출한다. `SafetySellConfig`에 `min_retry_interval_sec`와 `max_retry_warn_count` 두 필드를 추가하고, 기존 safety-sell env 5종과 동일 방식으로 optional 파싱한다(dcy-010 FR-5). - -## Requirement - -- `SafetySellConfig`(`trex_core/runtime/config.py`)에 두 필드를 추가한다: - - `min_retry_interval_sec: int` — env `TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`, 기본값 `600`(OQ-1 Answered). - - `max_retry_warn_count: int` — env `TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT`, 기본값 `3`(OQ-2 Answered). -- `DaemonConfig.from_env()`에서 기존 safety-sell env(`TREX_WATCHDOG_TIMEOUT` 등)와 동일 패턴으로 파싱한다. 미설정 시 기본값으로 동작(회귀 없음). -- 이 값들은 `SafetySellService`(REQ-052)에 주입되어 쿨다운/경고 임계로 사용된다. - -## Acceptance Criteria - -1. Given `TREX_SAFETY_SELL_MIN_RETRY_INTERVAL`/`TREX_SAFETY_SELL_MAX_RETRY_WARN_COUNT` 미설정, When `DaemonConfig.from_env()`, Then `min_retry_interval_sec=600`, `max_retry_warn_count=3` 기본값으로 구성된다. -2. Given 두 env 가 각각 `300`/`5` 로 설정, When `from_env()`, Then `SafetySellConfig`에 `300`/`5`로 파싱된다. -3. Given 두 env 미설정 + 기존 safety-sell 동작, When 전체 회귀, Then 회귀 없음(기존 5종 env 동작 불변). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md`/`docs/project-structure.md` — env 목록(REQ-054 / 또는 본 REQ Notes) -- Modules: - - `trex_core/runtime/config.py` — `SafetySellConfig` 필드 2종 + `from_env` 파싱 -- Tests: - - `tests/runtime/test_safety_sell_config.py` — 기본값 + 파싱 - -## Notes - -- 기존 SafetySellConfig env 5종(REQ-041)과 동일 패턴 확장. additive·optional. -- 값의 의미·동작은 REQ-052(쿨다운 로직)에서 정의. 본 REQ 는 설정 표면만. - -## Change Log - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- dcy-010(confirmed) → REQ-053 초안 생성. - -#### Intent - -- safety-sell 쿨다운 env 2종(FR-5)을 Configuration REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~3 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md/project-structure.md (REQ-054) -- Modules: config.py -- Tests: test_safety_sell_config.py - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Change Log 완비. OQ-1(600s)/OQ-2(3) 기본값 확정. 기존 SafetySellConfig env 패턴 확장(additive, 회귀 없음). - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-batch 로 delivery 편성(batch-lite). - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-014 release-candidate). - -#### Intent - -- BAT-014 verification: `SafetySellConfig` min_retry_interval_sec/max_retry_warn_count 기본값(600/3) + env 파싱(300/5) 단위 green, 미설정 회귀 없음(278 passed). AC-1~3 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-014 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-014 배포 편성. diff --git a/docs/srs/Configuration/req-060_max-active-slots-config.md b/docs/srs/Configuration/req-060_max-active-slots-config.md deleted file mode 100644 index ea4f21d..0000000 --- a/docs/srs/Configuration/req-060_max-active-slots-config.md +++ /dev/null @@ -1,92 +0,0 @@ -# req-060: 동시 활성 최대치(max_active_slots) per-instance TOML 설정 - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: project maintainers - -## Intent -- 동시 활성(holding) 슬롯 최대치 M 을 인스턴스별로 독립 제어하기 위해 per-instance TOML 설정 키로 노출한다. 영구 슬롯 전환으로 폐기되는 `TREX_TOTAL_SLOTS`(풀 크기)를 대체한다. (dcy-020 FR-2/OQ-6) - -## Requirement -- `DaemonInstanceConfig`(REQ-048 per-instance TOML)에 `max_active_slots`(정수, M) 키를 추가하고 `config/*.toml` 에서 읽는다. admission(REQ-059)이 이 값을 capacity 게이트로 사용한다. env(`TREX_*`)·하드코딩 기본값은 채택하지 않으며, 기존 `TREX_TOTAL_SLOTS` 는 폐기한다. 미설정 시 부팅 실패 또는 명시적 검증 오류로 처리한다(암묵 기본값 금지). - -## Acceptance Criteria -1. Given `config/inst.toml` 에 `max_active_slots = N`, When 데몬 기동, Then `DaemonInstanceConfig.max_active_slots == N` 이고 admission 이 N 을 M 으로 사용한다. -2. Given `max_active_slots` 미설정, When 기동, Then 명시적 오류로 거부된다(암묵 기본값 사용 안 함). -3. Given 두 인스턴스가 서로 다른 `max_active_slots`, When 각자 기동, Then 인스턴스별 독립 M 으로 동작한다. - -## Impacted Area -- Docs: docs/data-model.md/docs/runtime-flows.md(설정), config/instance.example.toml -- Modules: trex_core/runtime/config.py(DaemonInstanceConfig) -- Tests: tests/runtime/test_config*.py - -## Notes -- REQ-048(per-instance TOML 로더) 계열. `TREX_TOTAL_SLOTS` 폐기는 REQ-005/REQ-010 영향(change-req 검토). -- 출처: dcy-020 FR-2, OQ-6. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-2/OQ-6 로부터 최초 생성(Proposed). `max_active_slots` per-instance TOML + `TREX_TOTAL_SLOTS` 폐기. - -#### Intent -- 동시 활성 최대치 M 을 인스턴스별 TOML 로 제어하도록 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: TOML 로딩/미설정 거부/인스턴스별 독립 3개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: config/instance.example.toml, baseline 문서 -- Modules: runtime/config.py(DaemonInstanceConfig) -- Tests: test_config*.py - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. REQ-048 계열, REQ-005/010(TREX_TOTAL_SLOTS) change-req 검토. - -#### Validation Plan -- TOML 키 로딩 + 미설정 거부 단위테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-028(+BAT-030 M=용량 캡 의미 확정·e2e M=1) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md b/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md deleted file mode 100644 index 906b82d..0000000 --- a/docs/srs/Configuration/req-076_watchdog-timeout-per-instance-toml.md +++ /dev/null @@ -1,76 +0,0 @@ -# req-076: watchdog timeout per-instance TOML 설정 - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: project maintainers - -## Intent -- 워치독 timeout(현재 env `TREX_WATCHDOG_TIMEOUT` 전역 단일, 기본 120s)을 인스턴스별로 독립 설정 가능하게 한다. 저빈도 폴링(최소 1분~최대 10분, dcy-027 OQ-4) 중장기 전략 인스턴스에서 watchdog expired 가 일상 상태가 되어 가시화 신호로서 의미를 잃는 문제(dcy-027 FR-3/OQ-3)를 해소한다. - -## Requirement -- `DaemonInstanceConfig`(REQ-048 per-instance TOML)에 `watchdog_timeout_sec`(정수, 초) optional 키를 추가하고 `config/*.toml` 에서 읽는다. TOML 값이 있으면 그 값이 해당 인스턴스의 워치독 timeout 으로 사용되고, 없으면 기존 env `TREX_WATCHDOG_TIMEOUT`(기본 120s) 동작을 그대로 유지한다(TOML 우선, env 폴백 — 기존 인스턴스 무회귀). 기존 per-instance TOML 메커니즘을 재사용하며 새 설정 채널을 만들지 않는다(dcy-027 NFR-3). 중장기 인스턴스 권장값은 1800s(30분) 이상(OQ-4 최대 폴링 600s 대비 여유). - -## Acceptance Criteria -1. Given `config/inst.toml` 에 `watchdog_timeout_sec = 1800`, When 데몬 기동, Then 해당 인스턴스의 워치독 timeout 이 1800s 로 동작한다(120s env 기본값 미적용). -2. Given TOML 에 `watchdog_timeout_sec` 미설정, When 기동, Then 기존 env `TREX_WATCHDOG_TIMEOUT`(기본 120s) 값으로 동작한다(기존 인스턴스 byte-for-byte 무회귀). -3. Given 두 인스턴스가 서로 다른 `watchdog_timeout_sec`(예: 당일 120 / 중장기 1800), When 각자 기동, Then 인스턴스별 독립 timeout 으로 watchdog expired 판정이 분리된다. -4. Given 중장기 timeout(1800s) 인스턴스에서 전략이 10분(600s) 주기로 slots RPC(spy=false)를 호출, When watchdog 판정, Then expired 로 오인되지 않고 `last_seen_at` 가시성은 유지된다. - -## Impacted Area -- Docs: docs/runtime-flows.md(워치독 설정 경로 — REQ-078 에서 정합), config/instance.example.toml -- Modules: trex_core/runtime/config.py(`DaemonInstanceConfig.watchdog_timeout_sec` + `from_toml` + `DaemonConfig` 전파), trex_core/runtime/daemon.py(워치독 timeout wiring) -- Tests: tests/runtime/test_config*.py(TOML 로딩/폴백/인스턴스 독립), watchdog 판정 경계 테스트 - -## Notes -- 출처: dcy-027 FR-3, OQ-3(per-instance timeout 연장 확정), OQ-4(폴링 주기 1~10분 → 권장 1800s+). NFR-3(기존 TOML 메커니즘 재사용) 흡수. -- REQ-060(max_active_slots)과 달리 **env 폴백을 유지**한다 — `TREX_WATCHDOG_TIMEOUT` 은 기존 운영 인스턴스가 사용 중이므로 폐기하지 않고 TOML 미설정 시 기본 경로로 보존(무회귀). -- 패턴 선례: `safety_sell_enabled`(REQ-048 CHG, dcy-024) — per-instance 운영 모드 차이는 TOML. -- watchdog 소유권 진입 시 외부 알림(슬랙·이메일)은 후속 Discovery(dcy-027 OQ-3 주석) — 본 REQ 범위 밖. - -## Change Log - -### CHG-20260612-01 -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary -- dcy-027 FR-3/OQ-3/OQ-4 로부터 최초 생성(Proposed). watchdog timeout per-instance TOML + env 폴백. - -#### Intent -- 중장기 저빈도 폴링 인스턴스에서 watchdog 가시화 신호를 유의미하게 유지하도록 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: TOML 우선/env 폴백 무회귀/인스턴스별 독립/저빈도 폴링 비오인 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: instance.example.toml, runtime-flows(REQ-078 정합) -- Modules: runtime/config.py, runtime/daemon.py -- Tests: test_config*.py + watchdog 경계 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- TOML 로딩/폴백/인스턴스 독립 단위 테스트 + watchdog 판정 경계 테스트로 검증. - -### CHG-20260612-02 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027(confirmed, handoff 승인, 사용자 결정 잔여 0건)과 모순 없음. env 폴백으로 기존 인스턴스 무회귀 전제 확인. - -### CHG-20260612-03 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — AC-1~4 전부 evidence 연결(`test_watchdog_timeout_toml.py` 7 passed: TOML 적용/TOML>env/env 폴백/기본 120/인스턴스 독립/<=0 거부/프로파일 조합). `from_config` 단일 지점 override 로 소비자 2곳(slots RPC·SafetySellService) 일치. 전체 446 passed + ruff clean. Blocking Issues 없음. diff --git a/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md b/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md deleted file mode 100644 index 9563cab..0000000 --- a/docs/srs/Configuration/req-077_long-term-instance-operation-profile.md +++ /dev/null @@ -1,78 +0,0 @@ -# req-077: 중장기 인스턴스 운영 프로파일 — 표준 TOML 레시피 + 기동 검증 - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: project maintainers - -## Intent -- 중장기 매매 전략용 인스턴스의 표준 운영 프로파일(TOML 레시피)을 정의하고 실 데몬 기동으로 검증한다. 멀티데이 보유가 장 마감·일자 경계·재시작을 넘어 유지되고 자율 청산이 발화하지 않음을 보장하는 설정 조합을 확정한다. (dcy-027 FR-1, OQ-1 (a′) — 기존 슬롯 도메인 + 프로파일 추가) - -## Requirement -- 중장기 인스턴스 표준 TOML 프로파일을 예시 파일(`config/longterm.example.toml` 또는 동등)로 제공한다. 프로파일 구성: `instance_key`(예: longterm, 당일 인스턴스와 분리 — dcy-027 A-2 확정), `safety_sell_enabled = false`(REQ-048 CHG/dcy-024), `watchdog_timeout_sec = 1800` 이상(REQ-076), `max_active_slots`(REQ-060), market-close 강제 청산 미적용(env `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 미설정 전제 명시). -- 이 프로파일로 기동한 데몬에서 매수 체결 후 보유가 일자 경계·재시작을 넘어 유지되고(영구 슬롯, dcy-020 토대) 자율 청산(safety-sell/market-close/forced_close)이 발화하지 않음을 검증한다. -- 당일 인스턴스와 같은 PostgreSQL·RabbitMQ 인프라에서 병행 운영 시 슬롯·큐 스코프가 간섭하지 않아야 한다(dcy-027 NFR-2 — instance_key 격리). - -## Acceptance Criteria -1. Given 중장기 프로파일 TOML(safety_sell_enabled=false, watchdog_timeout_sec≥1800, max_active_slots, 독립 instance_key), When 데몬 기동, Then `trex-safety-sell` 워커 미기동(WARNING 로그) + watchdog timeout 1800s 적용 + 독립 큐(`trex.slots.req.longterm` 등) 선언이 확인된다. -2. Given 기동된 중장기 인스턴스에서 paper 매수 체결(holding), When 일자 경계 통과·데몬 재시작, Then 슬롯이 holding 을 유지하고 자율 청산이 발화하지 않는다. -3. Given 당일 인스턴스(safety_sell_enabled=true)와 중장기 인스턴스 병행, When 양쪽 기동·운영, Then 슬롯(instance_key 스코프)·큐(per-instance 큐명) 간섭이 없다(기존 instance 격리 테스트 패턴으로 검증). -4. Given 예시 프로파일 파일, When 저장소 확인, Then 표준 레시피와 각 키의 중장기 의미(왜 false/1800s 인지) 주석이 포함되어 운영자가 복제 사용 가능하다. - -## Impacted Area -- Docs: config/longterm.example.toml(신규 또는 instance.example.toml 확장), docs/runtime-flows.md·docs/project-structure.md(REQ-078 정합) -- Modules: 신규 코드 없음 목표 — 기존 설정 메커니즘(REQ-048/060/076 + dcy-024) 조합. 검증 과정에서 갭 발견 시 해당 REQ change-req -- Tests: 프로파일 기동 검증(설정 조합 단위) + instance 격리 통합(기존 `test_instance_topology.py` 패턴 확장) - -## Notes -- 출처: dcy-027 FR-1, S-1, A-2(별도 instance_key 확정), NFR-2(무간섭 — AC-3 흡수). -- 보호 규칙은 전략 모듈 전적 책임(dcy-027 OQ-2) — 코어는 운영자 CLI `exit` 백스톱만 제공. 프로파일은 이를 전제로 한 "보호장치 OFF" 구성이며 경계 명시는 REQ-078 소관. -- `forced_close_at` 은 중장기 인스턴스에서 미사용(dcy-027 CONFIRM) — 프로파일 주석에 명시. -- REQ-076(watchdog TOML) 선행 의존 — 같은 batch 에서 함께 구현. - -## Change Log - -### CHG-20260612-01 -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary -- dcy-027 FR-1/S-1 로부터 최초 생성(Proposed). 중장기 표준 TOML 프로파일 + 기동·멀티데이·격리 검증. - -#### Intent -- 중장기 인스턴스를 재현 가능한 표준 레시피로 운영 가능하게 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: 기동 구성 확인/멀티데이 보유 유지/병행 무간섭/예시 파일 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: longterm.example.toml + baseline(REQ-078) -- Modules: 신규 코드 없음 목표(기존 메커니즘 조합) -- Tests: 프로파일 기동 + instance 격리 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-076 과 동일 batch) - -#### Validation Plan -- 프로파일 TOML 로딩 + 워커 미기동 + watchdog 적용 단위, paper 매수→재시작 holding 유지, instance 격리 통합 테스트. - -### CHG-20260612-02 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027 정합. REQ-076 의존은 동일 batch 편성으로 해소 예정 — blocker 아님. - -### CHG-20260612-03 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — AC-1/4 프로파일 조합 로딩 테스트(`config/longterm.example.toml` — longterm·safety_sell false·1800·cap 10) PASSED, AC-2 멀티데이 보유는 영구 슬롯 영속(BAT-027~030)·워커 미기동(BAT-036) 기존 evidence + 조합 설정 검증, AC-3 무간섭은 인스턴스 독립 테스트 + 기존 격리 회귀. 실 데몬 장기 run 은 release 운영 검증 위임(verification 명시). 446 passed. Blocking Issues 없음. diff --git a/docs/srs/Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md b/docs/srs/Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md deleted file mode 100644 index 2074ede..0000000 --- a/docs/srs/Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md +++ /dev/null @@ -1,100 +0,0 @@ -# req-113: KISConfig 소스 우선순위 — portfolio-hub fetch 1차 + env break-glass + opt-in - -- Status: Implemented -- Type: Configuration -- Priority: High -- Owner: trex-core-dev - -## Intent -- 현재 `DaemonConfig.from_env()`는 KIS 인증정보를 `TREX_KIS_BROKER_*` 환경변수에서만 읽는다. dcy-036 C1/FR-4/NFR-1에 따라 "fetch 1차, env는 명시적 break-glass 오버라이드"로 소스 우선순위를 변경하되, 기존 단기 인스턴스(M1/M2)의 회귀를 막기 위해 fetch는 **opt-in**(명시적 활성화 없으면 기존 env-only 경로 보존)으로 도입한다. -- 이 REQ는 설정 계층(TOML/env/fetch 우선순위)과 `DaemonInstanceConfig`/`DaemonConfig` 로더의 변경만 다루며, 실제 fetch I/O는 REQ-112 어댑터에 위임한다. - -## Requirement -- `DaemonInstanceConfig`(per-instance TOML 로더)에 `credential_fetch_enabled: bool = False` 필드를 추가한다. TOML에 `credential_fetch_enabled = true`가 명시된 경우에만 활성화된다(opt-in, 부재 시 False = env-only 기존 경로 보존). -- `credential_fetch_enabled = true`일 때 `DaemonConfig` 구성 단계에서 KISConfig 소스 우선순위는 다음과 같다: - 1. **portfolio-hub fetch** (`PortfolioHubReadAdapter.fetch_broker_credential()` 호출) — 성공 시 반환된 `BrokerCredentialPayload`로 `KISConfig` 구성. - 2. **env break-glass** (`TREX_KIS_BROKER_BREAK_GLASS=true` 환경변수가 명시적으로 설정된 경우) — portfolio-hub fetch를 시도하지 않고 기존 `TREX_KIS_BROKER_*` env로 KISConfig 구성. 운영자가 의도적으로 break-glass 경로를 활성화한 것으로 간주. - - fetch와 break-glass가 모두 부재/비활성이면 env-only 기존 경로(`TREX_KIS_BROKER_*`) 유지. -- `credential_fetch_enabled = false`(기본값)일 때: 기존 `TREX_KIS_BROKER_*` env-only 경로를 byte-for-byte 보존한다. portfolio-hub를 호출하지 않는다. -- break-glass 활성화 여부를 기동 시 INFO 로그로 출력한다(운영자 인지). 단, app_secret은 마스킹(NFR-2). -- KISConfig 구성에 사용된 소스(fetch / break-glass / env-only)를 기동 시 INFO 로그로 기록한다. - -## Acceptance Criteria -1. Given TOML에 `credential_fetch_enabled = false`(부재 포함)이고 `TREX_KIS_BROKER_*` env 설정, When 데몬 기동, Then `KISConfig`가 `TREX_KIS_BROKER_*` env-only로 구성되고 portfolio-hub fetch 호출이 발생하지 않는다 (기존 회귀 없음). -2. Given TOML에 `credential_fetch_enabled = true`이고 portfolio-hub fetch가 유효한 `BrokerCredentialPayload`를 반환, When 데몬 기동, Then `KISConfig.app_key`, `KISConfig.app_secret`, `KISConfig.account_ref`가 fetch 결과로 구성된다. -3. Given TOML에 `credential_fetch_enabled = true`이고 `TREX_KIS_BROKER_BREAK_GLASS=true` 환경변수 설정, When 데몬 기동, Then portfolio-hub fetch를 시도하지 않고 `TREX_KIS_BROKER_*` env로 KISConfig가 구성된다 (break-glass 경로). -4. Given break-glass 활성화 상태, When 데몬 기동 로그 확인, Then break-glass 활성화 사실이 INFO로 기록된다. -5. Given 어떤 KISConfig 구성 경로든, When 기동 로그 확인, Then `app_secret` 값이 평문으로 출력되지 않는다 (마스킹). -6. Given `credential_fetch_enabled = true` TOML이고 `TREX_KIS_BROKER_BREAK_GLASS` 미설정, When 기동 로그 확인, Then KISConfig 소스가 "fetch" 임을 INFO로 기록한다. -7. Given `DaemonInstanceConfig.from_toml`에서 `credential_fetch_enabled` 파싱, When TOML에 해당 키 부재, Then `False`로 파싱된다 (opt-in 기본값). - -## Impacted Area -- Docs: 없음 (baseline 문서 갱신은 별도 scope) -- Modules: - - `trex_core/runtime/config.py` — `DaemonInstanceConfig`에 `credential_fetch_enabled: bool = False` 필드 추가; `from_toml()` 로더에서 파싱; `from_config()` 또는 `DaemonConfig` 조립 단계에서 fetch/break-glass/env-only 우선순위 로직 추가; `TREX_KIS_BROKER_BREAK_GLASS` 환경변수 인식 - - `trex_core/runtime/daemon.py` (또는 조립 지점) — 기동 시 소스 INFO 로그 출력 -- Tests: - - `tests/runtime/test_config.py` — AC-1~7 케이스 추가: fetch opt-in off/on, break-glass, 마스킹, TOML 파싱 기본값 - -## Notes -- `TREX_KIS_BROKER_BASE_URL`은 fetch 경로에서도 여전히 env로 제공한다. portfolio-hub `/v1/user_info`는 `app_key`/`app_secret`/`account_ref`만 전달하며 `base_url`과 기타 KIS 설정(timeout, allow_live 등)은 env에서 읽는다. -- `TREX_KIS_BROKER_BREAK_GLASS` 환경변수명은 의도적으로 명시적이다 — 운영자가 비상 경로임을 인지하고 설정해야 한다. -- portfolio-hub fetch 실패(네트워크 오류 등) 시 처리는 REQ-114(그날-시작 로드 게이트)가 담당한다. 이 REQ는 설정 우선순위 계층만 다룬다. -- OQ-4(알람 trex-notifier 연동): Deferred — REQ-114 Notes 참조. -- Source: dcy-036 C1, FR-4, NFR-1, OQ-3 Answered (fetch 1차, env 명시적 break-glass). - -## Change Log - -### CHG-20260628-01 -- Date: 2026-06-28 -- Author: trex-core-dev - -#### Change Summary -- 신규 생성 (Proposed) — dcy-036 C1/FR-4/NFR-1/OQ-3 산출물. - -#### Intent -- dcy-036 FR-4(KISConfig 소스 우선순위 확장: fetch 1차 + env break-glass + opt-in)를 Configuration REQ로 정규화한다. REQ-112 어댑터의 소비자 배선 계층. - -#### Acceptance Criteria Delta -- Added: - - AC-1~7 전체 (신규 초안) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - `config.py` `DaemonInstanceConfig` 확장 (`credential_fetch_enabled`), `from_toml` 파싱, 조립 우선순위 로직 - - `daemon.py` 기동 로그 -- Tests: - - `tests/runtime/test_config.py` 케이스 추가 - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 대기) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-112~115 일괄 batch 배정 검토. - -#### Validation Plan -- pytest로 AC-1~7 각 케이스 확인. AC-1(opt-in off → env-only, fetch 미호출)가 회귀 방지의 핵심 케이스. - -### CHG-20260628-02 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req: `Proposed` → `Approved`. dcy-036 FR-4/OQ-3 추적. credential_fetch_enabled opt-in 기본 false로 M1/M2 byte-for-byte 보존(AC-1·NFR-1) 적절. fetch 1차→env break-glass(명시적 TREX_KIS_BROKER_BREAK_GLASS) 우선순위 정합. base_url 등은 env 유지 합리. blocker 없음. - -### CHG-20260628-03 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- 부분 구현 — BAT-053 PR #34 머지(merge commit 01655ab). credential_fetch_enabled TOML 플래그 신규 필드, opt-in off 회귀보존(AC-1), break-glass 코드, TOML 기본 False(AC-7) 구현·머지 완료. **연기: AC-2(fetch 결과로 KISConfig 조립)는 KISBroker 런타임 재조립 필요 → M3 후속 batch.** AC-4 break-glass 로그 내용 assert 보강도 후속. Status `Approved` 유지(미충족 AC 존재). - -### CHG-20260628-04 -- Date: 2026-06-28 -- Author: 팀장(Claude) — confirm-req-implemented: `Approved` → `Implemented` -- 근거: BAT-054(dcy-036 KISBroker 양의 경로 주입), 커밋 11decaa. QC(opus) **PASS-WITH-NOTES, Blocker 0** — 독립 테스트 재실행 757 passed/회귀 0. **연기됐던 AC-2(fetch 결과로 KISConfig 조립 = KISBroker `reinitialize()` 런타임 재주입)** 및 **AC-4(break-glass 로그 'break-glass' 키워드 assert 보강)** 충족·검증. opt-in off 기본 경로 byte-for-byte 회귀보존(AC-1) 유지 확인. fail-closed 불변식 미파괴(reinit 실패가 entry_allowed 불변), secret 평문 로그 미노출, domain 순수·AMQP 무변경 QC 확인. -- 후속(비차단): QC Minor #3(design D1의 L2 token_cache.invalidate 호출이 구현에선 키-miss 전략으로 대체 — 설계-구현 문서 정합) 정정. AC-4 로그 assert 강도는 키워드 수준(수용). -- 게이트: 머지(PR)는 사장님 결재 게이트(실거래 주입=외부 비가역). diff --git a/docs/srs/Data/req-006_postgresql-slot-persistence.md b/docs/srs/Data/req-006_postgresql-slot-persistence.md deleted file mode 100644 index d579ff4..0000000 --- a/docs/srs/Data/req-006_postgresql-slot-persistence.md +++ /dev/null @@ -1,371 +0,0 @@ -# req-006: PostgreSQL SlotRepository 영속화 + 스키마 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent -- 코어가 `daily_slot` 을 durable 단일 writer 로 소유하도록 `SlotRepository` 의 PostgreSQL 구현과 스키마를 추가한다. dcy-001 부록 A 컬럼 계약을 영속화에서 실제로 충족시킨다. - -## Requirement -- `SlotRepository` Protocol 의 psycopg 기반 PostgreSQL 구현을 추가하고, trex-core 소유의 신규 마이그레이션으로 `daily_slot`(부록 A 16컬럼)·`daily_symbol_entries`·`slot_transition_log`·`intent_log` 테이블을 생성한다. advisory lock 을 실제 `pg_advisory_xact_lock` 으로 구현한다. `EXPECTED_SLOT_FIELDS` ↔ `daily_slot` 실제 컬럼(information_schema) 드리프트 테스트를 동반한다. - -## Acceptance Criteria -1. Given PostgreSQL 백엔드, When 마이그레이션 적용, Then `daily_slot`·`daily_symbol_entries`·`slot_transition_log`·`intent_log` 가 생성되고 `daily_slot` 컬럼이 부록 A(16컬럼)와 일치한다. -2. Given PostgreSQL `SlotRepository`, When `SlotService` 를 이 repo 로 구동, Then 도메인 단위/통합테스트가 in-memory 와 동일하게 통과한다(전이/재진입+1/race 감지). -3. Given 드리프트 테스트, When `EXPECTED_SLOT_FIELDS` ↔ information_schema 대조, Then 일치하고 컬럼 누락 시 실패한다(dcy-001 OQ-2 DB-side 검사 실현). -4. Given 상태 변경, When transit_slot, Then `pg_advisory_xact_lock` 하 단일 트랜잭션으로 직렬화되고 lock 보유 중 외부 I/O 가 없다. -5. (CHG-20260610-02, dcy-023/issue #22) Given 마이그레이션 013(`DROP COLUMN IF EXISTS allocated_capital`, 멱등) 적용, Then `daily_slot` 에 `allocated_capital` 컬럼이 없고 persistence flat 매핑(SELECT/UPDATE/행 복원)에서 해당 컬럼 참조가 제거되며, `EXPECTED_SLOT_FIELDS` ↔ information_schema 드리프트 테스트가 green 이다. 적용은 코드 배포와 동시(`apply_migrations` 자동, dcy-023 OQ-2), 비가역 drop 이므로 적용 전 백업 절차를 release 문서에 명시한다. -6. (CHG-20260612-01, issue #25) Given PostgreSQL `list_slots(instance_key, status_filter)`, When `status_filter` 가 status 값 집합이면, Then `WHERE instance_key = %s AND status = ANY(%s)` 로 DB 에서 직접 필터링해 해당 행만 fetch 하고(전 행 fetch + Python 필터 제거), `None` 이면 기존대로 `WHERE instance_key = %s` 전체를 반환한다. 반환 결과는 in-memory 구현과 동일하다. - -## Impacted Area -- Docs: docs/data-model.md(테이블/계약), dcy-001 부록 A(기준) -- Modules: trex_core/ 하위 persistence 어댑터(psycopg) + 마이그레이션 SQL; 도메인 패키지는 무의존 유지(NFR-1); (CHG-20260610-02) postgresql.py flat 매핑 allocated_capital 제거 + 013_drop_allocated_capital.sql; (CHG-20260612-01) postgresql.py `list_slots` DB-level status 필터(`status = ANY(%s)`) -- Tests: PostgreSQL repo 통합테스트 + 드리프트 테스트 - -## Notes -- 스키마는 부록 A 단일 기준, orbit-v2 DDL 참조(dcy-002 OQ-4). `daily_slot_archive`/prune 는 범위 밖. -- NFR-1(도메인 순수성) 일부 흡수: psycopg 의존은 persistence 어댑터에만, 도메인 import 스윕 0 유지. -- REQ-005(런타임) 선행 권장, REQ-007/008(API)의 단일 writer 근거. -- 출처: dcy-002 FR-5. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-002 FR-5 로부터 최초 생성(Proposed). NFR-1 일부 흡수. - -#### Intent -- PostgreSQL 영속화 + 스키마 + 드리프트 테스트 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 마이그레이션/컬럼 일치, repo 구동 테스트, 드리프트 테스트, advisory lock 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/data-model.md, dcy-001 부록 A -- Modules: - - persistence 어댑터(psycopg) + 마이그레이션 SQL -- Tests: - - PostgreSQL repo 통합테스트 + 드리프트 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- PostgreSQL 통합테스트(가능 시) + 드리프트 테스트로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-002(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-002 release-candidate). - -#### Intent -- BAT-002 verification.md 의 acceptance mapping + evidence(pytest 74 pass / PG 통합 / 데몬 E2E)로 모든 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-002 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-002 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012) `daily_slot.entry_price` → `limit_price` 컬럼 rename 마이그레이션(`006_rename_entry_price_to_limit_price.sql`, idempotent DO-block) + persistence 매핑(`_SLOT_COLUMNS`/update_slot/_row_to_slot) 갱신. 순수 rename 으로 기존 행 데이터 보존(S-3). BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 도메인 필드 rename(REQ-002)을 영속 스키마/매핑에 반영하면서 기존 데이터를 보존. - -#### Acceptance Criteria Delta - -- Updated: - - daily_slot 컬럼 entry_price → limit_price (마이그레이션 006, 데이터 보존). persistence read/write 매핑 동기화. - -#### Impacted Area Delta - -- Modules: `trex_core/adapters/persistence/postgresql.py`, `trex_core/adapters/persistence/sql/006_rename_entry_price_to_limit_price.sql`(신규) -- Tests: tests/adapters/persistence/test_migration_and_drift.py(컬럼 계약 = limit_price, 마이그레이션 idempotent) - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: yes (entry_price 컬럼/매핑) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: PG 게이트 마이그레이션 round-trip + 컬럼 계약(limit_price) + idempotency. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-019/issue #18) 영속화 레이어에 slot-scoped `entry_count` 반영: 멱등 마이그레이션 008(`ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS entry_count INTEGER NOT NULL DEFAULT 0`), `_SLOT_COLUMNS`/`update_slot`/`_row_to_slot` flat 매핑 +entry_count, 신규 `find_allocatable_slot`(idle ∪ closed, `ORDER BY entry_count ASC, updated_at ASC, slot_id ASC LIMIT 1`) selector 추가(in-memory 동등 구현 포함). 동시성은 기존 advisory lock + 재시도 규약 보존(find 쿼리 FOR UPDATE 미사용 — find_idle_slot 와 동일). BAT-025 검증 완료 → Implemented 유지. - -#### Intent - -- buy admission 의 후보 확장(idle∪closed)·공정 정렬을 영속 store 에서 결정적으로 지원하고, slot row 에 진입 횟수를 durable 하게 보존. - -#### Acceptance Criteria Delta - -- Updated: - - AC-1 컬럼 계약: `daily_slot` 에 `entry_count` 컬럼 추가(마이그레이션 008, additive·멱등). `EXPECTED_SLOT_FIELDS` ↔ information_schema 드리프트 가드가 자동 반영. -- Added: - - `find_allocatable_slot(trading_date)` 가 idle ∪ closed 후보를 `entry_count → updated_at → slot_id` 정렬로 1건 반환(없으면 None). - -#### Impacted Area Delta - -- Modules: `trex_core/adapters/persistence/sql/008_add_slot_entry_count.sql`(신규), `trex_core/adapters/persistence/postgresql.py`(_SLOT_COLUMNS/update_slot/_row_to_slot/find_allocatable_slot), `trex_core/domain/slot/repository.py`·`in_memory_repository.py`(selector 포트 + 동등 구현) -- Tests: tests/adapters/persistence/test_migration_and_drift.py(자동 반영), tests/domain/slot/test_slot_entry_count.py(selector 정렬) - -#### Delivery Trace Delta - -- Batches: BAT-025 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-025 에서 완료) -- Existing Implementation Invalidated: no (additive 컬럼·신규 selector — 기존 find_idle_slot/round-trip 보존) -- Status Recommendation: Implemented (change-req → BAT-025 재검증) -- Follow-up Action: confirm-req-implemented(BAT-025). - -#### Validation Plan - -- BAT-025: unit (in-memory selector 정렬·reallocation) green; 마이그레이션 008 round-trip + 드리프트 가드는 test DSN 환경에서 자동 검증(라이브 runtime DSN 미사용 — 파괴적 truncate 회피). - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020) `daily_slot` persistence 가 영구 슬롯 스키마로 **재정의 예정** — REQ-056(portfolio_id 영구키·instance_key·`UNIQUE(instance_key, portfolio_id)`·trading_date/entry_count 제거·retired_at)·REQ-062(qty 미러)가 대체. find_allocatable_slot 등 풀 모델 쿼리 폐기. 현재 코드 불변이라 Implemented 유지(keep-current), dcy-020 스키마 배치 + cutover(REQ-064)에서 확정. - -#### Intent - -- 영구 슬롯 persistence 전환 추적성 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(dcy-020 delivery 배치에서 스키마 AC 재정의) - -#### Impacted Area Delta - -- Docs: dcy-020, REQ-056/062/064 -- Modules: (예정) adapters/persistence/postgresql.py·sql/ 재설계 -- Tests: (예정) test_migration_and_drift 재작성 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-020 스키마/cutover 배치 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점) -- Existing Implementation Invalidated: no (코드 불변) -- Status Recommendation: keep-current -- Follow-up Action: dcy-020 스키마/cutover 배치에서 재정의 + status 전환. - -#### Validation Plan - -- dcy-020 스키마/cutover 배치 verification 으로 대체. - -### CHG-20260610-02 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-023/issue #22) `daily_slot.allocated_capital` 컬럼 제거 — 신규 마이그레이션 `013_drop_allocated_capital.sql`(`DROP COLUMN IF EXISTS`, 멱등) + persistence flat 매핑(SELECT 컬럼 목록/UPDATE 절/행 복원)에서 해당 컬럼 참조 삭제. 코드 배포와 동시 적용(dcy-023 OQ-2 운영자 결정), 비가역 drop — BAT-030 의 011 선례 준용(사전 백업 + 롤백 절차 release 문서 명시). AC-5 추가, Implemented→Approved(재구현 대기). - -#### Intent - -- 소비처 없는 죽은 컬럼을 영속 계약에서 제거해 `EXPECTED_SLOT_FIELDS` ↔ DB 계약을 실사용 필드로 정렬. - -#### Acceptance Criteria Delta - -- Added: - - AC-5: 마이그레이션 013 적용 후 allocated_capital 컬럼 부재 + flat 매핑 참조 제거 + 드리프트 테스트 green + 동시 적용·백업 절차. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Modules: trex_core/adapters/persistence/postgresql.py(flat 매핑), trex_core/adapters/persistence/sql/013_drop_allocated_capital.sql(신규) -- Tests: 컬럼 계약 가드(드리프트) + persistence round-trip 테스트 정리 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-023 batch -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (dcy-023 batch 에서 수행 — 테스트 DSN round-trip) -- Existing Implementation Invalidated: yes (컬럼·매핑 제거 — 기존 컬럼 계약 evidence 무효) -- Status Recommendation: Approved (Implemented→Approved 되돌림) -- Follow-up Action: dcy-023 batch 구현·검증 후 confirm-req-implemented. release 시 백업·롤백 절차 명시. - -#### Validation Plan - -- dcy-023 batch: 테스트 DSN 에서 013 멱등 적용 + information_schema 대조 + round-trip + 전체 suite green. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음. - -### CHG-20260612-01 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #25) PostgreSQL `list_slots` 에 `status_filter` 를 받아 `WHERE instance_key = %s AND status = ANY(%s)` 로 DB 레벨 필터링한다. slots RPC 의 per-request full scan(전 행 fetch + Python 필터)을 제거. `status_filter=None` 은 기존 전체 fetch. 스키마/마이그레이션 변경 없음(쿼리 한정). 신규 AC-6 → **Implemented → Approved 되돌림**. - -#### Intent - -- `trex.slots.req` 가 200-slot 인스턴스에서 매 요청마다 전 슬롯을 읽어오던 비용을 줄인다(issue #25 (1) DB 레벨 status 필터링). REQ-002 포트 확장에 대응하는 PostgreSQL 구현. - -#### Acceptance Criteria Delta - -- Added: - - AC-6: `list_slots(instance_key, status_filter)` DB-level `status = ANY(%s)` 필터 + `None` 전체. in-memory 와 결과 동일. -- Updated/Removed: 없음 - -#### Impacted Area Delta - -- Docs: 없음 -- Modules: `trex_core/adapters/persistence/postgresql.py`(`list_slots` 쿼리) -- Tests: PG `list_slots` status 필터 통합 테스트(PG 게이트), in-memory parity - -#### Delivery Trace Delta - -- Batches: BAT-040 (예정, issue #25) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-040 에서 AC-6 + 기존 AC 회귀) -- Existing Implementation Invalidated: no (쿼리 확장, default None 기존 동작 보존). 신규 AC-6 미구현으로 Implemented 유지 불가 → Approved. -- Status Recommendation: Implemented → **Approved** -- Follow-up Action: confirm-req → BAT-040 구현·검증 → confirm-req-implemented - -#### Validation Plan - -- BAT-040: PG `list_slots` status 필터 통합(PG 게이트, DSN 가용 시) + in-memory parity 단위 + 전체 suite + ruff clean. 스키마 드리프트 테스트 회귀(컬럼 불변). - -### CHG-20260612-IMPL -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-040(release-candidate) verification — AC-6 PG `list_slots` `status = ANY(%s)` 게이트 테스트(`test_list_slots_status_filter_pg`, DSN 가용 시 실행; 현재 skip) + in-memory parity(REQ-002 AC-6)로 동작 계약 보증, 스키마 변경 없음(쿼리 한정), 전체 439 passed + ruff clean. Blocking Issues 없음. diff --git a/docs/srs/Data/req-035_slot-price-threshold-fields-and-migration.md b/docs/srs/Data/req-035_slot-price-threshold-fields-and-migration.md deleted file mode 100644 index ed5f2ba..0000000 --- a/docs/srs/Data/req-035_slot-price-threshold-fields-and-migration.md +++ /dev/null @@ -1,116 +0,0 @@ -# req-035: 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent - -stop-loss(손절)·take-profit(익절) 가격 트리거를 지원하기 위해 슬롯 도메인 모델에 `stop_loss_price: Optional[Decimal]`과 `take_profit_price: Optional[Decimal]` 필드를 추가하고, PostgreSQL 스키마에 해당 컬럼을 additive 마이그레이션으로 추가한다(dcy-007 코드 영향 — 필드 추가 + DB 마이그레이션). - -## Requirement - -- 슬롯 도메인 모델(`SlotModel` / `Slot`)에 `stop_loss_price: Optional[Decimal]`과 `take_profit_price: Optional[Decimal]` 필드를 추가해야 한다. 기본값은 `None`이다. -- PostgreSQL `slots` 테이블에 `stop_loss_price NUMERIC` 및 `take_profit_price NUMERIC` 컬럼을 NULL 허용으로 추가하는 마이그레이션을 제공해야 한다. 기존 슬롯 행은 `NULL`로 유지된다. -- 매수 intent(`BuyIntent` / `IntentIn`) payload에 `stop_loss_price`와 `take_profit_price` 필드를 선택 항목으로 추가해야 한다. 미설정 시 `None`으로 처리한다. -- persistence 레이어(PostgreSQL 어댑터)는 두 필드를 읽기·쓰기 대상에 포함해야 한다. - -## Acceptance Criteria - -1. Given `SlotModel` 인스턴스를 생성할 때 `stop_loss_price`와 `take_profit_price`를 지정하지 않으면, When 필드를 조회하면, Then 두 필드 모두 `None`이다. -2. Given `stop_loss_price=Decimal("50000")`으로 슬롯을 생성해 Postgres에 저장하면, When 같은 슬롯을 다시 조회하면, Then `stop_loss_price`가 `Decimal("50000")`으로 복원된다. -3. Given 기존 슬롯 row가 마이그레이션 전에 존재하면, When 마이그레이션을 실행하면, Then 기존 row의 `stop_loss_price`와 `take_profit_price`가 `NULL`이고 마이그레이션이 멱등하게 완료된다. -4. Given 매수 intent payload에 `stop_loss_price`와 `take_profit_price`를 포함하면, When `IntentService.submit_buy`를 호출하면, Then 해당 슬롯의 두 필드가 저장된다. -5. Given 매수 intent payload에 두 필드가 없으면, When `submit_buy`를 호출하면, Then 두 필드는 `None`으로 저장되고 오류가 발생하지 않는다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — intent RPC payload에 `stop_loss_price`, `take_profit_price` 선택 필드 추가 (REQ-042 참조) -- Modules: - - `trex_core/domain/slot/models.py` — `stop_loss_price`, `take_profit_price` 필드 추가 - - `trex_core/adapters/persistence/postgresql.py` — 두 필드 read/write 포함 - - `trex_core/adapters/persistence/sql/` — 마이그레이션 스크립트 추가 - - `trex_core/application/intents.py` — `IntentIn`(또는 `BuyIntent`)에 두 필드 추가 - - `trex_core/application/messaging_handlers.py` — intent RPC payload 파싱에 두 필드 추가 -- Tests: - - 슬롯 도메인 모델 단위 테스트 — 필드 기본값 및 직렬화 확인 - - persistence 통합 테스트 — 저장·조회 라운드트립 확인 - - 마이그레이션 멱등성 테스트 - -## Notes - -- `Decimal` 타입을 사용해 부동소수점 오차 없이 가격을 표현한다. -- `take_profit_price`와 `stop_loss_price` 값의 논리적 유효성(예: take_profit > stop_loss)은 이번 REQ 범위 밖이다. REQ 단계에서 추가 필요 시 change-req로 처리한다. -- 기존 `forced_close_at` 필드는 이미 존재하며 이 REQ에서 다루지 않는다. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-035 초안 생성 - -#### Intent - -- 가격 임계값 필드 추가 + DB 마이그레이션을 독립 Data REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~5 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md (REQ-042에서 처리) -- Modules: models.py, postgresql.py, sql 마이그레이션, intents.py, messaging_handlers.py -- Tests: 도메인 단위 + persistence 통합 + 마이그레이션 멱등성 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 diff --git a/docs/srs/Data/req-044_killswitch-exclusion-store-and-migration.md b/docs/srs/Data/req-044_killswitch-exclusion-store-and-migration.md deleted file mode 100644 index 28beb3f..0000000 --- a/docs/srs/Data/req-044_killswitch-exclusion-store-and-migration.md +++ /dev/null @@ -1,149 +0,0 @@ -# req-044: kill-switch day-scoped 제외 store + 테이블 + 마이그레이션 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent - -kill-switch 발동 시 대상 범위(portfolio/전역)를 **당일(day-scoped) 전략 실행에서 제외**하는 상태를 데몬 재시작 후에도 유지하기 위해 영속화한다. 기존 `snapshot_pause`(portfolio_id PK, resume 전까지 영구, 날짜 없음)와 의미가 다르므로(당일만, `trading_date` 키), **별도 테이블 + 전용 store 어댑터를 신설**한다(dcy-008 OQ-2 Answered). 다음 거래일 부팅 시 자동 무효된다. - -## Requirement - -- 신규 테이블(예: `killswitch_exclusion`)을 추가하는 additive 마이그레이션을 작성한다. 최소 컬럼: `portfolio_id`(전역 제외 표현 포함), `trading_date`, `actor`, `created_at`. - - 전역(global) 제외 표현 방식(예: `portfolio_id = '*'` sentinel 또는 별도 scope 컬럼)은 구현 시 결정한다(Notes). - - PK 또는 unique 제약은 `(portfolio_id, trading_date)` 로 동일 거래일 중복 제외를 멱등 처리한다. -- 전용 store 어댑터(예: `PostgreSQLKillSwitchStore`)가 아래를 제공한다: `exclude(portfolio_id|global, trading_date, actor)`, `clear(portfolio_id|global, trading_date)`(수동 해제, REQ-046), `list_excluded(trading_date) -> set`. -- `list_excluded(trading_date)` 는 **해당 거래일** 의 제외 항목만 반환한다. 다른 날짜 row 는 자동으로 결과에서 제외된다(day-scoped). 과거 row 정리(삭제)는 선택이며 조회가 trading_date 로 필터링되므로 정합에 영향 없다. -- 기존 `snapshot_pause` / `PostgreSQLPauseStore` 는 변경하지 않는다(관심사 분리). - -## Acceptance Criteria - -1. Given 마이그레이션 적용, When 스키마를 확인하면, Then `killswitch_exclusion` 테이블이 `(portfolio_id, trading_date, actor, created_at)` 컬럼과 `(portfolio_id, trading_date)` 멱등 제약으로 존재한다. -2. Given 마이그레이션을 2회 적용, When 재적용하면, Then `IF NOT EXISTS` 패턴으로 오류 없이 멱등하다(기존 데이터 보존). -3. Given `exclude("pf-A", D)`, When `list_excluded(D)`, Then `{"pf-A"}` 를 포함한다. -4. Given `exclude("pf-A", D)`, When `list_excluded(D+1)`(다음 거래일), Then `pf-A` 가 포함되지 않는다(day-scoped 자동 무효). -5. Given 같은 `(pf-A, D)` 로 `exclude` 2회, When 호출하면, Then 멱등(중복 row 없음, 오류 없음). -6. Given `exclude("pf-A", D)` 후 `clear("pf-A", D)`, When `list_excluded(D)`, Then `pf-A` 가 더 이상 포함되지 않는다(수동 해제, REQ-046). -7. Given 전역 제외, When `exclude` 후 `list_excluded(D)`, Then 전역 sentinel 이 결과에 포함되어 호출자가 전체 제외로 해석할 수 있다. - -## Impacted Area - -- Docs: - - `docs/project-structure.md` — 신규 테이블/store 모듈 반영 (REQ-047 처리) -- Modules: - - `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (신규, additive) - - `trex_core/adapters/portfolio/killswitch_store.py` 또는 동등 위치 — `PostgreSQLKillSwitchStore` -- Tests: - - store CRUD + day-scoped 조회 + 멱등 + 전역 sentinel 단위/통합 테스트(PG 게이트) - -## Notes - -- dcy-008 OQ-2 Answered: 별도 테이블/store 신설(snapshot_pause 와 분리). -- 전역 제외 표현(`portfolio_id='*'` vs scope enum 컬럼)은 구현 결정 — masking 소비처(REQ-046)와 계약 일치만 보장하면 됨. -- `trading_date` 기준: dcy-008 의 day-scoped 의미 = "발동 거래일". 거래일 경계(UTC vs KST)는 데몬의 기존 trading_date 관례(UTC date)를 따르되 REQ-045/046 와 일치시킨다. -- BAT-010 R-2(마이그레이션 실 PG round-trip) 선례에 따라 verification 에서 실 PG 적용 확인. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-008(confirmed) → REQ-044 초안 생성. - -#### Intent - -- 당일 제외 영속(OQ-2 Answered: 별도 테이블)을 Data REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~7 전체 - -#### Impacted Area Delta - -- Docs: project-structure.md (REQ-047) -- Modules: sql/005_create_killswitch_exclusion.sql, killswitch_store.py -- Tests: store CRUD + day-scoped + 멱등 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-008(confirmed) OQ-2 결정(별도 테이블) 반영. 전역 sentinel 표현은 구현 결정으로 승인 blocker 아님. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-012 release-candidate). - -#### Intent - -- BAT-012 verification 의 AC↔evidence(store in-mem 5 + PG 5: 스키마/멱등/day-scoped/clear/전역 sentinel; 마이그레이션 005 실 PG round-trip 인프라 게이트 290 passed)로 AC-1~7 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-012 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-012 배포 편성. diff --git a/docs/srs/Data/req-056_asset-permanent-slot-schema.md b/docs/srs/Data/req-056_asset-permanent-slot-schema.md deleted file mode 100644 index 2f1c819..0000000 --- a/docs/srs/Data/req-056_asset-permanent-slot-schema.md +++ /dev/null @@ -1,95 +0,0 @@ -# req-056: 자산 영구 슬롯 스키마 재설계 + 마이그레이션 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent -- 익명 슬롯 풀(`daily_slot` 일일 익명 행)을 portfolio-hub universe 자산과 1:1 대응하는 **영구 슬롯**으로 전환하기 위해, `daily_slot` 스키마를 자산 키·인스턴스 스코프·영속 모델로 재설계한다. (dcy-020 FR-1/OQ-3/OQ-7) - -## Requirement -- `daily_slot` 을 영구 슬롯으로 재설계한다: `portfolio_id` 를 **NOT NULL 영구 키**로, `instance_key` 컬럼 추가, **`UNIQUE(instance_key, portfolio_id)`**, 은퇴 마커 `retired_at`(또는 `universe_detached`) 추가. `trading_date`(슬롯 정체성)·`entry_count`(dcy-019) 컬럼은 제거하고, trading_date 는 이벤트/원장/정책의 시간 속성으로만 둔다. 멱등 마이그레이션(009) + `EXPECTED_SLOT_FIELDS` 계약 + persistence flat 매핑 + `data-model.md` 를 같은 변경 묶음으로 정합한다. slot-scoped 테이블(slot_transition_log/intent_log/ledger_outbox/snapshot_pause/killswitch_exclusion)도 `instance_key` 스코프화한다. - -## Acceptance Criteria -1. Given 마이그레이션 적용, When `daily_slot` 스키마를 확인, Then `portfolio_id NOT NULL`, `instance_key` 존재, `UNIQUE(instance_key, portfolio_id)`, `retired_at` 존재, 슬롯 정체성 `trading_date`·`entry_count` 부재. -2. Given `EXPECTED_SLOT_FIELDS` 계약 가드, When 도메인 필드셋과 information_schema 를 비교, Then 영구 슬롯 필드셋과 일치한다. -3. Given 같은 `(instance_key, portfolio_id)` 행 중복 삽입, When insert, Then UNIQUE 제약으로 거부된다. -4. Given slot-scoped 테이블, When 스키마 확인, Then 각 테이블이 `instance_key` 로 스코프되어 인스턴스 간 행이 섞이지 않는다. - -## Impacted Area -- Docs: docs/data-model.md (daily_slot 재설계), dcy-020 -- Modules: trex_core/adapters/persistence/sql/009_*.sql(신규), trex_core/adapters/persistence/postgresql.py, trex_core/domain/slot/models.py -- Tests: tests/domain/slot/test_contract.py, tests/adapters/persistence/test_migration_and_drift.py - -## Notes -- 기존 REQ 영향(change-req 동반): REQ-002(Slot 필드/EXPECTED_SLOT_FIELDS 재정의), REQ-006(persistence 스키마), REQ-003(계약 가드). dcy-019 `entry_count`·dcy-014 `trading_date` 슬롯키 제거는 그 REQ들의 change-req 로 함께 처리. -- 라이브 데이터 일일행→영구행 전환은 REQ-064(cutover)에서 다룬다(본 REQ 는 목표 스키마 정의). -- 출처: dcy-020 FR-1, OQ-3, OQ-7. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-1/OQ-3/OQ-7 로부터 최초 생성(Proposed). 영구 슬롯 스키마(자산 키·instance_key·UNIQUE 복합·retired_at, trading_date/entry_count 제거). - -#### Intent -- 익명 풀 daily_slot → 자산 1:1 영구 슬롯 스키마 전환을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 영구 슬롯 스키마/UNIQUE(instance_key, portfolio_id)/계약 가드/slot-scoped 테이블 instance 스코프 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/data-model.md -- Modules: sql/009_*, postgresql.py, domain/slot/models.py -- Tests: test_contract.py, test_migration_and_drift.py - -#### Delivery Trace Delta -- Batches: none (batch 편성 전) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (목표 스키마 정의; 실데이터 전환은 REQ-064) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req → suggest-batch-reqs/draft-batch. REQ-002/003/006 change-req 동반. - -#### Validation Plan -- 마이그레이션 적용 후 스키마/UNIQUE/계약 가드 + drift 테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none (batch 편성 전) / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-027(토대)+BAT-030(컬럼 drop·promote-unique) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Data/req-074_merged-slot-row-schema.md b/docs/srs/Data/req-074_merged-slot-row-schema.md deleted file mode 100644 index 42f8ecb..0000000 --- a/docs/srs/Data/req-074_merged-slot-row-schema.md +++ /dev/null @@ -1,112 +0,0 @@ -# req-074: 슬롯 단일 행 스키마 — 자산·워치독·overlay 흡수 + 필드별 정본 라벨 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent -- `trex.slots.req`(REQ-073) 응답의 슬롯 행 하나가 슬롯 상태·자산 데이터·워치독·overlay 를 모두 운반해 소비자 측 배열 조인(assets[]/watchdog[])을 없앤다. 각 필드의 정본 출처(hub/슬롯·코어/워치독/overlay)를 계약에 라벨해 divergence 시 어느 값을 믿을지 모호하지 않게 한다. (dcy-025 FR-3, OQ-3 (a)) - -## Requirement -- `slots[]` 각 행은 아래 필드를 운반한다(필드별 정본 출처 명시): - - **슬롯/코어 정본**: `slot_id`, `status`(inactive/pending_buy/holding/pending_sell/error), `portfolio_id`, `symbol`, `avg_price`, `limit_price`, `forced_close_at`(+기존 슬롯 행 필드 유지) - - **수량(REQ-062 정합, 둘 다 노출)**: `asset_quantity`(**hub 정본** — 전략 reasoning primary) + `qty`(**슬롯 미러** — 코어 confirmed local). 정상 시 일치하며, 어긋나면 `warned=true` 로 표면화한다. 라이브 브로커 잔고는 행에 포함하지 않는다(청산/reconcile 시점 정본 — REQ-062). - - **현금(hub 정본)**: `cash_balance` + `cash_estimated`(bool, dcy-018 추정 현금 오버레이) - - **워치독(WatchdogStore)**: `watchdog: {expired: bool, last_seen: ISO-8601|null}` nested object — 기존 응답 top-level `watchdog[]` 배열은 폐기(REQ-073). - - **overlay 플래그(직교 축)**: `paused`(PauseStore), `warned`(reconcile/REQ-031·062) — status 와 독립이며 항상 노출(서버측 overlay 필터는 후속). -- strategy 계열 필드(`strategy_id`/`sell_strategy_id`/`strategy_params`/`active_strategy_id`)는 행에 포함하지 않는다(REQ-073, dcy-025 OQ-4 — 후속 discovery 확정 시 additive 재도입). -- `docs/interface-contract.md` 에 각 필드의 정본 출처(hub / 슬롯·코어 / 브로커(행 미포함) / watchdog / overlay)를 표로 명시한다(REQ-075). - -## Acceptance Criteria -1. Given holding 자산 슬롯, When `trex.slots.req` 조회, Then 행에 슬롯 상태(slot_id/status/avg_price/limit_price/forced_close_at)·자산(symbol/portfolio_id/cash_balance/asset_quantity)·`qty` 미러·`watchdog:{expired,last_seen}`·`paused`/`warned` 가 모두 포함된다(별도 배열 조인 불요). -2. Given hub `asset_quantity` 와 슬롯 미러 `qty` 가 어긋난 자산, When 조회, Then 두 값이 그대로 노출되고 `warned=true` 다(조용한 자동 교정·숨김 없음). -3. Given 추정 현금 구간(dcy-018), When 조회, Then `cash_balance` 와 `cash_estimated=true` 가 함께 노출되고 권위값 수렴 시 `cash_estimated=false` 로 돌아온다. -4. Given paused 처리된 holding 자산, When `filter=holding` 조회, Then 행이 반환되며 `paused=true` 플래그로 표시된다(필터는 status, overlay 는 플래그 — 직교). -5. Given 행 스키마, When 키 검사, Then strategy 계열 필드가 없다. - -## Impacted Area -- Docs: docs/interface-contract.md(행 스키마 + 정본 라벨 표 — REQ-075), docs/data-model.md(노출 스키마 참조) -- Modules: trex_core/application/portfolio.py(`slot_to_dict` 확장 — 자산·워치독·overlay 병합), trex_core/application/messaging_handlers.py(`_attach_watchdog` 제거·행 흡수) -- Tests: tests/application/(행 필드·정본·divergence·overlay 플래그) - -## Notes -- 수량 정본 모델은 REQ-062(hub 정본 + 슬롯 미러 + 브로커 ground truth)를 그대로 따른다 — 본 REQ 는 그 정본을 행에 표현·라벨하는 계약이다. -- NFR-2(단일 표면·조인 불필요)는 AC-1 에 흡수. -- 출처: dcy-025 FR-3(OQ-2/3). - -## Change Log - -### CHG-20260615-02 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented, BAT-042). 신규 AC-6(strategy_state 맵+occupancy_seq 행 필드) 구현·검증 완료 — `slot_to_dict` occupancy_seq + 핸들러 strategy_state 조립, `tests/application/test_slots_handler_strategy_state.py`(row 단언). 기존 AC-1~5 불변. 479 passed, ruff clean. Implemented 복귀. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- (change-req, dcy-028/issue #26 C-1) 슬롯 행 스키마에 두 additive 필드를 추가한다: **`strategy_state`**(`{: {version,data,updated_at}}` opaque 맵 — 별도 `slot_strategy_state` 테이블(REQ-079)에서 조립, 미보유 시 omit/`{}`)과 **`occupancy_seq`**(int — 슬롯 행 컬럼, REQ-081 점유 가드). 기존 필드(슬롯/자산/워치독/overlay 정본)·divergence·overlay 동작은 불변. AC-5 의 "strategy 계열 필드 없음"은 strategy_* **reasoning** 필드(strategy_id/sell_strategy_id/params/active)를 가리키며, `strategy_state`(opaque 상태 맵)는 그와 구분되는 신규 additive 필드다. - -#### Intent -- 전략 opaque 상태와 점유 식별자를 슬롯 행에 노출해 read piggyback(C-1)과 write CAS 점유 가드(REQ-080)를 지원. - -#### Acceptance Criteria Delta -- Added: - - AC-6(신규): 행에 `strategy_state` 맵(보유 시)과 `occupancy_seq`(int)가 포함된다. `strategy_state` 는 별도 테이블 조립, `occupancy_seq` 는 슬롯 행 컬럼. strategy_* reasoning 필드 부재 유지(AC-5 정합). -- Updated: - - AC-5: 제외되는 "strategy 계열"을 strategy_* reasoning 필드로 한정하고 `strategy_state`/`occupancy_seq` additive 필드는 허용함을 명확화. -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/interface-contract.md(strategy_state·occupancy_seq 행 필드 + 정본 라벨 — REQ-082), docs/data-model.md -- Modules: trex_core/application/portfolio.py(slot_to_dict — strategy_state 맵·occupancy_seq 병합) -- Tests: tests/application/(행 strategy_state 맵·occupancy_seq 단언) - -#### Delivery Trace Delta -- Batches: BAT-035(dcy-025) 불변 — 신규 AC-6 은 dcy-028 batch 에서 구현·검증. -- Releases: REL-006(REQ-074 released) — 기존 계약 영향 없음(additive). 신규 필드는 dcy-028 batch. - -#### Revalidation Impact -- Reverification Needed: yes (신규 AC-6 만) -- Existing Implementation Invalidated: no (기존 행 필드/divergence/overlay 보존) -- Status Recommendation: revert-to-approved (Implemented → Approved — 신규 additive AC 구현·검증 대기, 선례 REQ-002/006 CHG-20260612) -- Follow-up Action: confirm-req → dcy-028 batch 에서 REQ-073/079/081 과 함께 구현·검증 → confirm-req-implemented - -#### Validation Plan -- slot_to_dict 테스트: 행에 strategy_state 맵(보유/미보유)·occupancy_seq 포함, 기존 필드/divergence/overlay 회귀. - -### CHG-20260611-01 -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary -- dcy-025 FR-3(OQ-3 (a)) 로부터 최초 생성(Proposed). 슬롯 단일 행 스키마(자산·워치독·overlay 흡수 + hub 정본/슬롯 미러 둘 다 노출 + 정본 라벨). - -#### Intent -- 병렬 배열 조인 제거 + 필드별 정본 명확화. - -#### Acceptance Criteria Delta -- Added: - - AC-1~5 신규 -- Updated/Removed: none - -#### Impacted Area Delta -- Docs: interface-contract/data-model -- Modules: portfolio.py(slot_to_dict)/messaging_handlers.py -- Tests: 행 스키마/divergence/overlay - -#### Delivery Trace Delta -- Batches: none(승인 후 draft-batch) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no(신규) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current(Proposed) -- Follow-up Action: confirm-req - -#### Validation Plan -- 신규 batch: 행 필드 단언, qty↔asset_quantity divergence+warned, cash_estimated 수렴, paused holding 직교, strategy 필드 부재. diff --git a/docs/srs/Data/req-079_slot-strategy-state-store.md b/docs/srs/Data/req-079_slot-strategy-state-store.md deleted file mode 100644 index 6d40ba7..0000000 --- a/docs/srs/Data/req-079_slot-strategy-state-store.md +++ /dev/null @@ -1,86 +0,0 @@ -# req-079: Slot strategy-state 저장소 — `slot_strategy_state` 테이블 + StrategyStateStore 포트 + occupancy_seq·version CAS - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent -- 전략 데몬(trex-tsl 등)이 슬롯 점유 동안 누적하는 opaque 상태(예: TSL peak)를 슬롯 수명에 묶어 server-side 영속화할 저장소를 신설한다. trex-core 는 값을 해석하지 않고(opaque) 저장/조회/CAS 만 책임진다. 슬롯이 source of truth 이자 GC 가 되어 데몬은 cleanup 책임이 없다. (dcy-028 FR-2/OQ-1/OQ-4, issue #26 C-2) - -## Requirement -- 별도 테이블 `slot_strategy_state` 를 신설한다(슬롯 행 JSONB 미사용 — dcy-028 OQ-4): 복합 PK `(slot_id, strategy_id)`, 컬럼 `version int`, `data text`, `updated_at timestamptz`, 그리고 점유 식별 `occupancy_seq int`(REQ-081 가드 참조). strategy 당 1행. -- `StrategyStateStore` 포트(Protocol)를 정의하고 PG 구현과 in-memory 구현(복합키 dict)을 함께 제공한다(paper/테스트 무중단). -- CAS upsert 의미: - 1. occupancy_seq 일치 + version 일치 → commit, version+1 반환. - 2. occupancy 불일치 → `occupancy_changed` (저장 안 함). version 불일치 → `version_conflict` (저장 안 함). 슬롯 inactive/미존재 → `slot_inactive` (저장 안 함). - 3. 최초(엔트리 없음 + expected_version=0 + occupancy 일치) → version=1 생성. - 4. 동일 입력(같은 expected_version) 재적용은 부작용 없음(멱등). -- `data` 는 opaque UTF-8 문자열로 저장한다. core 검증은 (1) 유효 UTF-8 + (2) byte 크기 상한뿐 — JSON/base64 를 강제하지 않는다. 상한은 2단(기본 1~2KB / hard ceiling 4KB byte). 초과 시 `state_too_large` 로 저장 거부(REQ-080 RPC 가 reason 운반). -- 조회 API 는 slot_id 집합으로 batched 조회를 지원해 C-1 read(REQ-073/074) 가 추가 왕복 없이 행별 맵으로 조립할 수 있어야 한다. - -## Acceptance Criteria -1. Given 비어 있는 `(slot_id, strategy_id)`, When expected_version=0 + 일치 occupancy_seq 로 set, Then version=1 로 생성되고 같은 키 조회 시 data·version=1 이 반환된다. -2. Given version=N 인 엔트리, When expected_version=N + 일치 occupancy_seq 로 set, Then commit + version=N+1. When expected_version≠N 으로 set, Then `version_conflict` 로 저장되지 않고 기존 값이 유지된다. -3. Given 슬롯의 현재 occupancy_seq=k, When expected_occupancy_seq≠k 로 set, Then `occupancy_changed` 로 저장되지 않는다. Given inactive/미존재 슬롯, When set, Then `slot_inactive` 로 저장되지 않는다. -4. Given 크기 상한 초과 `data`, When set, Then `state_too_large` 로 저장 거부된다. Given 유효 UTF-8 + 상한 이내, Then core 가 내용 해석 없이 그대로 저장한다. -5. Given 여러 slot_id, When batched 조회, Then 각 slot_id 별 `{strategy_id: {version,data,updated_at}}` 맵으로 그룹되어 반환된다(C-1 조립 입력). -6. Given PG 구현과 in-memory 구현, When 동일 시나리오 테스트, Then 두 구현이 동일한 CAS 의미를 보인다. - -## Impacted Area -- Docs: docs/data-model.md(`slot_strategy_state` 스키마), docs/project-structure.md(저장소 구조 — REQ-082) -- Modules: trex_core/adapters/persistence/postgresql.py(PG StrategyStateStore + 마이그레이션 SQL), trex_core/domain/slot/in_memory_repository.py 또는 신규 모듈(in-memory), 신규 포트 정의(예: trex_core/application/ports.py 또는 strategy_state 모듈) -- Tests: tests/adapters/persistence/(PG CAS 게이트), tests/(in-memory CAS 6 케이스 — commit/version_conflict/occupancy_changed/slot_inactive/state_too_large/first-create + 멱등) - -## Notes -- occupancy_seq 컬럼은 REQ-081(슬롯 점유 가드)과 짝을 이룬다 — 본 REQ 는 저장·CAS, REQ-081 은 슬롯 측 occupancy_seq 부여·bump·close 삭제를 소유한다. -- write RPC 계약(요청/응답 shape, reject reason 운반)은 REQ-080 이 소유한다. 본 REQ 는 저장 의미. -- 단일 writer 전제(해당 slot·strategy 데몬 1개). CAS 는 재시도/중복·점유전환 안전용이지 다중 writer 동시성 제어가 아니다. -- 출처: dcy-028 FR-2, OQ-1(인코딩/상한), OQ-4(별도 테이블+맵+slot_id 키), R-2/R-3. - -## Change Log - -### CHG-20260615-03 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042(release-candidate) verification — `slot_strategy_state` 테이블(마이그 015) + `StrategyStateStore`/`InMemoryStrategyStateStore`/`PgStrategyStateStore` + occupancy_seq·version CAS + opaque UTF-8·size cap. AC-1~6 evidence: `tests/application/test_strategy_state_store.py`(CAS 6 케이스+ABA+batched+delete) + `test_postgresql_repository.py::test_strategy_state_store_pg`(PG 게이트). 479 passed, ruff clean. Blocking Issues 없음. - -### CHG-20260615-02 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(6개) 충족, source dcy-028(confirmed, Open OQ 0건, OQ-1/OQ-4 정합) 일치. blocker 없음. REQ-080/081/082 + REQ-073/074 CHG 와 동일 batch 편성 예정. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- dcy-028 FR-2/OQ-1/OQ-4 로부터 최초 생성(Proposed). slot_strategy_state 저장소 + StrategyStateStore 포트 + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap. - -#### Intent -- 전략 opaque 상태를 슬롯 수명에 묶어 server-side 영속화 — 데몬 재시작/장애에도 상태 보존, orphan-cleanup 제거. - -#### Acceptance Criteria Delta -- Added: first-create/version CAS/occupancy CAS/size cap/batched 조회/PG·in-memory 동등 6개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: data-model.md, project-structure.md(REQ-082) -- Modules: postgresql.py, in-memory store, 신규 포트 -- Tests: PG CAS, in-memory CAS 6 케이스 - -#### Delivery Trace Delta -- Batches: none (편성 예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-080/081/082 + REQ-073/074 CHG 와 동일 batch) - -#### Validation Plan -- in-memory CAS 6 케이스 + PG 게이트 테스트로 저장 의미 검증. batched 조회는 C-1 조립 테스트(REQ-073/074)와 연계. diff --git a/docs/srs/Data/req-081_slot-occupancy-seq-guard-and-close-delete.md b/docs/srs/Data/req-081_slot-occupancy-seq-guard-and-close-delete.md deleted file mode 100644 index b9b5fb0..0000000 --- a/docs/srs/Data/req-081_slot-occupancy-seq-guard-and-close-delete.md +++ /dev/null @@ -1,81 +0,0 @@ -# req-081: 슬롯 occupancy_seq 정합성 가드 + close 시 strategy_state 삭제 불변식 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: project maintainers - -## Intent -- 영구 슬롯이 재사용될 때(점유 전환) 옛 점유가 계산한 strategy_state 가 새 점유에 바인딩되는 ABA 누수를 구조적으로 차단하고, 슬롯 close 시 strategy_state 를 자동 삭제해 데몬 cleanup 책임을 없앤다. occupancy_seq 경량 가드(컬럼 1개)로 full 3계층 분할 없이 정합성 임계부만 선취한다. (dcy-028 FR-2b/FR-3, R-1, issue #26 occupancy race #4705990067) - -## Requirement -- `Slot` 도메인에 monotonic `occupancy_seq:int` 필드를 추가하고 `EXPECTED_SLOT_FIELDS` 계약과 PG 스키마(daily_slot, 레거시 명 — 실제 영구 자산 슬롯)에 마이그레이션으로 반영한다. 기본값 0(또는 점유 없음 표시). -- 새 점유 시작 전이(`inactive → pending_buy`)에서 `occupancy_seq` 를 ++ 한다. 이 값은 한 점유 구간 동안 안정적이고 직전 점유와 달라야 한다(monotonic, 실패한 매수 롤백으로 번호가 건너뛰어도 무방). -- `occupancy_seq` 를 슬롯 행에 노출해 C-1 read(REQ-073/074)·write CAS(REQ-079/080)가 점유 식별에 사용하게 한다. -- 슬롯이 어떤 경로로든 close(`→INACTIVE`: 전략 매도 체결/safety-sell/forced-close/operator)되면 단일 전이 지점(`service.py` INACTIVE 분기, position 필드 clear 와 동일 위치)에서 해당 slot_id 의 모든 strategy_state 를 삭제한다(REQ-079 store). 행 삭제가 아니라 영구 슬롯 `→INACTIVE` 전이 훅이다(dcy-020 정합). -- 재사용 슬롯은 새 occupancy_seq + 빈 strategy_state(version 없음)로 시작한다. - -## Acceptance Criteria -1. Given `Slot` 도메인, When 필드 계약 검사, Then `occupancy_seq` 가 `EXPECTED_SLOT_FIELDS` 에 포함되고 PG daily_slot 에 컬럼이 존재한다. -2. Given inactive 슬롯, When `inactive→pending_buy` 전이, Then `occupancy_seq` 가 직전보다 증가하고 해당 점유 구간(pending_buy→holding→pending_sell) 동안 동일 값으로 유지된다. -3. Given holding 슬롯의 strategy_state 존재, When 슬롯이 임의 close 경로로 `→INACTIVE`, Then 해당 slot_id 의 모든 strategy_state 가 삭제된다(전략 매도/safety-sell/forced-close 모두 커버). -4. Given close 후 재매수된 슬롯, When 조회, Then 새 occupancy_seq + 빈 strategy_state(version 없음)로 시작한다. -5. Given 점유 전환 ABA 시나리오(옛 점유 기준 occupancy_seq 로 write), When set, Then REQ-080 가 `occupancy_changed` 로 reject 해 stale 상태가 새 점유에 바인딩되지 않는다(R-1 차단). - -## Impacted Area -- Docs: docs/data-model.md(occupancy_seq 필드), docs/runtime-flows.md(점유 가드·close 삭제 흐름 — REQ-082) -- Modules: trex_core/domain/slot/models.py(`occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS), trex_core/domain/slot/service.py(inactive→pending_buy bump + →INACTIVE strategy_state 삭제 훅), trex_core/adapters/persistence/postgresql.py(컬럼 + 마이그레이션 SQL), trex_core/application/portfolio.py(slot_to_dict occupancy_seq 노출) -- Tests: tests/domain/slot/(occupancy_seq bump·계약 가드), tests/(각 close 경로 후 strategy_state 부재 + ABA reject S-4) - -## Notes -- 저장·CAS 의미는 REQ-079, write RPC 는 REQ-080 이 소유한다 — 본 REQ 는 슬롯 측 occupancy_seq 부여·bump 와 close 삭제 훅(lifecycle)을 소유한다. -- full 3계층(별도 occupancy 테이블 + strategy_state 를 occupancy_id 키로 전환)은 dcy-028 Out of Scope — 후속 "영구 슬롯 구조 정합" Discovery. 본 REQ 는 컬럼 1개 경량 가드. -- 중장기 영구 슬롯은 점유 회전이 드물어 full 분할이 과하고, intraday(trex-tsl Mode A) 정합성은 본 가드로 충족(dcy-028 OQ-4). -- 출처: dcy-028 FR-2b(C-2b)/FR-3(C-3), R-1. - -## Change Log - -### CHG-20260615-03 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042 verification — `Slot.occupancy_seq`(+EXPECTED_SLOT_FIELDS, 마이그 014) + `inactive→pending_buy` bump + `→INACTIVE` strategy_state delete 훅(service). AC-1~5 evidence: `tests/domain/slot/test_occupancy_seq_and_close_delete.py`(bump/안정성/모든 close 경로 삭제/재사용 빈 시작) + `test_strategy_state_store.py::TestAbaGuard`(S-4 ABA reject) + `test_contract`(계약 가드). 479 passed, ruff clean. Blocking Issues 없음. - -### CHG-20260615-02 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(5개) 충족, source dcy-028(FR-2b/FR-3, R-1 완화) 정합. occupancy_seq 경량 가드로 점유 ABA 구조적 차단. blocker 없음. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- dcy-028 FR-2b/FR-3 로부터 최초 생성(Proposed). occupancy_seq 슬롯 필드+bump+노출 + close 시 strategy_state 삭제 불변식. - -#### Intent -- 점유 전환 ABA 누수(R-1) 구조적 차단 + close cleanup 자동화. - -#### Acceptance Criteria Delta -- Added: 필드 계약/bump 안정성/close 삭제 모든 경로/재사용 빈 시작/ABA reject 5개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: data-model.md, runtime-flows.md(REQ-082) -- Modules: models.py, service.py, postgresql.py, portfolio.py -- Tests: occupancy_seq bump·계약, close 경로 삭제, ABA reject - -#### Delivery Trace Delta -- Batches: none (편성 예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-079/080/082 + REQ-073/074 CHG 동일 batch) - -#### Validation Plan -- occupancy_seq bump·계약 가드 테스트 + 각 close 경로 후 strategy_state 부재 테스트 + ABA reject 테스트(S-4)로 검증. diff --git a/docs/srs/Data/req-102_reconcile-warning-durable-store.md b/docs/srs/Data/req-102_reconcile-warning-durable-store.md deleted file mode 100644 index 7098842..0000000 --- a/docs/srs/Data/req-102_reconcile-warning-durable-store.md +++ /dev/null @@ -1,133 +0,0 @@ -# req-102: reconcile-warning durable store — slot_id/pid 키 별도 테이블 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: trex-core-dev - -## Intent -- boot/일일 reconcile에서 슬롯↔브로커(B) 또는 원장↔슬롯(C) 불일치가 탐지될 때 해당 슬롯을 차단하는 **durable한 reconcile-warning 상태**를 저장할 별도 PostgreSQL 테이블을 도입한다. -- OQ-1(dcy-034) 확정: pause_store와 동형 설계를 선례로 삼아 SlotState enum·전이표·slots RPC wire 계약을 변경하지 않는 비침습 접근을 취한다(NFR-4). -- 재시작 후에도 차단 상태가 유지되어야 한다(NFR-1). - -## Requirement -- 시스템은 `slot_reconcile_warning` 테이블(PK: slot_id)을 PostgreSQL에 생성하고, `slot_id`, `portfolio_id`, `case_code`(B 또는 C), `actor`, `reason`, `created_at`, `updated_at` 컬럼을 가져야 한다. -- `ReconcileWarningStore`(application port) 및 `PostgreSQLReconcileWarningStore`(adapter)는 pause_store와 동형 인터페이스(`set_warned`, `clear_warned`, `list_warned`, `is_warned`)를 제공해야 한다. -- 스토어는 slot_id를 PK로 삼고 동일 slot_id의 중복 삽입 시 `ON CONFLICT DO UPDATE`로 갱신해야 한다. -- DB 마이그레이션 스크립트로 테이블 생성을 관리해야 한다. - -## Acceptance Criteria -1. Given `slot_reconcile_warning` 테이블이 없는 DB에서 마이그레이션을 실행하면, When 마이그레이션이 완료되면, Then 테이블이 존재하고 PK(slot_id), case_code, portfolio_id, actor, reason, created_at, updated_at 컬럼이 모두 존재한다. -2. Given `ReconcileWarningStore.set_warned(slot_id, portfolio_id, case_code, actor, reason)` 호출 후, When 데몬이 재시작되어도, Then `is_warned(slot_id)` = True가 유지된다(durable). -3. Given 동일 slot_id에 대해 `set_warned`를 두 번 호출하면, When DB를 조회하면, Then 행이 1개이고 최신 값으로 갱신되어 있다(ON CONFLICT DO UPDATE). -4. Given `clear_warned(slot_id)` 호출 후, When `is_warned(slot_id)`를 조회하면, Then False를 반환한다. -5. Given `list_warned()` 호출 시, When 복수의 warned slot이 존재하면, Then 해당 slot_id 집합이 반환된다. -6. Given `SlotState` enum 및 `ALLOWED_TRANSITIONS`, When 스토어를 추가해도, Then `domain/slot/state.py`에 새 SlotState 또는 전이 추가가 없다(wire 계약 불변, NFR-4). - -## Impacted Area -- Docs: `docs/srs/index.md` (Next Requirement ID, Register 갱신) -- Modules: - - `trex_core/adapters/portfolio/reconcile_warning_store.py` (신규 — pause_store.py 동형) - - `trex_core/application/ports.py` 또는 `application/portfolio.py` (ReconcileWarningStore 포트 정의) - - `trex_core/adapters/persistence/migrations/` (신규 마이그레이션) - - `trex_core/domain/slot/state.py` (변경 없음 — 불변 확인) -- Tests: - - `tests/application/` 또는 `tests/adapters/` — ReconcileWarningStore 단위 테스트 (set/clear/list/is_warned, durable 검증, ON CONFLICT 검증) - - 기존 slot state machine 테스트 회귀 확인 - -## Notes -- pause_store 선례(`adapters/portfolio/pause_store.py`, `application/portfolio.py:PauseStore`)를 동형으로 따른다. -- 포트 정의 위치(application/portfolio.py 내 Protocol 클래스 vs 별도 파일)는 batch design에서 결정한다. -- `case_code` 값: `B`(슬롯 보유·브로커 수량 불일치), `C`(원장↔슬롯 수량 불일치). -- dcy-034 FR-5, NFR-1, NFR-4 대응. - -## Change Log - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-050(PR #31, ced7eda) + BAT-051(PR #32, 07b393d) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. 683 passed / 47 skipped / 0 fail. slot_reconcile_warning 테이블 마이그레이션, ReconcileWarningStore(in-memory/PG) 신규 구현, 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 (기존 AC 그대로 충족) - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-050 (core-A) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: M3 배포 시 PG durable 검증. - -#### Validation Plan -- BAT-050 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-102 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). - -#### Intent -- 팀장 confirm-req로 상태 승인. 내용 변경 없음(OQ-1 반영 초안 그대로 승인). - -#### Acceptance Criteria Delta -- Added: 없음. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- 없음. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 후 구현. - -#### Validation Plan -- ReconcileWarningStore 단위 테스트 pass / DB 마이그레이션 스크립트 적용 검증 / SlotState 불변 회귀 테스트 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-102 신규 생성 (Proposed) — dcy-034 FR-5 대응, draft-req 단계. - -#### Intent -- dcy-034 OQ-1 확정(pause_store 동형 별도 durable store)을 반영해 reconcile-warning 저장 능력 도입. - -#### Acceptance Criteria Delta -- Added: AC-1~6 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: index.md 갱신. -- Modules: reconcile_warning_store.py 신규, 마이그레이션 신규. -- Tests: ReconcileWarningStore 단위 테스트 신규. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성. - -#### Validation Plan -- ReconcileWarningStore 단위 테스트 pass / DB 마이그레이션 스크립트 적용 검증 / SlotState 불변 회귀 테스트 pass. diff --git a/docs/srs/Data/req-121_managed-non-holding-divergence-classification-query.md b/docs/srs/Data/req-121_managed-non-holding-divergence-classification-query.md deleted file mode 100644 index c6d769d..0000000 --- a/docs/srs/Data/req-121_managed-non-holding-divergence-classification-query.md +++ /dev/null @@ -1,129 +0,0 @@ -# REQ-121: managed non-holding divergence 분류와 slot 존재 조회 계약 - -- Status: Implemented -- Type: Data -- Priority: High -- Owner: trex-core-dev - -## Intent -- broker-held symbol을 holding slot만으로 비교하면 동일 instance에 `inactive` 또는 `error` managed slot이 존재하는 경우도 true unmanaged position으로 잘못 분류된다. -- holding 수량 대조의 기존 의미론을 보존하면서, unmatched broker holding의 managed slot 존재 여부를 repository parity로 확인해 안전한 triage classification을 제공한다. - -## Requirement -- 시스템은 `reconcile_core_holdings()`에서 broker-held symbol이 holding slot에 매칭되지 않을 때 동일 instance의 관리 slot 존재 여부를 조회해야 한다. -- slot이 전혀 없으면 기존 `unmanaged_position` 분류를 유지하고, slot은 존재하지만 non-holding 상태이면 `managed_non_holding_divergence`로 별도 분류해야 한다. -- 이 분류는 slot 상태 전이, warning 자동 해제, 자동 매매, 자동 청산 또는 unmanaged holding의 자동 slot 생성을 유발하지 않아야 하며, 상태 수렴은 기존 승인 gate의 검증된 `sync` 경로에만 맡겨야 한다. -- SlotRepository Protocol과 PostgreSQL/InMemory 구현은 instance-scoped managed-slot existence query에 대해 동등한 결과를 제공해야 한다. - -## Acceptance Criteria -1. Given broker-held symbol에 대해 대상 instance에 어떤 slot도 없음, When `reconcile_core_holdings()`를 실행하면, Then `unmanaged_position` decision code와 기존 safe warning semantics가 유지된다. -2. Given broker-held symbol에 대해 같은 instance의 slot이 `inactive` 또는 `error`이고 holding slot은 없음, When `reconcile_core_holdings()`를 실행하면, Then `managed_non_holding_divergence` decision code와 `slot_id`, `portfolio_id`, `symbol`, `status`, `instance_key`의 구조화·비밀 없는 triage context가 남는다. -3. Given matching holding slot 또는 holding qty mismatch, When `reconcile_core_holdings()`를 실행하면, Then 각각 기존 match 및 mismatch path가 유지되고 non-holding divergence로 재분류되지 않는다. -4. Given AC-1~3 fixture, When reconciliation 후 slot 및 warning persistence를 검사하면, Then managed non-holding 분류만으로 slot state write, warning 자동 해제, 자동 매매, 자동 청산 또는 unmanaged slot 생성이 발생하지 않는다. -5. Given PostgreSQL 및 InMemory SlotRepository, When 같은 instance/symbol/portfolio fixture로 managed-slot existence query를 실행하면, Then 두 구현이 같은 존재 여부와 instance isolation 결과를 반환한다. -6. Given 다른 instance에만 같은 symbol 또는 portfolio_id slot이 존재함, When reconciliation을 실행하면, Then 그것은 대상 instance의 managed slot으로 취급되지 않는다. - -## Impacted Area -- Docs: `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, `docs/srs/Integration/req-103_blocking-reconcile-b-c-cases.md`, `docs/srs/Interface/req-105_sync-cli-command.md` -- Modules: `trex_core/application/reconciliation.py`, `trex_core/domain/slot/repository.py`, `trex_core/adapters/persistence/postgresql.py`, `trex_core/domain/slot/in_memory_repository.py` -- Tests: reconciliation 4-case regression matrix; PostgreSQL/InMemory repository parity; instance isolation; no-auto-write/no-auto-remediation assertions - -## Notes -- Source Discovery: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (FR-4~5, NFR-2~4; GitHub #41). -- Existing `list_holding_slots(instance_key)` is holding-only evidence; this REQ adds the separate managed-slot existence decision needed only after an unmatched broker holding. -- This is additive classification. `sync --live` target selection and execution remain release-gated and out of scope. - -## Change Log - -### CHG-20260714-01 -- Date: 2026-07-14 -- Author: delivery-runner - -#### Change Summary -- dcy-037 #41의 managed non-holding divergence와 repository query parity를 Proposed REQ로 초안화했다. - -#### Intent -- 진짜 unmanaged broker holding과 이미 관리되는 non-holding slot divergence를 혼동하지 않도록 한다. - -#### Acceptance Criteria Delta -- Added: - - 4-case reconciliation matrix, repository parity/instance isolation, additive no-auto-remediation constraints. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - reconcile decision 및 operator triage baseline. -- Modules: - - reconciliation 및 slot repository protocol/implementations. -- Tests: - - application classification 및 DB/in-memory parity regression. - -#### Delivery Trace Delta -- Batches: - - none (draft only; dcy-037 emergency scope의 향후 입력). -- Releases: - - none (production read/write 또는 live sync 실행 없음). - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 전 decision-code naming과 existing warning/alert inventory compatibility를 검토한다. - -#### Validation Plan -- isolated four-case fixture와 PostgreSQL/InMemory parity suite에서 classification, structured context, instance scope 및 no-write invariant를 검증한다. - -### CHG-20260714-02 -- Date: 2026-07-14 -- Author: Justin (TREX lead) - -#### Change Summary -- Proposed에서 Approved로 승인했다. - -#### Intent -- dcy-037 #41의 additive classification을 기존 holding mismatch와 unmanaged semantics를 보존하는 delivery input으로 확정한다. - -#### Acceptance Criteria Delta -- Added: - - 없음. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - 없음. -- Modules: - - 없음. -- Tests: - - 없음. - -#### Delivery Trace Delta -- Batches: - - dcy-037의 #39/#40/#41 단일 emergency batch (아직 미생성). -- Releases: - - none; `sync --live`는 별도 release approval gate 유지. - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs에서 REQ-119/120과 함께 emergency batch로 편성한다. - -#### Validation Plan -- batch verification과 독립 QC에서 4-case matrix, repository parity, instance isolation 및 no-auto-remediation AC를 재검증한다. - -### CHG-20260715-03 -- Date: 2026-07-15 -- Author: TREX lead - -#### Change Summary -- Approved → Implemented (`confirm-req-implemented`). - -#### Delivery Trace -- BAT-057 verification confirmed; PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`. -- Independent QC: PASS-WITH-NOTES, acceptance blocker 0. Fake-driver PostgreSQL adapter/InMemory parity is accepted AC evidence; real PostgreSQL engine integration remains an environmental coverage note. diff --git a/docs/srs/Documentation/req-004_baseline-doc-alignment.md b/docs/srs/Documentation/req-004_baseline-doc-alignment.md deleted file mode 100644 index 04d287b..0000000 --- a/docs/srs/Documentation/req-004_baseline-doc-alignment.md +++ /dev/null @@ -1,152 +0,0 @@ -# req-004: baseline 문서 planned→실제 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- slot 순수 도메인 이식 이후, baseline 문서의 planned 표기를 실제 코드 구조와 일치시켜 Discovery/REQ/Batch 가 참조하는 cross-cutting baseline 의 정확성을 유지한다. - -## Requirement -- 이식 완료 후 `docs/project-structure.md` 와 `docs/runtime-flows.md` 의 planned 표기(디렉터리 트리, 모듈 책임, `(planned)`/미구현 표기)를 실제 `trex_core/` 구조와 일치하도록 갱신한다. - -## Acceptance Criteria -1. Given 이식 완료, When `docs/project-structure.md` 를 확인, Then `(planned)` 표시가 실제 생성 구조로 갱신되고 트리에 `trex_core/domain/slot` 이 실재 구조로 반영된다. -2. Given 이식 완료, When `docs/runtime-flows.md` 를 확인, Then in-memory 기반 도메인 흐름 설명이 실제 코드와 일치하고 "미구현" 성격 표기가 정정된다. -3. Given baseline 문서 전체, When 네임스페이스 표기를 확인, Then `trex_core` 로 일관되며 `app/` 잔존이 없다(정정 이력을 남기는 Change Log 항목은 예외). - -## Impacted Area -- Docs: docs/project-structure.md, docs/runtime-flows.md -- Modules: 없음(문서 전용) -- Tests: 없음 - -## Notes -- dcy-001 FR-4. REQ-002 이식 완료 후 수행해야 의미가 있다(순서 의존). -- `docs/data-model.md`·`docs/interface-contract.md` 는 이미 도메인 계약 기준으로 작성돼 큰 변경이 없을 수 있으나, 불일치 발견 시 함께 정정한다. -- 출처: dcy-001 FR-4. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-001 FR-4 로부터 최초 생성(Proposed). - -#### Intent -- 이식 후 baseline 문서 정합 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - project-structure 트리/Status 갱신, runtime-flows 흐름 정합, 네임스페이스 일관성 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md, docs/runtime-flows.md -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-002 이식 완료 후 batch verification 단계에서 수행 - -#### Validation Plan -- baseline 문서와 실제 `trex_core/` 트리 비교 + `app/` 잔존 grep 0(정정 이력 제외)으로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-001(confirmed, handoff 승인)과 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-002 이식 완료 후 batch verification 단계에서 수행 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-001 release-candidate). - -#### Intent -- BAT-001 verification.md 의 evidence(project-structure/runtime-flows 정합, `app/` 잔존 0)로 AC1~AC3 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented), docs/project-structure.md, docs/runtime-flows.md -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-001 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-001 verification evidence 재참조로 충분. diff --git a/docs/srs/Documentation/req-009_daemon-api-baseline-alignment.md b/docs/srs/Documentation/req-009_daemon-api-baseline-alignment.md deleted file mode 100644 index f584049..0000000 --- a/docs/srs/Documentation/req-009_daemon-api-baseline-alignment.md +++ /dev/null @@ -1,228 +0,0 @@ -# req-009: 데몬/API/영속화 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- 데몬화·HTTP API·영속화 레이어가 추가된 뒤 baseline 문서를 실제 구조/흐름/계약과 일치시켜 cross-cutting 참조의 정확성을 유지한다. - -## Requirement -- `docs/interface-contract.md` 에 HTTP API 계약(/v1 엔드포인트 6종)을 추가하고, `docs/runtime-flows.md` 에 데몬 lifecycle·intent 요청 흐름을 추가하며, `docs/project-structure.md`(API/persistence/런타임 레이어)와 `docs/data-model.md`(intent_log + 영속화 테이블)를 갱신한다. - -## Acceptance Criteria -1. Given API 구현 완료, When `docs/interface-contract.md` 확인, Then /v1 엔드포인트 6종의 입력/출력/에러 계약이 실제 구현과 일치한다. -2. Given 데몬/영속화 구현 완료, When `docs/runtime-flows.md`·`docs/project-structure.md` 확인, Then 데몬 흐름과 레이어 구조(domain/application/adapters)가 실제와 일치한다. -3. Given intent_log + 영속화 테이블, When `docs/data-model.md` 확인, Then 신규 엔티티/테이블이 반영된다. - -## Impacted Area -- Docs: docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md, docs/data-model.md -- Modules: 없음(문서 전용) -- Tests: 없음 - -## Notes -- dcy-002 FR-6. REQ-005~008 구현 완료 후 수행해야 의미가 있다(순서 의존). -- 출처: dcy-002 FR-6. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-002 FR-6 로부터 최초 생성(Proposed). - -#### Intent -- 데몬/API/영속화 baseline 정합 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - interface-contract, runtime-flows/project-structure, data-model 정합 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md, docs/data-model.md -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-005~008 구현 후 batch verification 단계에서 수행 - -#### Validation Plan -- baseline 문서와 실제 구현(엔드포인트/레이어/테이블) 대조로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-002(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-005~008 구현 후 batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-002 release-candidate). - -#### Intent -- BAT-002 verification.md 의 acceptance mapping + evidence(pytest 74 pass / PG 통합 / 데몬 E2E)로 모든 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-002 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-002 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-014) baseline 문서를 trading_date 타임존 분리·일일 preallocation 흐름에 맞춰 정합: `docs/runtime-flows.md`(데몬 lifecycle TOML 키·8 스레드·`일일 슬롯 preallocation` Flow 신규), `docs/data-model.md`(trading_date=설정 TZ 기준 / timestamp=UTC 구분). BAT-018 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(preallocation_scheduler/daemon/config) 변경과 baseline 문서를 일치(S-1~S-4)시켜 운영자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - runtime-flows 데몬 lifecycle/일일 preallocation, data-model trading_date TZ vs UTC timestamp 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/runtime-flows.md, docs/data-model.md - -#### Delivery Trace Delta - -- Batches: BAT-018 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-018 정합) -- Follow-up Action: confirm-req-implemented(BAT-018). - -#### Validation Plan - -- BAT-018: 코드-문서 diff 대조. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-019/issue #18) baseline 문서 정합: `docs/data-model.md` 에 daily_slot `entry_count` 필드(slot-scoped, daily_symbol_entries 와 구분)·Lifecycle `closed→pending_buy`·State Rules(전이 패밀리 8 base 포함, closed→pending_buy clean-slate) 반영, `docs/runtime-flows.md` 의 buy admission 흐름을 `find_allocatable_slot`(idle∪closed, entry_count 정렬)·closed 직접 재할당으로, transit_slot 흐름에 `→pending_buy 시 entry_count +1` 단계 반영. BAT-025 검증 완료 → Implemented 유지. - -#### Intent - -- entry_count + closed 재할당 도메인 변경 후 baseline 문서가 실제 흐름/데이터 모델과 일치하도록 유지. - -#### Acceptance Criteria Delta - -- Updated: - - AC-2: `docs/runtime-flows.md` admission 슬롯 선택 흐름이 idle∪closed 후보 selector·closed→pending_buy 재할당과 일치. - - AC-3: `docs/data-model.md` 에 daily_slot `entry_count` 필드 + lifecycle/transition 규칙이 반영. - -#### Impacted Area Delta - -- Docs: docs/data-model.md(daily_slot entry_count/lifecycle/state rules), docs/runtime-flows.md(buy admission selector + transit_slot entry_count step) - -#### Delivery Trace Delta - -- Batches: BAT-025 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-025 에서 완료 — 코드-문서 diff 대조) -- Existing Implementation Invalidated: no (문서 추가 정합 — 기존 서술 보존) -- Status Recommendation: Implemented (change-req → BAT-025 재검증) -- Follow-up Action: confirm-req-implemented(BAT-025). - -#### Validation Plan - -- BAT-025: 코드(state.py/service.py/intents.py/postgresql.py) ↔ 문서(data-model/runtime-flows) diff 대조. diff --git a/docs/srs/Documentation/req-015_broker-execution-baseline-alignment.md b/docs/srs/Documentation/req-015_broker-execution-baseline-alignment.md deleted file mode 100644 index 41664b4..0000000 --- a/docs/srs/Documentation/req-015_broker-execution-baseline-alignment.md +++ /dev/null @@ -1,225 +0,0 @@ -# req-015: 브로커/집행 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- 브로커 세션·주문 집행·체결·reconciliation 레이어가 추가된 뒤 baseline 문서를 실제 구조/흐름/계약과 일치시킨다. - -## Requirement -- `docs/runtime-flows.md`(집행 루프·체결·reconciliation 흐름), `docs/project-structure.md`(broker adapter·execution 레이어), `docs/interface-contract.md`(BrokerPort 경계·KIS), `docs/data-model.md`(broker_order_id 를 실행큐/멱등 가드로 쓰는 의미)를 갱신한다. - -## Acceptance Criteria -1. Given 집행 구현 완료, When runtime-flows 확인, Then 집행 루프(claim→송신→poll→전이)·reconciliation 흐름이 실제와 일치한다. -2. Given project-structure 확인, Then broker adapter(paper/KIS)·execution 레이어가 반영된다. -3. Given interface-contract/data-model 확인, Then BrokerPort 경계와 broker_order_id 의 실행큐/멱등 역할이 반영된다. - -## Impacted Area -- Docs: docs/runtime-flows.md, docs/project-structure.md, docs/interface-contract.md, docs/data-model.md -- Modules: 없음 -- Tests: 없음 - -## Notes -- dcy-003 FR-6. REQ-011~014 구현 완료 후 수행(순서 의존). -- 출처: dcy-003 FR-6. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-003 FR-6 로부터 최초 생성(Proposed). - -#### Intent -- 브로커/집행 baseline 정합 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - runtime-flows, project-structure, interface-contract/data-model 정합 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/project-structure.md, docs/interface-contract.md, docs/data-model.md -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-011~014 구현 후 batch verification 단계에서 수행 - -#### Validation Plan -- baseline 문서와 실제 구현(레이어/흐름/계약) 대조로 검증. -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate). - -#### Intent -- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-004 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~004) - -#### Validation Plan -- BAT-004 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012) baseline 문서를 새 매도 가격 정책·`limit_price` 생애주기·컬럼 rename 에 맞춰 정합: `docs/runtime-flows.md`(dispatch 가격 결정 = slot.limit_price 단일 필드 + 생애주기 set/clear), `docs/interface-contract.md`(sell payload optional limit_price + ORD_DVSN 매핑), `docs/data-model.md`(슬롯 limit_price 컬럼). BAT-016 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(execution/intents/slot/persistence) 변경과 baseline 문서를 일치(S-4)시켜 운영자/전략 개발자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - runtime-flows/interface-contract/data-model 의 매도 가격·limit_price 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/runtime-flows.md, docs/interface-contract.md, docs/data-model.md - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-016 정합) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: 코드-문서 diff 대조(S-4). - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013) baseline 문서를 외부 취소 감지·부분 체결 회계·pending stale 워치독에 맞춰 정합: `docs/interface-contract.md`(FillStatus.cancelled/is_partial_cancel + state별 집행 처리 + KIS cncl_yn 매핑), `docs/runtime-flows.md`(poll 단계 외부 취소 → 롤백/부분 회계 + Alert-only stale 워치독). BAT-017 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(kis/broker_port/execution/config) 변경과 baseline 문서를 일치(S 기준)시켜 운영자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - interface-contract/runtime-flows 의 취소 감지·부분 회계·stale 워치독 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/interface-contract.md, docs/runtime-flows.md - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-017 정합) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 코드-문서 diff 대조. diff --git a/docs/srs/Documentation/req-020_ledger-baseline-alignment.md b/docs/srs/Documentation/req-020_ledger-baseline-alignment.md deleted file mode 100644 index ada8f3a..0000000 --- a/docs/srs/Documentation/req-020_ledger-baseline-alignment.md +++ /dev/null @@ -1,185 +0,0 @@ -# req-020: ledger 레이어 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- portfolio-hub ledger 통합(REQ-017~019)이 도입하는 LedgerPort, 체결→원장 기록 흐름, ledger outbox, 원장 데이터 필드를 baseline 문서에 반영해 코드와 문서의 정합을 유지한다. - -## Requirement -- 아래 baseline 문서를 ledger 레이어에 맞게 갱신한다. - - `docs/runtime-flows.md`: 체결→원장 기록 흐름 + ledger outbox 재시도/conflict 흐름. - - `docs/interface-contract.md`: `LedgerPort.record_trade` 계약 + `FillStatus` 확장(execution_id/fee) + portfolio-hub ledger 엔드포인트. - - `docs/project-structure.md`: ledger 어댑터(`adapters/ledger`) 및 outbox 컴포넌트 위치. - - `docs/data-model.md`: 원장 거래 기록 필드(asset_id/side/qty/price/fee/tax/executed_at/execution_id) + outbox work item/멱등 키(correlation_id). - -## Acceptance Criteria -1. Given REQ-017~019 구현, When baseline 문서를 점검, Then 4개 문서(runtime-flows/interface-contract/project-structure/data-model)에 ledger 레이어가 반영되어 있다. -2. Given baseline 문서, When 코드와 대조, Then LedgerPort 계약·FillStatus 확장·outbox 구조·원장 필드 설명이 실제 구현과 모순되지 않는다. -3. Given 변경 로그, When 점검, Then 각 baseline 문서의 변경 이력에 ledger 통합(REQ-017~020) 반영이 기록되어 있다. - -## Impacted Area -- Docs: docs/runtime-flows.md, docs/interface-contract.md, docs/project-structure.md, docs/data-model.md -- Modules: 없음(문서 전용) -- Tests: 문서-코드 정합 점검(리뷰), 필요 시 interface-contract 예시와 실제 시그니처 대조 - -## Notes -- 출처: dcy-004 FR-5. -- baseline 갱신은 REQ-017~019 구현 내용에 의존하므로 동일 batch 후반 또는 구현 직후 수행하는 편이 적절하다(REQ-015 baseline 정합 선례와 동일 패턴). - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-004 FR-5 로부터 최초 생성(Proposed). - -#### Intent -- ledger 레이어 도입에 따른 baseline 문서(runtime-flows/interface-contract/project-structure/data-model) 정합을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 4개 baseline 반영, 코드 정합, 변경 이력 기록 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/interface-contract.md, docs/project-structure.md, docs/data-model.md -- Modules: - - 없음 -- Tests: - - 문서-코드 정합 점검 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-017~019 와 동일 batch 권장) - -#### Validation Plan -- REQ-017~019 구현 후 4개 baseline 문서를 코드와 대조해 정합 확인. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- ledger 레이어 baseline 문서 정합 요구사항을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - 없음 -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성(REQ-017~019 와 동일 batch 권장) - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반. - -#### Intent -- runtime-flows / interface-contract / project-structure / data-model 4문서를 ledger 레이어와 정합시킨 baseline 갱신을 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 (변경은 상태 전이만) - -#### Delivery Trace Delta -- Batches: - - BAT-006 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: 4 baseline 문서(runtime-flows/interface-contract/project-structure/data-model) 에 ledger 레이어 섹션 반영 완료. -- AC-2: baseline 문서 ↔ 코드 정합 manual review (verification.md Manual 섹션). NFR-1 import 스윕 + grep 결과로 보강. -- AC-3: 4 baseline 문서 Change Log 모두 2026-06-04 BAT-006 항목 추가. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-015) baseline 문서를 ledger 오류 분류·백오프·터미널 상태에 맞춰 정합: `docs/runtime-flows.md`(ledger writer permanent/backoff/dead-letter + list_pending 백오프 스킵 + env), `docs/data-model.md`(ledger_outbox status `delivery_failed`·`next_retry_at`·last_outcome 확장), `docs/interface-contract.md`(LedgerPermanentError 오류 계약). BAT-019 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(ledger_port/portfolio_hub/ledger_outbox/postgresql/config) 변경과 baseline 문서를 일치(S 기준)시켜 운영자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - runtime-flows/data-model/interface-contract 의 ledger 오류 분류·백오프·터미널 상태 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/runtime-flows.md, docs/data-model.md, docs/interface-contract.md - -#### Delivery Trace Delta - -- Batches: BAT-019 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-019 정합) -- Follow-up Action: confirm-req-implemented(BAT-019). - -#### Validation Plan - -- BAT-019: 코드-문서 diff 대조. diff --git a/docs/srs/Documentation/req-026_messaging-baseline-alignment.md b/docs/srs/Documentation/req-026_messaging-baseline-alignment.md deleted file mode 100644 index c2b3a0e..0000000 --- a/docs/srs/Documentation/req-026_messaging-baseline-alignment.md +++ /dev/null @@ -1,186 +0,0 @@ -# req-026: messaging 레이어 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- dcy-006 transport 전환(REQ-021~025) 이 도입하는 메시지 토폴로지/계약, RabbitMQ 어댑터 + sync+thread 런타임, delivery 신뢰성 + degrade 정책, HTTP 표면 0 결과를 baseline 문서에 반영해 코드와 문서의 정합을 유지한다. - -## Requirement -- 아래 baseline 문서를 messaging 레이어에 맞게 갱신한다. - - `docs/interface-contract.md` - - HTTP `/v1` 섹션 제거(또는 deprecated 명시). - - 신규 섹션: messaging 메시지 계약(스냅샷/intent RPC, 이벤트 topic), 큐/익스체인지/routing key, `correlation_id`/reply-to 규약, ack/durable/DLQ/TTL 정책, 어댑터 신선도 상태 노출. - - `docs/runtime-flows.md` - - 기존 매수/매도 intent HTTP 흐름 제거. - - 신규 섹션: 스냅샷 RPC 흐름, intent admission RPC 흐름, 체결/슬롯 이벤트 발행/구독 흐름, broker degrade 분기 흐름, consumer/publisher 스레드 구성. - - `docs/project-structure.md` - - `trex_core/adapters/api/` 제거, `trex_core/adapters/messaging/` 추가. - - sync+thread 런타임 구성(intent RPC consumer / snapshot RPC consumer / execution / ledger 스레드) 반영. - - 의존성 변경(`fastapi`/`uvicorn` 제거, `pika` 추가) 반영. -- 각 baseline 문서의 `Change Log` 에 본 변경(REQ-021~026 일괄 반영) 항목을 남긴다. -- baseline 문서 본문이 코드와 충돌하지 않도록 manual review evidence 를 남긴다(REQ-020 패턴). - -## Acceptance Criteria -1. Given REQ-021~025 구현 완료 상태, When 3 baseline 문서를 점검, Then `interface-contract.md`/`runtime-flows.md`/`project-structure.md` 모두에 messaging 레이어 섹션이 반영되어 있다. -2. Given 갱신된 baseline 문서, When 실제 코드(`trex_core/adapters/messaging/`, `runtime/daemon.py`, 토폴로지 선언) 와 대조, Then 메시지 계약·토폴로지·런타임 스레드 구성·의존성 변경이 모순 없이 일치한다. -3. Given 3 baseline 문서의 `Change Log`, When 점검, Then 본 transport 전환(REQ-021~026) 반영이 2026-06-05 일자로 기록되어 있다. -4. Given baseline 문서, When 점검, Then HTTP `/v1` 섹션과 `adapters/api` 트리 항목이 잔존하지 않는다(또는 deprecated 라벨로 명시). - -## Impacted Area -- Docs: docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md -- Modules: 없음(문서 전용) -- Tests: 문서-코드 정합 점검(리뷰); 필요 시 interface-contract 예시와 실제 메시지 형식 대조. - -## Notes -- 출처: dcy-006 FR-6. -- baseline 갱신은 REQ-021~025 구현 내용에 의존하므로 동일 batch 후반 또는 구현 직후 수행한다(REQ-020 baseline 정합 선례와 동일 패턴). -- `data-model.md` 는 본 transport 전환의 직접 대상이 아니다(이벤트 페이로드/큐 스키마는 interface-contract 가 담는 편이 자연스러움). 추후 이벤트 페이로드가 entity 수준으로 안정화되면 별도 REQ 에서 data-model 갱신 여부 판단. - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 FR-6 로부터 최초 생성(Proposed). - -#### Intent -- transport 전환에 따른 baseline 3문서(interface-contract/runtime-flows/project-structure) 정합을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 3 baseline 반영, 코드 정합, 변경 이력 기록, HTTP 잔존 0 (총 4개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md -- Modules: - - 없음 -- Tests: - - 문서-코드 정합 점검 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-021~025 와 동일 batch 편성 권장. - -#### Validation Plan -- REQ-021~025 구현 후 3 baseline 문서를 코드와 대조해 정합 확인. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- messaging 레이어 baseline 3문서(interface-contract/runtime-flows/project-structure) 정합 요구사항을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: REQ-021~025 와 동일 batch 후반 또는 후속 batch 편성 권장. - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- messaging 레이어 baseline 3문서(interface-contract/runtime-flows/project-structure) 정합을 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행. - -#### Validation Plan -- AC-1: 3 baseline 문서 messaging 섹션 + Interface Summary/Covered Entry Points/트리 반영 완료. -- AC-2: baseline ↔ 코드 정합 manual review (BAT-007 verification.md Manual 섹션). -- AC-3: 3 baseline 문서 Change Log 모두 2026-06-05 BAT-007 항목 추가. -- AC-4: docs/interface-contract.md 의 HTTP `/v1` 섹션 제거 + project-structure 트리에서 adapters/api 제거. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-016) baseline 문서를 운영자 CLI·RPC 매핑·워치독 억제 플래그에 맞춰 정합: `docs/interface-contract.md`(운영자 CLI↔RPC 매핑 표 + snapshot 요청 `touch_watchdog`/`actor` 플래그), `docs/runtime-flows.md`(운영자 CLI flow: 라우팅·워치독 억제·enter/exit 안전장치·실패 표시). BAT-020 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(cli/messaging_handlers) 변경과 messaging baseline 문서를 일치시켜 운영자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - interface-contract/runtime-flows 의 CLI 명령·RPC 매핑·워치독 억제 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/interface-contract.md, docs/runtime-flows.md - -#### Delivery Trace Delta - -- Batches: BAT-020 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-020 정합) -- Follow-up Action: confirm-req-implemented(BAT-020). - -#### Validation Plan - -- BAT-020: 코드-문서 diff 대조. diff --git a/docs/srs/Documentation/req-033_snapshot-read-baseline-alignment.md b/docs/srs/Documentation/req-033_snapshot-read-baseline-alignment.md deleted file mode 100644 index 8e44eb3..0000000 --- a/docs/srs/Documentation/req-033_snapshot-read-baseline-alignment.md +++ /dev/null @@ -1,131 +0,0 @@ -# req-033: 스냅샷 read 레이어 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- portfolio-hub read 포트·스냅샷 read 메시지 계약·일일 스냅샷 스케줄러·정합 검증을 baseline 문서에 정합시켜 문서-코드 일치를 유지한다. - -## Requirement -- `docs/interface-contract.md` 를 read 포트(`PortfolioHubReadPort`)·스냅샷 read 메시지 계약(envelope·view·strategy_key·degraded)에 맞게 갱신한다. -- `docs/runtime-flows.md` 를 일일 스냅샷 조회 흐름·옵션 D 현금/포지션 갱신·정합 검증 흐름에 맞게 갱신한다. -- `docs/project-structure.md` 를 신규 `adapters/portfolio` read 어댑터·스냅샷 스케줄러 컴포넌트에 맞게 갱신한다. -- dcy-006 의 transport baseline 갱신(REQ-026)과 정합을 유지한다. - -## Acceptance Criteria -1. Given REQ-027~032 구현, When baseline 3개 문서를 검토, Then read 포트·스냅샷 메시지 계약·스케줄러·정합 검증이 실제 코드와 정합한다. -2. Given 문서-코드 정합 점검, Then baseline 문서와 실제 포트/계약/흐름 사이 불일치가 0 이다. - -## Impacted Area -- Docs: docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md, docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- Modules: none(문서) -- Tests: 문서-코드 정합 리뷰 - -## Notes -- dcy-005 FR-5 반영. dcy-006 transport baseline 과 정합. -- 보통 REQ-027~032 구현 batch 의 검증 단계에서 함께 마감한다. - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-5 분해로 최초 생성(Proposed). - -#### Intent -- read 레이어 baseline 문서 정합 작업 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~2 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - interface-contract, runtime-flows, project-structure, dcy-005 -- Modules: - - none -- Tests: - - 문서-코드 정합 리뷰 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req - -#### Validation Plan -- baseline 문서 vs 실제 포트/계약/흐름 정합 리뷰로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. REQ-027~032 구현 batch 검증 단계에서 함께 마감. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- baseline 3 문서(runtime-flows / interface-contract / project-structure) 에 BAT-008 섹션 + Change Log 추가 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: 3 baseline 문서 모두 BAT-008 섹션 + 2026-06-06 Change Log 항목 추가됨. -- AC-2: BAT-008 verification.md Manual 섹션의 baseline ↔ 코드 정합 리뷰 (필드 시그니처/디렉토리 트리/Protocol 동작 일치 확인). diff --git a/docs/srs/Documentation/req-042_safety-sell-baseline-alignment.md b/docs/srs/Documentation/req-042_safety-sell-baseline-alignment.md deleted file mode 100644 index 662d38e..0000000 --- a/docs/srs/Documentation/req-042_safety-sell-baseline-alignment.md +++ /dev/null @@ -1,189 +0,0 @@ -# req-042: safety-sell baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent - -dcy-007에서 정의된 자율 안전 매도 기능(REQ-034~041)의 구현 완료 후 `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` 세 baseline 문서를 최신 아키텍처와 정합시킨다. - -## Requirement - -- `docs/interface-contract.md`에 아래 항목을 반영해야 한다: - - intent RPC payload에 `stop_loss_price`, `take_profit_price` 선택 필드 추가 - - `update_thresholds` action 계약 (payload 구조, 응답 구조, holding-only 제약) - - snapshot RPC가 워치독 `last_seen_at` 갱신 신호임을 명시 -- `docs/runtime-flows.md`에 아래 흐름을 추가해야 한다: - - `per-slot 워치독 갱신` 흐름 (snapshot RPC handler → last_seen_at 갱신) - - `forced_close_at 만기 자동 매도` 흐름 (safety-sell worker → forced_close_at 체크 → sell()) - - `stop-loss·take-profit 가격 체크` 흐름 (KIS polling → 조건 평가 → sell()) - - `장 마감 강제 청산` 흐름 (TREX_MARKET_CLOSE_LIQUIDATION_TIME → 전체 holding 매도) - - `runtime-flows.md:114`의 "A-5 미구현" 주석을 dcy-007 REQ 목록 참조로 갱신 -- `docs/project-structure.md`에 아래 항목을 반영해야 한다: - - Primary Runtime 스레드 목록에 `trex-safety-sell` 추가 - - 신규 5종 env var(`TREX_WATCHDOG_TIMEOUT` 등) 목록 추가 - - 신규 어댑터/모듈(KIS 현재가 polling, watchdog store) 반영 - -## Acceptance Criteria - -1. Given `docs/interface-contract.md`를 읽을 때, When `update_thresholds` action을 찾으면, Then payload 구조(`slot_id`, `stop_loss_price?`, `take_profit_price?`, `forced_close_at?`)와 응답(`status: updated|ignored`)이 명시되어 있다. -2. Given `docs/interface-contract.md`를 읽을 때, When intent RPC payload 예시를 찾으면, Then `stop_loss_price`와 `take_profit_price`가 선택 필드로 포함되어 있다. -3. Given `docs/runtime-flows.md`를 읽을 때, When safety-sell 관련 흐름을 찾으면, Then 워치독 갱신·forced_close_at 체크·가격 체크·장 마감 청산 흐름이 각각 서술되어 있다. -4. Given `docs/runtime-flows.md:114`(또는 이에 해당하는 broker degrade 흐름)을 읽을 때, When "A-5 미구현" 주석을 찾으면, Then 해당 주석이 dcy-007 구현 REQ 참조로 갱신되어 있다. -5. Given `docs/project-structure.md`를 읽을 때, When Primary Runtime 스레드 목록을 찾으면, Then `trex-safety-sell`이 포함되어 있다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` - - `docs/runtime-flows.md` - - `docs/project-structure.md` -- Modules: 없음 (문서 전용) -- Tests: 없음 - -## Notes - -- 이 REQ는 REQ-034~041 구현이 완료된 후 실행해야 한다. batch verification 단계에서 최신 구현 상태를 반영해 문서를 갱신한다. -- `runtime-flows.md:114`의 "A-5 미구현" 문구는 dcy-007 confirmed 시점에 이미 근거가 확보됐으므로, 구현 완료 전에도 "dcy-007 REQ-034~041로 트래킹" 형태로 미리 갱신할 수 있다. -- NFR-2(outbox retry) 관련 흐름은 기존 runtime-flows.md의 outbox 섹션에 흡수되어 있으므로 별도 추가 불필요. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-042 초안 생성 - -#### Intent - -- safety-sell 구현 후 세 baseline 문서 정합을 Documentation REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~5 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md, project-structure.md - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 (REQ-034~041 구현 batch 이후 연계) - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). - -#### Intent - -- BAT-011 에서 세 baseline 문서 정합 완료(interface-contract: update_thresholds 계약 + price 선택 필드 + 워치독 신호; runtime-flows: 워치독 갱신/forced_close/가격 체크/장 마감 4종 흐름 + A-5 주석 갱신; project-structure: trex-safety-sell 스레드 + watchdog.py/safety_sell.py 모듈). AC-1~5 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-011 배포 편성. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017) baseline 문서를 워치독 가시화에 맞춰 정합: `docs/runtime-flows.md`(워치독 상태 노출 + safety-sell 가시화 flow), `docs/interface-contract.md`(snapshot 응답 `watchdog` 필드). BAT-021 정합 완료 → Implemented 유지. - -#### Intent - -- 코드(watchdog/messaging_handlers/safety_sell/cli) 변경과 baseline 문서를 일치시켜 운영자 참조 정합 유지. - -#### Acceptance Criteria Delta - -- Updated: - - runtime-flows/interface-contract 의 워치독 가시화·safety-sell 발동 가시화 기술 정합. - -#### Impacted Area Delta - -- Docs: docs/runtime-flows.md, docs/interface-contract.md - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (문서 정합) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (change-req → BAT-021 정합) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: 코드-문서 diff 대조. diff --git a/docs/srs/Documentation/req-047_killswitch-baseline-alignment.md b/docs/srs/Documentation/req-047_killswitch-baseline-alignment.md deleted file mode 100644 index 2c02496..0000000 --- a/docs/srs/Documentation/req-047_killswitch-baseline-alignment.md +++ /dev/null @@ -1,146 +0,0 @@ -# req-047: kill-switch baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent - -dcy-008(비상 정지/kill-switch) 기능(REQ-043~046)의 구현 완료 후 `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` 세 baseline 문서를 최신 아키텍처와 정합시킨다. - -## Requirement - -- `docs/interface-contract.md` 에 아래를 반영한다: - - `trex.pause.req` → `trex.control.req` 제어 채널 확장 + action 목록(`pause`/`resume`/`list`/`kill_switch`/`resume_kill_switch`) (REQ-043) - - `kill_switch` payload(`scope`, `portfolio_id?`)/응답(`liquidated`) 계약 (REQ-045) - - `resume_kill_switch` payload/응답 계약 (REQ-046) - - snapshot masking 소스에 "kill-switch 당일 제외" 추가 명시 (REQ-046) -- `docs/runtime-flows.md` 에 아래 흐름을 추가한다: - - kill-switch 발동 흐름(control RPC → 범위별 holding 순차 sell() → 당일 제외 표시) (REQ-045) - - 당일 제외 masking / 다음 거래일 자동 복귀 / 수동 resume 흐름 (REQ-046) -- `docs/project-structure.md` 에 아래를 반영한다: - - control consumer(`trex.control.req`) 로의 전환 (REQ-043) - - 신규 `killswitch_exclusion` 테이블 + `005_create_killswitch_exclusion.sql` + kill-switch store 모듈 (REQ-044) - -## Acceptance Criteria - -1. Given `docs/interface-contract.md` 를 읽을 때, When 제어 채널을 찾으면, Then `trex.control.req` + action 목록(kill_switch/resume_kill_switch 포함)이 명시되어 있다. -2. Given `docs/interface-contract.md` 를 읽을 때, When kill_switch/resume_kill_switch 를 찾으면, Then payload(scope/portfolio_id)와 응답 구조가 명시되어 있다. -3. Given `docs/runtime-flows.md` 를 읽을 때, When kill-switch 관련 흐름을 찾으면, Then 발동(순차 sell()+당일 제외)·masking·자동 복귀·수동 resume 흐름이 서술되어 있다. -4. Given `docs/project-structure.md` 를 읽을 때, When 제어 채널/저장소를 찾으면, Then control consumer 전환 + `killswitch_exclusion` 테이블/store + `005_*.sql` 이 반영되어 있다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` - - `docs/runtime-flows.md` - - `docs/project-structure.md` -- Modules: 없음 (문서 전용) -- Tests: 없음 - -## Notes - -- 이 REQ 는 REQ-043~046 구현 완료 후 batch verification 단계에서 최신 구현 상태를 반영해 수행한다(REQ-042 선례). -- baseline 문서가 없는 갭이 아니라 기존 baseline 갱신이므로 `bootstrap-baseline` 선행 불필요. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-008(confirmed) → REQ-047 초안 생성. - -#### Intent - -- kill-switch 구현 후 세 baseline 문서 정합을 Documentation REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~4 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md, project-structure.md - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인 (REQ-043~046 구현 batch 이후 연계). - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC(1~4)·Impacted Area·Change Log 완비. baseline 정합 문서 REQ로 REQ-043~046 구현 후 verification 단계 수행 예정. 범위와 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성(REQ-043~046 이후 연계). - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-012 release-candidate). - -#### Intent - -- BAT-012 에서 baseline 3문서 정합 완료: interface-contract(`trex.control.req` action + kill_switch/resume_kill_switch payload + masking 소스), runtime-flows(kill-switch 비상 정지 흐름 + control consumer), project-structure(control consumer + killswitch_exclusion/store + 005 sql + killswitch.py). AC-1~4 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-012 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-012 배포 편성. diff --git a/docs/srs/Documentation/req-051_multi-instance-baseline-alignment.md b/docs/srs/Documentation/req-051_multi-instance-baseline-alignment.md deleted file mode 100644 index a9019db..0000000 --- a/docs/srs/Documentation/req-051_multi-instance-baseline-alignment.md +++ /dev/null @@ -1,144 +0,0 @@ -# req-051: multi-instance topology baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent - -dcy-009(다중 인스턴스 RabbitMQ 토폴로지) 구현(REQ-048~050) 완료 후 `docs/runtime-flows.md`, `docs/project-structure.md`, `docs/interface-contract.md` 세 baseline 문서를 multi-instance topology + TOML 기반 기동 기준으로 정합시킨다(dcy-009 FR-6). - -## Requirement - -- `docs/interface-contract.md` 에 반영한다: - - routing key 계약: directed = `{instance_key}.{channel_type}`(`trex.direct` topic), broadcast = `trex.broadcast`(fanout, 전역 control) - - 인스턴스별 큐명 규칙(`{base}.{instance_key}`, default = 리터럴 하위호환) -- `docs/runtime-flows.md` 에 반영한다: - - TOML(`--config`) 기반 기동 절차(instance_key/portfolio_hub_token 로드) - - multi-instance directed vs broadcast 라우팅 흐름, split-brain 방지(인스턴스 전용 큐) 설명 -- `docs/project-structure.md` 에 반영한다: - - `topology.py` 역할 변경(고정 상수 → 동적 큐명/exchange 빌더) - - 설정 계층(TOML + env 병합), `DaemonInstanceConfig`, `--config` 진입점 - - `trex.direct`/`trex.broadcast` exchange - -## Acceptance Criteria - -1. Given `docs/interface-contract.md`, When routing key 규칙을 찾으면, Then directed(`{instance_key}.{channel_type}`)와 broadcast(fanout) 계약 + 인스턴스 큐명 규칙이 명시되어 있다. -2. Given `docs/runtime-flows.md`, When 기동/라우팅 흐름을 찾으면, Then TOML(`--config`) 기동 + directed/broadcast 라우팅 + split-brain 방지가 서술되어 있다. -3. Given `docs/project-structure.md`, When topology/설정 항목을 찾으면, Then 동적 큐명 빌더 + `DaemonInstanceConfig`/`--config` + `trex.direct`/`trex.broadcast` 가 반영되어 있다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` - - `docs/runtime-flows.md` - - `docs/project-structure.md` -- Modules: 없음 (문서 전용) -- Tests: 없음 - -## Notes - -- REQ-048~050 구현 완료 후 batch verification 단계에서 최신 구현 상태를 반영해 수행한다(REQ-042/047 선례). -- baseline 갱신이므로 `bootstrap-baseline` 선행 불필요. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-009(confirmed) → REQ-051 초안 생성. - -#### Intent - -- multi-instance 구현 후 세 baseline 문서 정합(FR-6)을 Documentation REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~3 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md, project-structure.md - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인 (REQ-048~050 구현 batch 이후 연계). - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. baseline 정합 문서 REQ로 REQ-048~050 구현 후 verification 단계 수행. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성(REQ-048~050 이후 연계). - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-013 release-candidate). - -#### Intent - -- BAT-013 에서 baseline 3문서 정합 완료: interface-contract(multi-instance routing: 큐명 규칙·trex.direct/trex.broadcast·routing key), runtime-flows(`--config` 기동 + multi-instance 라우팅 흐름 + split-brain 방지), project-structure(동적 큐명 빌더·DaemonInstanceConfig/`--config`·exchange). AC-1~3 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-013 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-013 배포 편성. diff --git a/docs/srs/Documentation/req-054_safety-sell-cooldown-baseline-alignment.md b/docs/srs/Documentation/req-054_safety-sell-cooldown-baseline-alignment.md deleted file mode 100644 index 8b784c0..0000000 --- a/docs/srs/Documentation/req-054_safety-sell-cooldown-baseline-alignment.md +++ /dev/null @@ -1,137 +0,0 @@ -# req-054: safety-sell 쿨다운 runtime-flows 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Low -- Owner: project maintainers - -## Intent - -dcy-010 safety-sell 재시도 쿨다운(REQ-052/053) 구현 후 `docs/runtime-flows.md`의 safety-sell tick 흐름에 "쿨다운 체크 → 건너뜀/재시도" 단계를 반영한다(dcy-010 FR-6). - -## Requirement - -- `docs/runtime-flows.md`의 safety-sell 관련 흐름(forced_close / stop-loss·take-profit)에 per-slot 쿨다운 단계를 추가한다: - - `_safe_sell` 호출 전 `now - last_attempt_at < min_retry_interval_sec` 이면 건너뜀 - - 쿨다운 만료 후 트리거 조건 유지 시 재시도 + `attempt_count` 증가 - - `attempt_count >= max_retry_warn_count` 시 WARNING 로그 - - 슬롯 holding 이탈 시 쿨다운 상태 정리 - - market_close 는 쿨다운 제외(하루 1회 가드) -- (선택) `docs/project-structure.md` config.py 설명에 safety-sell env 2종 추가 반영. - -## Acceptance Criteria - -1. Given `docs/runtime-flows.md`의 stop-loss·take-profit / forced_close 흐름, When 읽으면, Then 쿨다운 체크(건너뜀/재시도)·경고 로그·holding 이탈 정리 단계가 서술되어 있다. -2. Given `docs/runtime-flows.md`, When market_close 흐름을 읽으면, Then 쿨다운 제외(하루 1회 가드)가 명시되어 있다. - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` (필수), `docs/project-structure.md`(선택) -- Modules: 없음 -- Tests: 없음 - -## Notes - -- REQ-052/053 구현 완료 후 batch verification 단계에서 수행(REQ-042/047/051 선례). - -## Change Log - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- dcy-010(confirmed) → REQ-054 초안 생성. - -#### Intent - -- safety-sell 쿨다운 runtime-flows 정합(FR-6)을 Documentation REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~2 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, project-structure.md(선택) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인 (REQ-052/053 구현 batch 이후 연계). - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Change Log 완비. baseline 정합 문서 REQ로 REQ-052/053 구현 후 verification 단계 수행. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-batch 로 delivery 편성(batch-lite, REQ-052/053 이후 연계). - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-014 release-candidate). - -#### Intent - -- BAT-014 에서 `docs/runtime-flows.md` 에 "safety-sell 재시도 쿨다운" flow 추가(skip/재시도/경고/정리 + market_close 제외) + 가격체크 tr_id(FHKST01010100) 정정. AC-1~2 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-014 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-014 배포 편성. diff --git a/docs/srs/Documentation/req-065_asset-centric-baseline-alignment.md b/docs/srs/Documentation/req-065_asset-centric-baseline-alignment.md deleted file mode 100644 index 7a03b79..0000000 --- a/docs/srs/Documentation/req-065_asset-centric-baseline-alignment.md +++ /dev/null @@ -1,92 +0,0 @@ -# req-065: 자산 중심 모델 baseline 문서 정합 + 풀 모델 폐기 명시 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- 자산 중심 영구 슬롯 모델 전환 후 baseline 문서를 실제 구조/흐름/데이터 모델과 일치시키고, 폐기되는 풀 모델(dcy-001/002/014/019) 산출물의 처분을 명시한다. (dcy-020 FR-5) - -## Requirement -- `docs/data-model.md`(영구 slot 스키마·instance_key·retired_at·trading_date 강등·entry_count 제거), `docs/runtime-flows.md`(universe reconcile lifecycle·신호 기반 admission·불일치 해소·일일 preallocation 폐기), `docs/project-structure.md`(자산 중심 슬롯 모델)를 새 모델로 갱신한다. dcy-001(6상태/closed→idle)·dcy-014(일일 preallocation)·dcy-019(entry_count/closed 재할당/find_allocatable_slot) 의 폐기/재해석 범위와 `daily_symbol_entries` 폐기를 명시한다. - -## Acceptance Criteria -1. Given 전환 구현 완료, When `docs/data-model.md` 확인, Then 영구 슬롯 스키마·상태·트레이딩데이트 강등이 코드와 일치한다. -2. Given 전환 구현 완료, When `docs/runtime-flows.md` 확인, Then reconcile lifecycle·admission·불일치 해소 흐름이 실제와 일치하고 일일 preallocation 서술이 제거된다. -3. Given 폐기 대상, When 문서 확인, Then dcy-001/014/019·daily_symbol_entries 폐기/재해석 범위가 명시된다. - -## Impacted Area -- Docs: docs/data-model.md, docs/runtime-flows.md, docs/project-structure.md -- Modules: 없음(문서 전용) -- Tests: 없음(코드-문서 diff 대조) - -## Notes -- 다른 dcy-020 REQ 구현 완료 후 수행해야 의미가 있다(순서 의존). baseline 갱신은 각 배치의 일부로 분산 가능. -- 출처: dcy-020 FR-5. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-5 로부터 최초 생성(Proposed). baseline 3문서 자산 중심 재설계 + dcy-001/014/019·daily_symbol_entries 폐기 명시. - -#### Intent -- 모델 전환 후 baseline 문서 정합과 폐기 범위 명시를 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: data-model/runtime-flows/폐기 명시 3개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/data-model.md, docs/runtime-flows.md, docs/project-structure.md -- Modules: 없음(문서 전용) -- Tests: 없음(코드-문서 diff) - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. 다른 dcy-020 REQ 구현 후 수행(순서 의존). - -#### Validation Plan -- 코드-문서 diff 대조로 정합 확인. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: 각 배치에서 분산 수행. - -#### Validation Plan -- 승인 후 코드-문서 diff 대조로 검증. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-030(baseline 4문서 마감) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-067_position-sizing-baseline-alignment.md b/docs/srs/Documentation/req-067_position-sizing-baseline-alignment.md deleted file mode 100644 index 22f8d56..0000000 --- a/docs/srs/Documentation/req-067_position-sizing-baseline-alignment.md +++ /dev/null @@ -1,88 +0,0 @@ -# req-067: 포지션 사이징 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- dcy-021: 보유 포지션 사이징(증액·부분 감액·명령형 사이징 intent) 구현이 끝나면 cross-cutting baseline 문서를 실제 동작과 일치시켜, 이후 Discovery/REQ/Batch 가 정확한 기준을 참조하게 한다. (issue #1, dcy-020 OQ-4 후속) - -## Requirement -- `docs/data-model.md`: 보유 슬롯 수량 가변(증액 누적·부분 감액 잔량)·avg_price 가중평균 재계산·cost basis 유지 규칙을 반영한다. -- `docs/runtime-flows.md`: 사이징 intent 수신 → 집행(증액 매수/부분 매도, 집행 수량 = min(intent, 브로커 잔고)) → 부분 체결 실제 반영·미추적 → 슬롯 holding 유지/inactive 종료 흐름과 확장된 상태 전이(HOLDING→PENDING_BUY 등)를 반영한다. -- `docs/interface-contract.md`: 사이징 intent payload(명령형 qty+price; 증액 buy-on-holding·부분 매도 qty)·이벤트·집행 수량 규칙을 반영한다. -- `docs/project-structure.md`: 사이징으로 영향받은 모듈(slot service·state·intents·execution) 책임 서술을 갱신한다. - -## Acceptance Criteria -1. Given 사이징 구현 완료, When `docs/data-model.md` 확인, Then 수량 가변·avg_price 가중평균·cost basis 유지가 코드와 일치한다. -2. Given 사이징 구현 완료, When `docs/runtime-flows.md`·`docs/interface-contract.md` 확인, Then 사이징 intent·집행(min(intent,잔고))·부분 체결 미추적·상태 전이 확장이 실제와 일치한다. -3. Given baseline 갱신, When `docs/project-structure.md` 확인, Then 영향 모듈 서술이 구현과 일치한다. - -## Impacted Area -- Docs: docs/data-model.md, docs/runtime-flows.md, docs/interface-contract.md, docs/project-structure.md -- Modules: 없음(문서) -- Tests: 없음(문서 리뷰) - -## Notes -- 본 REQ 는 REQ-066 및 change-req(REQ-057/061/062) 구현 완료 후 마지막에 정합한다(dcy-020 REQ-065 와 동일 패턴). -- 출처: dcy-021 FR-4. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-021(포지션 사이징)로부터 최초 생성(Proposed). 사이징 baseline 4문서 정합. - -#### Intent -- 구현 후 cross-cutting baseline 을 실제 동작과 일치. - -#### Acceptance Criteria Delta -- Added: data-model/runtime-flows·interface-contract/project-structure 정합 3개 기준 -- Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: data-model/runtime-flows/interface-contract/project-structure / Modules: 없음 / Tests: 없음(문서 리뷰) - -#### Delivery Trace Delta -- Batches: dcy-021 신규 배치(예정) / Releases: none - -#### Revalidation Impact -- Reverification Needed: no(문서) / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: confirm-req → dcy-021 배치 마지막 정합. - -#### Validation Plan -- 사이징 구현 완료 후 baseline 4문서 diff 리뷰. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-021 와 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md(Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: dcy-021 배치 마지막 정합. - -#### Validation Plan -- 승인 후 dcy-021 배치 verification 에서 baseline 정합 확인. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-072_execution-policy-baseline-alignment.md b/docs/srs/Documentation/req-072_execution-policy-baseline-alignment.md deleted file mode 100644 index 17728f7..0000000 --- a/docs/srs/Documentation/req-072_execution-policy-baseline-alignment.md +++ /dev/null @@ -1,82 +0,0 @@ -# req-072: 실행 정책 baseline 문서 정합 — interface-contract / runtime-flows - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- dcy-022 실행 정책 변경(timeout_sec·slippage_pct intent 계약, timeout 집행 흐름, slippage dispatch, 운영자 cancel 백스톱)을 baseline 문서에 반영해 docs 와 코드 일관성을 유지한다. (dcy-022 FR-6) - -## Requirement -- `docs/interface-contract.md`: buy/sell intent payload 에 optional `timeout_sec`(주문 유효시간), `slippage_pct`(체결 보호밴드) 필드 추가 및 설명 반영. `docs/runtime-flows.md`: 집행 루프 흐름에 timeout 취소(cancel_order 송신·race·rollback)·slippage dispatch·운영자 cancel 백스톱 흐름 추가. 하위호환 및 default 정책(config 기본값·미지정=OFF) 명시. - -## Acceptance Criteria -1. Given `docs/interface-contract.md`, When 확인, Then buy/sell payload 에 `timeout_sec`(optional int), `slippage_pct`(optional float) 필드 명세와 하위호환 설명이 포함된다. -2. Given `docs/runtime-flows.md`, When 확인, Then 집행 루프 섹션에 timeout 초과 → cancel_order 송신 → 다음 poll 수렴 흐름이 기술된다. -3. Given `docs/runtime-flows.md`, When 확인, Then dispatch 섹션에 slippage_pct 실효 지정가 산출 흐름(entry_price*(1±pct), entry_price 없으면 무시)이 기술된다. -4. Given `docs/runtime-flows.md`, When 확인, Then 운영자 cancel 백스톱 흐름(trex-core cancel → control RPC → cancel_order 송신)이 기술된다. - -## Impacted Area -- Docs: docs/interface-contract.md, docs/runtime-flows.md - -## Notes -- 출처: dcy-022 FR-6. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-022 FR-6 로부터 최초 생성(Proposed). 실행 정책 baseline 2문서 정합. - -#### Intent -- 실행 정책 변경을 baseline 에 동기화. - -#### Acceptance Criteria Delta -- Added: AC-1~4 (interface-contract payload, runtime-flows timeout·slippage·cancel 흐름 4개 기준) - -#### Impacted Area Delta -- Docs: docs/interface-contract.md, docs/runtime-flows.md - -#### Delivery Trace Delta -- Batches: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current - -#### Validation Plan -- 문서 확인(커밋 메시지 + 내용 일치). - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) - -#### Delivery Trace Delta -- Batches: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032. - -#### Validation Plan -- 승인 후 batch planning 에서 AC 검증 기준으로 사용. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-075_slots-read-surface-baseline-alignment.md b/docs/srs/Documentation/req-075_slots-read-surface-baseline-alignment.md deleted file mode 100644 index dfedfcb..0000000 --- a/docs/srs/Documentation/req-075_slots-read-surface-baseline-alignment.md +++ /dev/null @@ -1,61 +0,0 @@ -# req-075: slots read 표면 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- `trex.snapshot.req`→`trex.slots.req` 표면 교체(REQ-073)·슬롯 단일 행 스키마(REQ-074)를 baseline 문서에 반영해, REQ/batch/운영이 참조하는 계약 문서가 실제 표면과 어긋나지 않게 한다. (dcy-025 FR-7) - -## Requirement -- `docs/interface-contract.md`: `trex.snapshot.req` 섹션(요청 view/strategy_key, 응답 assets[]/available_slots/watchdog[])을 제거하고 `trex.slots.req` 계약(요청 {filter, spy}, 응답 {slots[], degraded/reason/message}, 행 스키마 + **필드별 정본 출처 표**(hub/슬롯·코어/브로커(행 미포함)/watchdog/overlay))으로 대체한다. 토폴로지 표의 큐명도 갱신한다. -- `docs/runtime-flows.md`: 전략/운영자 read 흐름을 새 표면(slots.req, filter/spy, 워치독 터치 시맨틱)으로 갱신하고 snapshot envelope 흐름 서술을 제거·대체한다. -- 제거되는 표면(view 모드, assets[], available_slots, watchdog[] 배열)이 문서 어디에도 현행 계약으로 남지 않는다(이력/Change Log 는 보존). - -## Acceptance Criteria -1. Given `docs/interface-contract.md`, When 검토, Then `trex.slots.req` 계약(요청/응답/행 스키마/정본 표)이 기술되어 있고 `trex.snapshot.req` 가 현행 계약으로 남아 있지 않다. -2. Given `docs/runtime-flows.md`, When 검토, Then read 흐름이 slots.req(filter/spy/워치독 시맨틱) 기준으로 기술되어 있다. -3. Given 저장소 전체 문서, When grep(`trex.snapshot.req`/`available_slots`/`assets[]` 현행 서술), Then 현행 계약으로서의 잔존 서술이 없다(이력 제외). - -## Impacted Area -- Docs: docs/interface-contract.md, docs/runtime-flows.md -- Modules: 없음(문서 전용) -- Tests: 없음(문서 검토) - -## Notes -- REQ-073/074 와 같은 batch 에서 구현과 동시 정합(코드-문서 drift 방지). -- 출처: dcy-025 FR-7. - -## Change Log - -### CHG-20260611-01 -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary -- dcy-025 FR-7 로부터 최초 생성(Proposed). interface-contract/runtime-flows 의 snapshot→slots 표면 정합. - -#### Intent -- 표면 교체의 계약 문서 동기화. - -#### Acceptance Criteria Delta -- Added: - - AC-1~3 신규 -- Updated/Removed: none - -#### Impacted Area Delta -- Docs: interface-contract/runtime-flows - -#### Delivery Trace Delta -- Batches: none(승인 후 draft-batch) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no(신규) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current(Proposed) -- Follow-up Action: confirm-req - -#### Validation Plan -- 신규 batch verification 에서 문서 검토 + grep 잔존 확인. diff --git a/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md b/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md deleted file mode 100644 index 670c6d0..0000000 --- a/docs/srs/Documentation/req-078_long-term-operation-baseline-alignment.md +++ /dev/null @@ -1,76 +0,0 @@ -# req-078: 중장기 운영 baseline 문서 정합 — 보호 규칙 경계 + forced_close_at 미사용 + 병행 운영 흐름 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- 중장기 인스턴스 운영의 구조·흐름·정책 경계를 baseline 문서에 명시해 이후 REQ/batch 와 운영자가 참조 가능하게 한다. 특히 "보호 규칙은 전략 모듈 전적 책임"이라는 경계(dcy-027 OQ-2)와 `forced_close_at` 중장기 미사용(CONFIRM)을 문서로 확정한다. (dcy-027 FR-2/FR-4/FR-5) - -## Requirement -- `docs/runtime-flows.md` 에 중장기 인스턴스 운영 흐름을 추가한다: 병행 인스턴스 토폴로지(당일/중장기 instance_key 분리), 중장기 프로파일 구성(REQ-077), `safety_sell_enabled=false` 시 보호장치 경로(코어 손절매 루틴 일절 미사용 — stop-loss·트레일링·타임스톱 전부 전략 모듈 전적 책임, 코어는 운영자 CLI `exit` 백스톱만 제공). -- `forced_close_at` 의 중장기 의미를 명시한다: **미사용(의미 없음)** — 집행 주체(safety-sell 워커)가 OFF 이므로 구조적으로 발화 불가(dcy-027 CONFIRM). 운영자 CLI·intent 계약 관련 서술에 반영한다. -- `docs/project-structure.md` 에 중장기 운영 관련 구조(설정 키, 예시 프로파일 위치)가 필요한 만큼 반영한다(구조 변화가 없으면 흐름 문서 중심). - -## Acceptance Criteria -1. Given `docs/runtime-flows.md`, When 중장기 운영 섹션 확인, Then 병행 인스턴스 토폴로지·프로파일 구성·보호 규칙 경계(전략 전적 책임 + CLI exit 백스톱)가 현행 코드와 정합하게 기술되어 있다. -2. Given baseline 문서, When `forced_close_at` 서술 확인, Then 중장기 인스턴스에서 미사용(의미 없음, 발화 불가 근거 포함)이 명시되어 있다. -3. Given 신규 운영자, When baseline 문서만으로 중장기 인스턴스 운영 모델 파악 시도, Then 어떤 보호장치가 없고 누가 책임지는지(전략 모듈/운영자 CLI) 모호함 없이 판단 가능하다. - -## Impacted Area -- Docs: docs/runtime-flows.md(중장기 운영 흐름 + Change Log), docs/project-structure.md(필요 시) -- Modules: 없음(문서 전용) -- Tests: 없음(문서 diff 리뷰로 검증 — S-4) - -## Notes -- 출처: dcy-027 FR-2(보호 규칙 경계 명시), FR-4(forced_close_at 미사용), FR-5(baseline 갱신), S-2/S-4. -- REQ-076/077 구현 결과와 같은 batch 에서 정합 — 코드 확정 후 문서화(선례: REQ-042/047/051 등 baseline 정합 REQ). -- R-1(보호장치 공백 방치 위험)의 완화가 곧 이 문서화다 — 경계가 운영 문서에 명시되어야 전략 모듈 설계·운영 절차가 책임을 인지한다. - -## Change Log - -### CHG-20260612-01 -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary -- dcy-027 FR-2/FR-4/FR-5 로부터 최초 생성(Proposed). 중장기 운영 baseline 정합 + 보호 경계·forced_close_at 미사용 명시. - -#### Intent -- 중장기 운영 모델의 정책 경계를 문서로 확정해 운영·후속 개발 참조점 제공. - -#### Acceptance Criteria Delta -- Added: 흐름 정합/forced_close_at 미사용 명시/운영자 판단 가능성 3개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: runtime-flows.md, project-structure.md(필요 시) -- Modules: 없음 -- Tests: 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-076/077 과 동일 batch) - -#### Validation Plan -- baseline 문서 diff 리뷰 — 중장기 흐름 섹션 존재 + 현행 코드 정합 + 경계 명시 확인. - -### CHG-20260612-02 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-027 정합(FR-2/4/5 — 보호 경계·forced_close_at 미사용 결정 반영 확인). - -### CHG-20260612-03 -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-041(release-candidate) verification — `docs/runtime-flows.md` `중장기(멀티데이) 인스턴스 병행 운영` flow 신설(토폴로지/프로파일/보호 경계: 전략 전적 책임+CLI exit 백스톱/forced_close_at 미사용/watchdog 의미) + Change Log, 현행 코드 정합 확인(AC-1~3). project-structure 는 구조 불변으로 변경 불요. Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-082_strategy-state-baseline-alignment.md b/docs/srs/Documentation/req-082_strategy-state-baseline-alignment.md deleted file mode 100644 index b339f13..0000000 --- a/docs/srs/Documentation/req-082_strategy-state-baseline-alignment.md +++ /dev/null @@ -1,77 +0,0 @@ -# req-082: strategy-state baseline 문서 정합 — read/write/삭제 흐름 + occupancy_seq 가드 + 저장소 구조 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- Slot strategy-state 저장소의 구조·흐름·계약 경계를 baseline 문서에 명시해 이후 REQ/batch·전략 데몬·운영자가 참조 가능하게 한다. 특히 read piggyback / write 별도 RPC 비대칭, occupancy_seq 점유 가드, close 삭제 불변식, opaque/size cap 경계를 문서로 확정한다. (dcy-028 FR-4) - -## Requirement -- `docs/runtime-flows.md` 에 strategy-state 흐름을 추가한다: 데몬 decision-cycle(`trex.slots.req` read piggyback → `evaluate()` → 변경 시 `trex.slots.strategy_state.set` write), occupancy_seq·version CAS, 점유 전환 가드(`occupancy_changed`), 슬롯 close(`→INACTIVE`) 시 strategy_state 삭제 훅. -- `docs/project-structure.md` 에 저장소 구조를 반영한다: `slot_strategy_state` 테이블(복합 PK), `StrategyStateStore` 포트 + PG/in-memory 구현 위치, slot `occupancy_seq` 컬럼. -- `docs/interface-contract.md` 에 `trex.slots.strategy_state.set` 계약(요청/응답 shape, reject reason 집합)과 slots.req 응답의 strategy_state 맵·occupancy_seq 필드(C-1)를 명시한다. -- 경계 명시: trex-core 는 `data` 를 해석하지 않는다(opaque), schema 는 전략 소유, size cap(기본 1~2KB / ceiling 4KB byte), 단일 writer 전제, full 3계층 분할은 후속 Discovery. - -## Acceptance Criteria -1. Given `docs/runtime-flows.md`, When strategy-state 섹션 확인, Then read piggyback/write 별도 RPC·occupancy_seq 가드·close 삭제 흐름이 현행 코드(REQ-079/080/081)와 정합하게 기술되어 있다. -2. Given `docs/project-structure.md`, When 확인, Then `slot_strategy_state` 테이블·StrategyStateStore·occupancy_seq 컬럼 위치가 반영되어 있다. -3. Given `docs/interface-contract.md`, When 확인, Then strategy_state.set 계약(요청/응답/reject reason 집합)과 slots.req 행의 strategy_state·occupancy_seq 필드가 명시되어 있다. -4. Given 신규 전략 개발자, When baseline 문서만으로 strategy-state 사용 모델 파악 시도, Then opaque 경계·size cap·점유 가드·삭제 책임(core)이 모호함 없이 판단 가능하다. - -## Impacted Area -- Docs: docs/runtime-flows.md(strategy-state 흐름 + Change Log), docs/project-structure.md(저장소 구조), docs/interface-contract.md(계약), docs/data-model.md(스키마 참조 — REQ-079/081 과 정합) -- Modules: 없음(문서 전용) -- Tests: 없음(문서 diff 리뷰로 검증) - -## Notes -- REQ-079/080/081 구현 결과와 같은 batch 에서 정합 — 코드 확정 후 문서화(선례: REQ-075/078 등 baseline 정합 REQ). -- 출처: dcy-028 FR-4, S-1~S-4 의 문서 참조 가능성. - -## Change Log - -### CHG-20260615-03 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042 verification — `docs/runtime-flows.md`(slot strategy-state 흐름 + Change Log), `docs/project-structure.md`(strategy_state.py 책임), `docs/interface-contract.md`(strategy_state.set 계약 + slots.req additive + Change Log) 반영, 현행 코드 정합. AC-1~4 충족(흐름/구조/계약/경계 명시). Blocking Issues 없음. - -### CHG-20260615-02 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(4개) 충족, source dcy-028(FR-4) 정합. 코드 확정 후 문서화(REQ-079/080/081 과 동일 batch). blocker 없음. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- dcy-028 FR-4 로부터 최초 생성(Proposed). strategy-state read/write/삭제 흐름·occupancy_seq 가드·저장소 구조·계약 baseline 정합. - -#### Intent -- strategy-state 모델의 구조·흐름·경계를 문서로 확정해 운영·후속 개발·전략 데몬 참조점 제공. - -#### Acceptance Criteria Delta -- Added: runtime-flows/project-structure/interface-contract 정합 + 개발자 판단 가능성 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: runtime-flows.md, project-structure.md, interface-contract.md, data-model.md -- Modules: 없음 -- Tests: 없음 - -#### Delivery Trace Delta -- Batches: none (편성 예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-079/080/081 + REQ-073/074 CHG 와 동일 batch, 코드 확정 후 문서화) - -#### Validation Plan -- baseline 문서 diff 리뷰 — strategy-state 흐름·구조·계약 섹션 존재 + 현행 코드 정합 확인. diff --git a/docs/srs/Documentation/req-089_operator-runbook.md b/docs/srs/Documentation/req-089_operator-runbook.md deleted file mode 100644 index c087731..0000000 --- a/docs/srs/Documentation/req-089_operator-runbook.md +++ /dev/null @@ -1,54 +0,0 @@ -# req-089: 운영자 런북 — docs/operations.md 신설 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- trex-core에 사람 운영자용 문서(배포·디버깅·CLI 사용법)가 없다. 운영 런북 `docs/operations.md`를 신설해 신규 운영자가 배포·실행, 상태/PnL 조회, 거부·미체결 디버깅을 그 문서만으로 수행하게 한다. (dcy-029 FR-8) - -## Requirement -- `docs/operations.md`를 신설하고 최소 4영역을 포함한다: - 1. **배포/실행**: 필수 env(`TREX_POSTGRESQL_DSN`/`TREX_RABBITMQ_URL`/`TREX_BROKER` 등), `--config`/`--env-file`, 기동·종료, 테스트 DB 안전 절차. - 2. **운영자 CLI 사용법**: `pause/resume/slots/universe/enter/exit/cancel` + 신설 조회/PnL 서브커맨드(REQ-088). - 3. **로깅/관측성 사용법**: 타임스탬프·레벨, 주문 결과 로그 grep(`event=order_submit|fill|reject`), `TREX_LOG_FORMAT=json`/Loki·correlation id, 청산 reason+actor 해석(REQ-086). - 4. **디버깅/트러블슈팅**: 거부 ERROR 확인, 미체결 진단, DB 상태 조회(daily_slot/ledger_outbox), 자주 겪는 증상→원인→조치. -- 기존 `.claude/skills/run-trex-core-daemon/SKILL.md`의 사실 자료(env 표·launch·troubleshooting)를 사람용 문서로 추출·정리하고, 본 반복(REQ-084~088) 관측성 기능 사용법을 반영한다. - -## Acceptance Criteria -1. Given docs/operations.md, When 확인, Then 배포/실행 절차(env·config·기동·종료)가 포함돼 있다. -2. Given 운영자 CLI 절, When 확인, Then 기존 서브커맨드 + 신설 조회/PnL(REQ-088) 사용법이 있다. -3. Given 로깅/관측성 절, When 확인, Then 타임스탬프·주문결과 grep·JSON/Loki·correlation id·reason+actor 해석이 있다. -4. Given 디버깅 절, When 확인, Then 거부 ERROR 확인·미체결 진단·DB 조회 가이드가 있다. -5. Given 신규 운영자, When 런북만 참고, Then 데몬 배포·CLI 조회·거부 디버깅을 수행할 수 있다(4영역 모두 충족). - -## Impacted Area -- Docs: docs/operations.md(신설), 참조: .claude/skills/run-trex-core-daemon/SKILL.md -- Modules: 없음(문서) -- Tests: 없음(문서 — verification은 4영역 포함 리뷰 체크) - -## Notes -- 본 REQ는 REQ-084~088 구현 사용법을 문서화하므로 그 REQ들과 같은 batch(P1) 후반에 작성한다. -- 출처: dcy-029 FR-8. - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-8로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-046 release-candidate, dcy-029 P1). 근거: 단위/문서 AC 매핑(REQ-086 reason+actor 로그·interface-contract; REQ-088 operator_report 집계·net PnL·KST 경계·CLI report; REQ-089 docs/operations.md 4영역; REQ-090 runtime-flows·project-structure 정합). 14 passed(report+safety_sell), 전체 537 passed/47 skipped 회귀 0, ruff clean. report direct-DB·Loki 실 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-090_observability-baseline-alignment.md b/docs/srs/Documentation/req-090_observability-baseline-alignment.md deleted file mode 100644 index 24673ac..0000000 --- a/docs/srs/Documentation/req-090_observability-baseline-alignment.md +++ /dev/null @@ -1,54 +0,0 @@ -# req-090: 관측성 baseline 문서 정합 — 로깅/관측성·PnL 조회 흐름 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- dcy-029가 런타임 로깅·청산 분류·PnL 조회 흐름을 바꾸므로 baseline 문서를 실제 구현과 정합시킨다. (dcy-029 FR-6) - -## Requirement -- `docs/runtime-flows.md`에 다음 흐름을 추가/갱신한다: - - 구조화 로깅 기반(ISO8601 KST 타임스탬프·레벨·JSON/Loki·correlation id, REQ-084). - - 주문 결과 로깅(submit/fill/reject 일관 라인, REQ-085) 및 거부 ERROR 가시성(REQ-087). - - 청산 reason+actor 동반 노출(REQ-086). - - 운영자 조회 CLI(보유/오늘 체결/실현 PnL/대기 주문, REQ-088). -- `docs/project-structure.md`에 신규 로깅 모듈(REQ-084) 책임을 반영한다. -- baseline 문서는 전체 흐름을 복제하지 않고 구현된 제약·진입점만 정합 수준으로 기술한다. - -## Acceptance Criteria -1. Given runtime-flows.md, When 갱신 후, Then 구조화 로깅·correlation id 흐름이 기술돼 있다. -2. Given runtime-flows.md, When 갱신 후, Then 주문 결과 로깅·거부 ERROR·청산 reason+actor·PnL 조회 CLI가 기술돼 있다. -3. Given project-structure.md, When 갱신 후, Then 신규 로깅 모듈이 모듈 트리에 반영돼 있다. -4. Given baseline 문서, When 확인, Then 실제 구현(REQ-084~088)과 모순이 없다. - -## Impacted Area -- Docs: docs/runtime-flows.md, docs/project-structure.md -- Modules: 없음(문서) -- Tests: 없음(문서 — verification은 정합 리뷰) - -## Notes -- 본 REQ는 P1 batch 구현 완료 후 정합한다(REQ-084~088 결과 반영). -- 출처: dcy-029 FR-6. - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-6로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-046 release-candidate, dcy-029 P1). 근거: 단위/문서 AC 매핑(REQ-086 reason+actor 로그·interface-contract; REQ-088 operator_report 집계·net PnL·KST 경계·CLI report; REQ-089 docs/operations.md 4영역; REQ-090 runtime-flows·project-structure 정합). 14 passed(report+safety_sell), 전체 537 passed/47 skipped 회귀 0, ruff clean. report direct-DB·Loki 실 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Documentation/req-094_event-publisher-reliability-baseline-alignment.md b/docs/srs/Documentation/req-094_event-publisher-reliability-baseline-alignment.md deleted file mode 100644 index 3c55626..0000000 --- a/docs/srs/Documentation/req-094_event-publisher-reliability-baseline-alignment.md +++ /dev/null @@ -1,101 +0,0 @@ -# req-094: 이벤트 발행 신뢰성 baseline 문서 정합 (runtime-flows / project-structure / operations 갱신) - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: project maintainers - -## Intent -- REQ-091(발행 재시도+재연결), REQ-092(idle keepalive), REQ-093(DLQ 가시성/emit 경보) 구현 후 `docs/runtime-flows.md`와 `docs/project-structure.md`의 `슬롯 이벤트 발행/구독` 및 `데몬 lifecycle` 흐름 기술이 구현과 어긋나게 된다. 또한 `docs/operations.md`에 DLQ 관측/경보 운영 절차가 신규 필요하다. 구현 완료 시점에 세 baseline 문서를 실제 구현과 정합시킨다. - -## Requirement -- REQ-091~093 구현 완료 후, 아래 baseline 문서를 실제 구현 결과와 정합시킨다. - - `docs/runtime-flows.md`: `슬롯 이벤트 발행/구독` 흐름에 재시도/강제 재연결 경로, idle keepalive 동작, DLQ 유입 관측성을 반영한다. - - `docs/runtime-flows.md`: `데몬 lifecycle` 흐름에 keepalive 틱 연동(OQ-1 결정 결과)을 반영한다. - - `docs/project-structure.md`: keepalive 컴포넌트 또는 관측성 훅이 새 모듈/함수로 추가된 경우 구조 기술을 갱신한다. - - `docs/operations.md`: DLQ 가시성/경보(OQ-3 결정 결과 반영), 운영자 조치 절차, 경보 임계값 기준을 신규 기재한다. -- 문서 갱신은 배포 wire 계약(`docs/interface-contract.md`)을 변경하지 않는다. AMQP 토폴로지·큐·exchange·라우팅 키 계약은 불변이며 본 REQ 범위 밖이다. - -## Acceptance Criteria -1. Given REQ-091~093 구현이 완료된 상태에서, When `docs/runtime-flows.md`를 확인하면, Then `슬롯 이벤트 발행/구독` 항목이 재시도/강제 재연결 경로와 idle keepalive를 포함하여 실제 구현과 일치한다. -2. Given OQ-1(keepalive 실행 위치)이 batch design에서 확정된 후, When `docs/runtime-flows.md`의 `데몬 lifecycle` 흐름을 확인하면, Then keepalive 틱 연동이 결정된 구현 방식대로 기술되어 있다. -3. Given OQ-3(DLQ 가시성 구현 방식)이 batch design에서 확정된 후, When `docs/operations.md`를 확인하면, Then DLQ 유입/깊이 관측 방법과 운영자 조치 절차가 신규 기재되어 있다. -4. Given `docs/project-structure.md`를 확인할 때, When keepalive 또는 관측성 훅이 신규 모듈로 추가된 경우, Then 해당 모듈이 구조 문서에 누락 없이 반영되어 있다. -5. Given `docs/interface-contract.md`를 확인할 때, When 본 REQ 작업이 완료되면, Then AMQP 토폴로지·큐·exchange·라우팅 키 기술이 변경되지 않았다(wire 계약 무회귀). - -## Impacted Area -- Docs: docs/runtime-flows.md(발행/keepalive 흐름 갱신), docs/project-structure.md(신규 컴포넌트 반영), docs/operations.md(DLQ 운영 절차 신규). docs/interface-contract.md는 불변. -- Modules: 없음 (문서 전용 REQ). -- Tests: 없음. 단, batch verification 시 문서-구현 정합 체크리스트 수동 검증. - -## Notes -- 출처: dcy-031 §4(영향 범위 — 문서 영향), NFR-1(wire 계약 무회귀). -- 이 REQ는 문서 전용이며 코드 변경 없이 구현된다. batch 편성 시 REQ-091~093와 같은 batch에 묶어 구현 완료 후 문서 갱신 단계로 처리한다. -- OQ-1·OQ-3 결정 전에는 해당 항목(AC-2, AC-3)을 확정할 수 없다. batch design 완료 후 갱신 내용이 확정된다. -- `docs/interface-contract.md`는 AMQP wire 계약 문서이며 trex-orb-v1·trex-tsl-v1에 파급 영향이 있다. 본 주기에서 wire 계약을 변경하지 않으므로 이 파일은 본 REQ 범위 밖이다(SDLC 교차 영향 사안 해당 없음). - -## Change Log - -### CHG-20260619-01 -- Date: 2026-06-19 -- Author: trex-core-dev - -#### Change Summary -- dcy-031 §4(문서 영향 범위)로부터 최초 생성(Proposed). REQ-091~093 구현 후 baseline 정합 문서 REQ. - -#### Intent -- 이벤트 발행 신뢰성 구현 후 runtime-flows / project-structure / operations baseline 문서 갱신을 backlog에 정규화한다. - -#### Acceptance Criteria Delta -- Added: - - AC-1: runtime-flows 발행/keepalive 경로 정합 - - AC-2: OQ-1 결정 결과 데몬 lifecycle 반영 - - AC-3: OQ-3 결정 결과 operations.md DLQ 절차 신규 - - AC-4: project-structure 신규 컴포넌트 반영 - - AC-5: interface-contract.md 불변 확인 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/project-structure.md, docs/operations.md -- Modules: - - 없음 -- Tests: - - 없음 (수동 검증) - -#### Delivery Trace Delta -- Batches: - - none (draft-req 단계, batch 미편성) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-091~093과 함께 단일 batch 편성 예정 - -#### Validation Plan -- batch verification 단계에서 docs/runtime-flows.md · docs/project-structure.md · docs/operations.md의 변경 내용이 구현과 일치하는지 수동 체크리스트로 확인. docs/interface-contract.md 불변 diff 확인. - -### CHG-20260619-02 -- Date: 2026-06-19 -- Author: 팀장 (confirm-req) - -#### Change Summary -- Proposed → **Approved**. 문서 전용 REQ. 필수 필드 충족, source dcy-031 정합. AC-2/AC-3은 OQ-1/OQ-3 design 확정 후 충족(구현 후 정합 단계라 승인 비차단). - -#### Intent -- delivery input 승인. REQ-091~093 구현 후 baseline 문서 정합을 같은 batch에서 처리. - -#### Acceptance Criteria Delta -- Added: 없음 -- Updated: 없음 -- Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-091~093과 함께 단일 batch 편성 예정 diff --git a/docs/srs/Documentation/req-097_alert-capability-spike.md b/docs/srs/Documentation/req-097_alert-capability-spike.md deleted file mode 100644 index dc63fa4..0000000 --- a/docs/srs/Documentation/req-097_alert-capability-spike.md +++ /dev/null @@ -1,74 +0,0 @@ -# req-097: 알람(alert) 능력 검토 스파이크 산출물 - -- Status: Approved -- Type: Documentation -- Priority: Medium -- Owner: trex-core-dev - -## Intent -- trex-core에는 현재 구조화된 알람(alert) 채널이 없다. 운영자 표면은 CLI `trex-core report`와 구조화 로그가 전부이며, 이상 징후(체결가 백필 발생, price≤0 체결 등)를 운영자에게 즉시 푸시로 알리는 메커니즘이 부재한다. -- 본 REQ는 알람 능력 도입 여부와 최소 구현 범위를 결정하기 위한 스파이크(설계 분석) 산출물 작성을 요구한다. 실제 알람 구현 포함 여부는 이 산출물을 근거로 design review에서 사장님이 최종 결정한다. -- 본 REQ 자체는 "분석 산출물 제출"까지가 요구사항이며, 알람 구현은 설계 분석 결과에 따라 조건부로 포함 또는 후속 이월된다. - -## Requirement -- trex-core-dev는 알람(alert) 채널 후보(아래 최소 3가지)에 대해 장단점·최소 구현 범위·M2 일정 적합성을 분석한 설계 스파이크 문서를 작성해야 한다. - - 후보 A: 구조화 로그 기반 (현재 Loki JSON 로그를 알람 소스로 활용 + 로그 기반 외부 알림 파이프라인) - - 후보 B: AMQP 이벤트 기반 (trex.events exchange 또는 신규 exchange에 알람 이벤트 발행 — wire 계약 영향 검토 필수) - - 후보 C: 외부 푸시 직접 연동 (Slack/webhook/등) -- 분석 산출물은 `docs/` 아래 별도 파일(또는 design 산출물 디렉토리)로 작성하며, design review 전에 팀장에게 제출되어야 한다. -- 알람 구현이 M2에 포함될 경우 최소 구현 범위(무엇을, 어디서, 어떤 채널로)를 명시해야 한다. - -## Acceptance Criteria - -1. Given draft-batch-design 단계, When trex-core-dev가 알람 스파이크 분석을 수행할 때, Then 후보 A·B·C(이상 3가지)에 대해 각각 장점·단점·최소 구현 범위·M2 일정 적합성을 명시한 문서가 작성된다. -2. Given 후보 B(AMQP 이벤트 기반) 분석, When 설계 문서가 작성될 때, Then wire 계약(slots RPC 스키마, trex.events 메시지 구조 등) 변경 여부와 blast radius(trex-orb-v1, trex-tsl-v1 영향)가 명시된다. wire 계약 변경이 수반되면 팀장에게 즉시 보고하고 멈춘다. -3. Given 분석 산출물이 팀장에게 제출된 후 design review, When 사장님이 알람 구현 범위를 결정할 때, Then "M2 포함" 또는 "후속 이월" 중 하나로 확정되어 REQ-097 Notes에 기록된다. -4. Given 알람 구현이 M2 포함으로 결정된 경우, When 신규 REQ가 생성될 때, Then 본 REQ에서 분석된 채널과 최소 범위를 근거로 구현 REQ가 별도 생성된다(본 REQ는 분석 산출물 REQ로 유지). -5. Given 알람 구현이 후속 이월로 결정된 경우, Then 본 REQ는 산출물 제출로 완료 처리되며, 구현 REQ는 별도 Discovery에서 정의한다. - -## Impacted Area -- Docs: - - `docs/` 아래 신규 알람 스파이크 분석 파일 (design 단계 산출물, 경로 미확정) - - `docs/interface-contract.md` — 후보 B 채택 시 wire 계약 변경 필요 여부 검토 대상 (변경 시 별도 승인 필요) -- Modules: 없음 (본 REQ는 분석 산출물만 요구) -- Tests: 없음 (본 REQ는 문서 산출물; 구현 REQ 생성 시 해당 REQ에서 AC/테스트 정의) - -## Notes -- **[사장님 결정 2026-06-21]** 스파이크 산출물(BAT-048 design.md 알람 섹션) 제출로 AC-1·AC-2 충족 → 본 REQ는 **산출물 완료**(AC-5 경로). **알람 구현은 BAT-048(백필+WARN 로깅) 완료 후 별도 후속 트랙으로 진행**하며, **M2 포함 여부도 그때 재결정**한다(현 시점 M2 미확정). design 권고: 후보 A(로그 기반)=WARN 로그+Loki 알람 룰, 후보 B(AMQP)=wire 계약 변경이라 제외, 후보 C(외부 푸시)=M3. BAT-048 구현 범위는 REQ-095+REQ-096만. -- **조건부 구현**: 본 REQ 완료(AC-1~2 충족, 분석 산출물 제출)는 알람 구현 REQ 생성의 선행 조건이다. 알람 구현 포함 여부(AC-3)는 사장님 결정 사안이며 design review 게이트에서 확정된다. trex-core-dev는 이 결정을 임의로 내리지 않는다. -- **wire 계약 변경 주의**: 후보 B(AMQP 이벤트) 분석 시 wire 계약 변경이 수반되면 trex-orb-v1·trex-tsl-v1에 파급 효과가 있다. 이 경우 즉시 팀장에게 보고하고 멈춘다(CLAUDE.md 교차 영향 사안 정책). -- FR-3 WARN 로깅(REQ-096)은 알람 채널 채택 여부와 무관하게 M2 필수 항목이며, 본 REQ와 독립적으로 진행된다. -- Discovery: dcy-032_20260621_fill-missing-price-cost-basis-backfill.md (FR-4, OQ-1) - -## Change Log - -### CHG-20260621-01 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- 최초 생성 (draft-req dcy-032) - -#### Intent -- 알람 능력 도입 여부를 결정하기 위한 스파이크 산출물 요구사항 신규 정의. 실제 구현 포함 여부는 design review 후 사장님 결정. - -#### Acceptance Criteria Delta -- Added: - - AC-1~5 신규 정의 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/ 신규 스파이크 분석 파일, docs/interface-contract.md 검토 대상 -- Modules: 없음 -- Tests: 없음 - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후 편성) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (최초 생성) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: design review 후 AC-3 결과를 Notes에 반영 필요 diff --git a/docs/srs/Documentation/req-101_alarm-baseline-alignment.md b/docs/srs/Documentation/req-101_alarm-baseline-alignment.md deleted file mode 100644 index bc9bf50..0000000 --- a/docs/srs/Documentation/req-101_alarm-baseline-alignment.md +++ /dev/null @@ -1,96 +0,0 @@ -# req-101: 알람 발신 흐름 baseline 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: Medium -- Owner: trex-core-dev - -## Intent -- dcy-033 요구에 따라 `docs/runtime-flows.md`에 trex-core 알람 발신 흐름을 추가하고, `docs/interface-contract.md`의 알람 채널 섹션(REQ-098/099)이 완성되도록 Change Log를 갱신해 baseline 정합을 완료한다. -- REQ-098·099·100 구현으로 변경되는 아키텍처 흐름(알람 발행 경로)이 baseline 문서에 반영되지 않으면 향후 Discovery·batch design에서 오래된 문서를 참조하는 오류가 생긴다. - -## Requirement -- `docs/runtime-flows.md`에 `Flow: 알람 발신 (trex.alerts, dcy-033)` 섹션을 추가해야 한다. 섹션에는 다음 내용이 포함되어야 한다. - - 알람 발생 → `AlarmEmitter.publish_alarm()` → `MessagingTransport.publish_alarm_event()` → `trex.alerts` exchange → routing key `alarm.{severity}.{source}` 흐름. - - best-effort 비차단 정책: 발행 실패 시 ERROR 로그 후 core 본업 계속. - - transport 미연결 시 silent skip. - - trex-core CRITICAL code 8종 발생 지점 목록(코드 이름과 모듈 경로). - - 기존 `trex.events` 발행 흐름과의 차이(알람 채널은 독립 exchange, mandatory=False, DLQ 없음). -- `docs/interface-contract.md`의 Change Log에 dcy-033/REQ-098~101 배치로 인한 알람 채널 추가 항목을 기록해야 한다. -- 두 문서의 `Last Updated (KST)` 헤더를 구현 batch 완료 시점으로 갱신해야 한다. - -## Acceptance Criteria - -1. Given REQ-098·099·100이 구현 완료됐을 때, When `docs/runtime-flows.md`를 검토하면, Then `Flow: 알람 발신 (trex.alerts, dcy-033)` 또는 동등한 제목의 섹션이 존재해야 한다. -2. Given 해당 섹션이 추가됐을 때, When 내용을 확인하면, Then AlarmEmitter → MessagingTransport → trex.alerts exchange의 발행 흐름과 best-effort 비차단 정책이 명시되어야 한다. -3. Given 섹션이 추가됐을 때, When core CRITICAL code 목록을 확인하면, Then ALARM-INVENTORY §A core 행의 8개 code(`fill_price_unresolvable`, `sell_skipped_no_balance`, `core_holding_broker_flat`, `ledger_delivery_failed`, `kill_switch_fired`, `publish_unavailable_after_reconnect`/`dlq_publish_failed`, `portfolio_hub_unavailable`, `schema_verification_failed`/`migration_failed`)와 발생 모듈 경로가 모두 기술되어야 한다. -4. Given 섹션이 추가됐을 때, When `trex.events` 발행 흐름과 비교하면, Then 알람 채널(`trex.alerts`)이 별도 exchange이고 `mandatory=False`·DLQ 없는 점이 명시되어야 한다(기존 흐름 섹션 내용은 변경 없음). -5. Given REQ-098~101 구현이 완료됐을 때, When `docs/interface-contract.md`의 Change Log를 확인하면, Then dcy-033/알람 채널 추가에 해당하는 항목이 기록되어야 한다. -6. Given 두 baseline 문서가 갱신됐을 때, When `Last Updated (KST)` 헤더를 확인하면, Then 구현 batch 완료 시점(KST)으로 갱신되어야 한다. - -## Impacted Area -- Docs: - - `docs/runtime-flows.md` — 알람 발신 흐름 섹션 추가(기존 섹션 불변) - - `docs/interface-contract.md` — Change Log 항목 추가, Last Updated 갱신 -- Modules: 코드 변경 없음(이 REQ는 문서 정합만) -- Tests: 문서 전용 REQ이므로 코드 테스트 불필요. batch verification에서 섹션 존재·내용 수동 검토. - -## Notes -- 이 REQ는 REQ-098·099·100 구현이 완료된 동일 batch에서 함께 처리한다(문서는 구현 후 정합). -- `docs/runtime-flows.md`의 기존 섹션(데몬 lifecycle, slots RPC, intent admission, 이벤트 발행/구독, safety-sell 등)은 변경하지 않는다. -- dcy-033 FR-3(baseline 갱신) 대응. -- dcy-033 성공기준 S-3(기존 wire 계약 무회귀) — 이 REQ 자체는 문서 전용이지만 기존 섹션 diff=0이 AC-4에서 암묵 검증된다. - -## Change Log - -### CHG-20260621-02 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- Status: Approved → Implemented. BAT-049 confirm-batch-verification 팀장 승인(QC PASS-WITH-NOTES blocker 0 + 팀장 실인프라 e2e 통과). docs/runtime-flows.md 알람 발신 흐름 섹션 + docs/interface-contract.md Change Log 추가 확인. - -#### Delivery Trace Delta -- Batches: BAT-049 (release-candidate) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no - -### CHG-20260621-01 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- REQ-101 신규 생성(Proposed). dcy-033 FR-3 대응: 알람 발신 흐름 baseline 문서 정합. - -#### Intent -- REQ-098~100 구현으로 도입된 알람 발신 흐름을 runtime-flows.md와 interface-contract.md에 반영해 baseline을 최신 상태로 유지한다. - -#### Acceptance Criteria Delta -- Added: AC-1~6 (신규 생성) -- Updated: (없음) -- Removed: (없음) - -#### Impacted Area Delta -- Docs: - - `docs/runtime-flows.md` 알람 발신 섹션 추가 - - `docs/interface-contract.md` Change Log + Last Updated 갱신 -- Modules: 없음 -- Tests: 없음(문서 전용) - -#### Delivery Trace Delta -- Batches: (미정, M2 이후 큐잉 — REQ-098~100과 동일 batch) -- Releases: (미정) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-098~100 구현 batch에서 마지막 단계로 처리 - -#### Validation Plan -- `docs/runtime-flows.md`에 알람 발신 섹션 존재 확인(grep) -- 섹션 내용 수동 검토(AlarmEmitter 흐름, 8 code 목록, mandatory=False, DLQ 없음) -- `docs/interface-contract.md` Change Log 항목 추가 확인 -- 기존 섹션 diff=0 확인(git diff 수동 검토) diff --git a/docs/srs/Documentation/req-122_operator-broker-truth-hotfix-baseline-alignment.md b/docs/srs/Documentation/req-122_operator-broker-truth-hotfix-baseline-alignment.md deleted file mode 100644 index 434497f..0000000 --- a/docs/srs/Documentation/req-122_operator-broker-truth-hotfix-baseline-alignment.md +++ /dev/null @@ -1,127 +0,0 @@ -# REQ-122: operator broker truth HOTFIX baseline 및 remediation 문서 정합 - -- Status: Implemented -- Type: Documentation -- Priority: High -- Owner: trex-core-dev - -## Intent -- fetched credential source, balance failure semantics 및 managed/non-managed divergence의 의미가 runtime, interface, operations baseline에 없으면 운영자가 dry-run 결과를 잘못 해석하거나 승인 없는 live sync를 실행할 수 있다. -- 구현된 계약을 기준으로 operator triage와 release gate를 문서화하되, production live sync 실행 자체는 이 REQ에 포함하지 않는다. - -## Requirement -- 시스템 문서는 구현 완료 후 daemon/operator fetched credential source parity, sanitized broker-read failure semantics, token/broker lifecycle 운영 제약, reconciliation decision code 및 managed non-holding triage 절차를 실제 구현과 일치하게 기술해야 한다. -- 운영 문서는 dry-run → broker/ledger/slot cross-check → 사장님 명시 approval → single-target `sync --live --yes` → re-reconcile 순서를 release gate로 명시하고, live sync 실행 명령이나 특정 production target을 승인·자동화하지 않아야 한다. -- 문서의 예시, 로그 키 및 fixture 설명은 credential, token, app secret, raw account number 및 raw credential payload를 포함하지 않아야 한다. - -## Acceptance Criteria -1. Given REQ-119~121 구현과 검증 evidence, When `docs/runtime-flows.md`를 갱신하면, Then daemon/operator broker-read 공통 provider, failure가 empty balance가 아님, reconciliation의 `unmanaged_position`/`managed_non_holding_divergence` 분기와 no-auto-remediation 경계가 구현과 일치한다. -2. Given REQ-119~121 구현과 검증 evidence, When `docs/interface-contract.md`를 갱신하면, Then operator CLI의 broker-read failure exit semantics, safe observable fields 및 repository/reconciliation 계약 변화가 구현과 일치하고 외부 wire 계약의 변경 여부가 명시된다. -3. Given 운영자 remediation 문서, When `docs/operations.md`를 갱신하면, Then dry-run evidence, source/account reference masking, failure hold, broker/ledger/slot cross-check 및 explicit release approval gate가 순서대로 제시되고 승인 없는 `sync --live` 실행을 지시하지 않는다. -4. Given 문서 예시와 로그/JSON 샘플, When secret scan 또는 리뷰를 수행하면, Then token, authorization header, app key/app secret, raw credential payload 및 raw account number가 없다. -5. Given 문서의 명령/decision code/reference, When implementation tests 및 CLI help와 대조하면, Then 존재하지 않는 명령·code·source mode를 사실처럼 기술하지 않고, unresolved lifecycle implementation choice는 검증된 정책으로 확정된 뒤에만 구체화한다. - -## Impacted Area -- Docs: `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, `docs/project-structure.md`, `docs/srs/index.md`, REQ-119~121 -- Modules: none (documentation-only requirement; implementation facts are supplied by REQ-119~121) -- Tests: markdown link/reference check; documented CLI/help and decision-code review; secret scan; implementation-evidence cross-check - -## Notes -- Source Discovery: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (FR-6, S-4; GitHub #39/#40/#41). -- OQ-2 lifecycle mechanism is documented only after an evidence-backed implementation decision; this REQ does not mandate process-local reuse, shared cache, or another mechanism. -- OQ-3 production live sync target is a release approval record, not a documentation authorization or execution task. - -## Change Log - -### CHG-20260714-01 -- Date: 2026-07-14 -- Author: delivery-runner - -#### Change Summary -- dcy-037 #39/#40/#41 HOTFIX의 baseline/운영 정합 문서를 독립 Proposed REQ로 초안화했다. - -#### Intent -- 안전한 broker truth triage를 구현 사실 및 release gate와 동기화한다. - -#### Acceptance Criteria Delta -- Added: - - runtime/interface/operations alignment, approval-gated remediation, secret-safe examples 및 evidence-only lifecycle documentation. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - runtime, interface, operations, project structure 및 SRS traceability. -- Modules: - - none. -- Tests: - - markdown/reference, CLI/code evidence 및 secret scan review. - -#### Delivery Trace Delta -- Batches: - - none (draft only; dcy-037 emergency scope의 향후 입력). -- Releases: - - none (release gate 문서화만 포함; production live sync 실행 없음). - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-119~121 구현 verification evidence가 준비된 뒤 baseline 문서를 갱신하고 cross-check한다. - -#### Validation Plan -- implemented CLI/help, structured decision codes, test evidence 및 markdown 링크를 대조하고 secret scan을 통과시킨다. - -### CHG-20260714-02 -- Date: 2026-07-14 -- Author: Justin (TREX lead) - -#### Change Summary -- Proposed에서 Approved로 승인했다. - -#### Intent -- dcy-037의 runtime/interface/operations alignment와 release-gated remediation 경계를 implementation evidence 이후 갱신할 delivery input으로 확정한다. - -#### Acceptance Criteria Delta -- Added: - - 없음. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - 없음. -- Modules: - - 없음. -- Tests: - - 없음. - -#### Delivery Trace Delta -- Batches: - - dcy-037의 #39/#40/#41 단일 emergency batch (아직 미생성). -- Releases: - - none; documentation은 live sync 실행을 승인하지 않는다. - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: implementation/verification evidence가 준비된 뒤 baseline과 runbook을 갱신한다. - -#### Validation Plan -- batch verification과 독립 QC에서 actual CLI/help, decision code, tests, markdown links 및 secret scan으로 AC를 재검증한다. - -### CHG-20260715-03 -- Date: 2026-07-15 -- Author: TREX lead - -#### Change Summary -- Approved → Implemented (`confirm-req-implemented`). - -#### Delivery Trace -- BAT-057 verification confirmed; PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`. -- Independent QC: PASS-WITH-NOTES, acceptance blocker 0. Production live sync/deploy remains a separate release gate. diff --git a/docs/srs/Exception/req-014_order-handoff-durability-reconciliation.md b/docs/srs/Exception/req-014_order-handoff-durability-reconciliation.md deleted file mode 100644 index eead15d..0000000 --- a/docs/srs/Exception/req-014_order-handoff-durability-reconciliation.md +++ /dev/null @@ -1,189 +0,0 @@ -# req-014: 주문 핸드오프 durability + reconciliation - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- 집행 도중 크래시가 나도 중복 주문 없이 일관 상태로 복구되도록, 옵션 A(pending 슬롯=실행큐 + broker_order_id 멱등)에 재기동 reconciliation 을 더한다. - -## Requirement -- 재기동 시 코어가 `broker_order_id` 가 채워진 pending 슬롯을 브로커 주문 상태와 대조해 reconciliation 한다: 체결됨→해당 전이(holding/closed), 거부/취소됨→롤백(idle), 미체결→계속 poll. `broker_order_id` 가 비어 있는 pending 슬롯은 미송신으로 보고 정상 claim 경로로 재개한다. 동일 슬롯/주문에 대해 중복 주문이 발생하지 않는다. - -## Acceptance Criteria -1. Given 주문 송신 후 broker_order_id 기록까지 된 상태에서 크래시, When 재기동 reconciliation, Then 같은 슬롯에 재주문하지 않고 브로커 상태로 전이를 정리한다(중복 주문 0). -2. Given 주문 송신 전(broker_order_id NULL) 크래시, When 재기동, Then 정상 claim 으로 재개해 1회만 송신한다. -3. Given 브로커에서 이미 체결된 주문, When reconciliation, Then 슬롯이 holding/closed 로 수렴한다. -4. Given 동일 집행이 두 번 수행(루프 중복 틱), When 점검, Then broker_order_id 멱등 가드로 중복 주문이 생기지 않는다. (NFR-3) - -## Impacted Area -- Docs: docs/runtime-flows.md(reconciliation 흐름) — REQ-015 -- Modules: trex_core 집행/복구 컴포넌트(REQ-013 루프에 reconcile 단계), 브로커 주문 상태 조회 재사용 -- Tests: 크래시 주입(송신 전/후) 후 reconciliation, 중복 주문 0, 멱등 재틱 - -## Notes -- 옵션 A 기반(별도 outbox 테이블 없음). 부분체결/재시도 본격화 시 옵션 B 승격은 후속. -- 출처: dcy-003 FR-5, NFR-3. 핵심 안전(R-1) 완화의 실현. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-003 FR-5 로부터 최초 생성(Proposed). NFR-3 흡수. - -#### Intent -- durable 핸드오프 + reconciliation(크래시 복구·멱등) 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 송신후 크래시 reconciliation, 송신전 크래시 재개, 체결 수렴, 멱등 재틱 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md -- Modules: - - 집행/복구 컴포넌트(reconcile) -- Tests: - - 크래시 주입 reconciliation 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- 크래시 주입(송신 전/후) reconciliation + 중복 주문 0 테스트로 검증. -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate). - -#### Intent -- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-004 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~004) - -#### Validation Plan -- BAT-004 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013/issue #9, OQ-2 Alert-only) pending 주문 stale 워치독 추가: dispatched-pending 슬롯이 `TREX_PENDING_STALE_THRESHOLD`(초, 0=비활성) 초과로 머물면 WARN 로그 + `ExecutionResult.stale_pending` 카운터로 가시화한다. **슬롯/주문 상태는 변경하지 않는다**(auto-remediation 후속). 취소 감지 누락(R-1) 등 미지 원인 무한 갇힘이 silent 로 남지 않게 하는 reconciliation 안전망. BAT-017 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 자동 취소 감지(REQ-012)가 놓치는 케이스까지 포함해, 비정상적으로 오래 pending 인 주문을 운영자에게 노출하는 durability 안전망 제공. - -#### Acceptance Criteria Delta - -- Added: - - pending stale 임계 초과 시 WARN+카운터 가시화(상태 미변경, Alert-only). 임계 env `TREX_PENDING_STALE_THRESHOLD`(0=비활성). - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_check_stale_pending`, ExecutionService threshold), `trex_core/runtime/config.py`(`pending_stale_threshold_sec`/env), `trex_core/runtime/daemon.py`(배선) -- Tests: tests/application/test_execution_cancel.py(stale WARN + 비활성) - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-017 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가, 기존 reconcile/poll 동작 불변) -- Status Recommendation: Implemented (change-req → BAT-017 재검증) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 임계 초과 pending 주입 → WARN+카운터, 상태 미변경 / 비활성(default) → 무경고 단위. diff --git a/docs/srs/Exception/req-019_ledger-outbox-durability-idempotency.md b/docs/srs/Exception/req-019_ledger-outbox-durability-idempotency.md deleted file mode 100644 index 2dc936e..0000000 --- a/docs/srs/Exception/req-019_ledger-outbox-durability-idempotency.md +++ /dev/null @@ -1,229 +0,0 @@ -# req-019: ledger outbox durability + correlation_id 멱등 - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- portfolio-hub 일시 장애에도 원장 기록이 유실되거나 중복되지 않도록, 체결 시 원장 기록을 ledger outbox(work item)에 적재하고 재시도 루프가 portfolio-hub write 를 수행하며 correlation_id 로 멱등을 보장한다. - -## Requirement -- 체결 시 원장 기록을 ledger outbox(work item)에 적재하고, 재시도 루프가 portfolio-hub write 를 시도한다(trex-orbit `LedgerWriter`/recovery work item 패턴). 원장 기록은 슬롯 전이와 분리되어(전이 우선, 기록 eventually-consistent) 일시 장애에도 유실 없이 결국 기록된다. -- 멱등 키 `correlation_id = {asset_id}:{order_id}:{execution_id}` 로 로컬 dedup 하고, portfolio-hub 중복/409 를 안전 처리해 동일 체결의 중복 원장이 0 이 되게 한다. -- write 결과는 outcome 어휘 `completed` / `duplicate_completed` / `retryable_failure` / `conflict_blocked` 로 분류한다. `retryable_failure` 는 재시도하고, `conflict_blocked`(409)는 운영자 해소 대상으로 보류한다. - -## Acceptance Criteria -1. Given 동일 execution_id 의 원장 기록을 2회 수행, When outbox 처리, Then 원장에 중복 거래가 생기지 않는다(correlation_id dedup, 중복 0). (NFR-2) -2. Given portfolio-hub write 가 일시 실패(retryable), When 재시도 루프, Then 결국 1건이 기록되고(`completed`) 슬롯 전이 일관성이 유지된다(무유실). (NFR-2) -3. Given portfolio-hub 가 409 충돌을 반환, When outbox 처리, Then outcome 이 `conflict_blocked` 로 표시되고 운영자 해소 전까지 자동 재시도로 무한 반복하지 않는다. -4. Given 이미 기록된 work item 재처리, When outbox 처리, Then outcome 이 `duplicate_completed` 로 분류되고 원장은 변하지 않는다. -5. Given 체결 직후 portfolio-hub 불가, When 슬롯 전이, Then 슬롯 전이는 기록 성공 여부와 무관하게 진행된다(분리). - -## Impacted Area -- Docs: docs/runtime-flows.md(ledger outbox/재시도 흐름), docs/data-model.md(outbox work item/멱등 키) — REQ-020 에서 정합 -- Modules: 신규 ledger outbox(work item store) + 재시도 워커, REQ-018 의 fill 기록 경로가 직접 write 대신 outbox 적재로 연결 -- Tests: 동일 execution_id 중복 0, 실패 주입 후 재시도로 결국 기록, 409→conflict_blocked, duplicate_completed, 전이/기록 분리 - -## Notes -- 출처: dcy-004 FR-3, FR-4, NFR-2, R-1(High, 원장 유실/중복). -- BAT-004 의 broker handoff durability(REQ-014, 옵션 A)와 유사 구조이나 ledger 전용 outbox 다. 부분체결/정밀 세금 모델은 본 REQ 범위 밖(후속). -- 멱등/무유실(NFR-2)을 별도 REQ 로 승격하지 않고 본 REQ 의 AC-1/AC-2 로 흡수한다. -- conflict_blocked 운영자 해소 절차의 상세(알림/대시보드)는 운영 단계 후속. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-004 FR-3/FR-4 로부터 최초 생성(Proposed). NFR-2(멱등/무유실) 흡수. - -#### Intent -- 원장 기록 무유실+멱등(outbox 재시도, correlation_id dedup, outcome 어휘)을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 중복 0, 실패 재시도 결국 기록, conflict_blocked, duplicate_completed, 전이/기록 분리 5개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/data-model.md -- Modules: - - ledger outbox store + 재시도 워커 -- Tests: - - 멱등/무유실/conflict 주입 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- 동일 execution_id 중복 0 + 원장 실패 주입 후 재시도 결국 기록 + 409 conflict_blocked 테스트로 검증. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- ledger outbox durability 와 correlation_id 멱등 요구사항을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - 없음 -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반. - -#### Intent -- ledger outbox durability + correlation_id 멱등 + outcome 분류 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 (변경은 상태 전이만) - -#### Delivery Trace Delta -- Batches: - - BAT-006 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_execution_ledger.py::test_duplicate_execution_id_results_in_zero_duplicate_in_ledger. -- AC-2: tests/application/test_execution_ledger.py::test_retryable_failure_then_eventually_completes + tests/application/test_ledger_outbox.py::test_writer_retries_until_success. -- AC-3: tests/application/test_execution_ledger.py::test_conflict_blocks_without_auto_retry + tests/application/test_ledger_outbox.py::test_writer_marks_conflict_blocked_and_does_not_retry. -- AC-4: tests/application/test_execution_ledger.py::test_duplicate_completed_when_reprocessing_terminal_item + test_duplicate_execution_id_results_in_zero_duplicate_in_ledger. -- AC-5: tests/application/test_execution_ledger.py::test_slot_transition_proceeds_when_ledger_is_down. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-015/issue #13) ledger outbox 에 영속/백오프/dead-letter 도입: 영속 오류(`LedgerPermanentError`)→터미널 `delivery_failed` 즉시; retryable→**지수 백오프+jitter**(`next_retry_at`)·**max_attempts** 초과 시 `delivery_failed`(dead-letter). `list_pending` 이 `next_retry_at > now` 행을 건너뜀(head-of-line 보호). status enum 에 `delivery_failed`·`next_retry_at` 컬럼 마이그레이션(007). 백오프 env 설정(base/cap/max-attempts). BAT-019 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 미등록 포트폴리오 404 의 초당 무한 재시도(실측 930회) 제거 + 일시 오류의 절제된 백오프 + FIFO 잠식 방지. - -#### Acceptance Criteria Delta - -- Added: - - 영속→`delivery_failed`(재시도 0), retryable→백오프+max_attempts→`delivery_failed`(dead-letter), `list_pending` 백오프 스킵, status enum/`next_retry_at` 마이그레이션, 백오프 env. - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_outbox.py`(LedgerWriter 백오프/dead-letter, InMemory outbox), `trex_core/adapters/persistence/postgresql.py`(next_retry_at/status/list_pending skip), `trex_core/adapters/persistence/sql/007_*.sql`(신규), `trex_core/runtime/config.py`(env)+daemon 배선 -- Tests: tests/application/test_ledger_outbox_backoff.py, tests/adapters/persistence/test_postgresql_ledger_outbox.py(delivery_failed/backoff skip), test_ledger_outbox.py/test_execution_ledger.py(백오프 정합) - -#### Delivery Trace Delta - -- Batches: BAT-019 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-019 에서 완료) -- Existing Implementation Invalidated: yes (무한 재시도·백오프 부재 경로) -- Status Recommendation: Implemented (change-req → BAT-019 재검증) -- Follow-up Action: confirm-req-implemented(BAT-019). - -#### Validation Plan - -- BAT-019: permanent 즉시 터미널, backoff 스킵, max_attempts dead-letter, PG 마이그레이션 round-trip 단위/인프라 게이트. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-018) `LedgerWriter` 에 `SnapshotStore` 주입 + option D write-back: `new`·`duplicate` outcome 의 `*_after`(절대 권위값)를 `update_cash_position` 으로 반영(OQ-2 b, best-effort — 실패해도 ledger 멱등·청산 무차단). 추정 현금 오버레이용 `LedgerOutbox.pending_cash_delta()`(미정산 행 cash delta 합) 추가(InMemory + PG). BAT-023 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 체결 → portfolio-hub 권위 cash/quantity 를 스냅샷에 write-back(option D 활성화) + 공백 동안 추정 현금 산출 근거 제공. - -#### Acceptance Criteria Delta - -- Added: - - LedgerWriter 가 new+duplicate 에 best-effort write-back(*_after None 이면 skip). `pending_cash_delta()` 로 미정산 추정 합 제공. - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_outbox.py`(LedgerWriter write-back/`_writeback`/`trade_cash_delta`/`pending_cash_delta`), `trex_core/adapters/persistence/postgresql.py`(`pending_cash_delta` SQL), `trex_core/runtime/daemon.py`(SnapshotStore 주입) -- Tests: tests/application/test_ledger_option_d.py, tests/adapters/persistence/test_postgresql_ledger_outbox.py::test_pending_cash_delta_on_pg - -#### Delivery Trace Delta - -- Batches: BAT-023 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-023 에서 완료) -- Existing Implementation Invalidated: no (write-back/추정은 additive, 기존 outbox 멱등 불변) -- Status Recommendation: Implemented (change-req → BAT-023 재검증) -- Follow-up Action: confirm-req-implemented(BAT-023). - -#### Validation Plan - -- BAT-023: write-back new/duplicate/skip/best-effort + pending_cash_delta 단위 + PG. diff --git a/docs/srs/Exception/req-023_messaging-delivery-reliability.md b/docs/srs/Exception/req-023_messaging-delivery-reliability.md deleted file mode 100644 index 9176ce0..0000000 --- a/docs/srs/Exception/req-023_messaging-delivery-reliability.md +++ /dev/null @@ -1,154 +0,0 @@ -# req-023: messaging delivery 신뢰성 (effectively-once + durable + DLQ + publisher confirms) - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- RabbitMQ 메시징을 도입하면서 at-least-once 재전달이 intent 중복 실행이나 슬롯 권위 불변식 위반을 일으키지 않도록, idempotency + durable + 단일 consumer FIFO + dead-letter + publisher confirms 로 effectively-once 동작을 보장한다. 진짜 exactly-once 는 추구하지 않는다. - -## Requirement -- intent admission RPC 는 다음 신뢰성 규약을 따른다. - - 메시지/큐는 durable + persistent delivery_mode. - - `correlation_id` = intent 멱등키(dcy-003 패턴, `{asset_id}:{order_id}:{execution_id}` 또는 등가) 로 dedup. - - 슬롯 전이를 Postgres 에 durable commit 한 뒤 ack(manual ack). 재전달 시 dedup 게이트가 단일 실행을 보장한다. - - 단일 consumer + prefetch=1 로 FIFO 순서 보존(trex-core 단일 writer 불변식 A 와 정합). -- 스냅샷 RPC 는 read-only 멱등이므로 at-least-once + transient 큐/메시지 + manual ack 로 운영하고 별도 dedup 을 강제하지 않는다. -- 이벤트 pub/sub 는 다음 신뢰성 규약을 따른다. - - `trex.events` 는 durable topic exchange. - - 전략별 queue 는 durable + per-strategy 로 down 중 catch-up 가능. 무한 적체 방지를 위해 TTL 또는 max-length 정책을 적용하고 초과분은 drop-oldest 후 스냅샷 RPC 로 reconcile. - - 이벤트 페이로드는 delta 가 아니라 **현재 상태 + 단조 증가 시퀀스(슬롯 version/timestamp)** 를 운반해 last-wins 가 성립하도록 한다(순서 보증 불요). - - 발행은 publisher confirms 로 broker 수락을 확인하고, 발행 실패/거부는 dead-letter 경로로 격리한다. -- 미수신/중복 이벤트 보정은 권위 reconcile 경로인 스냅샷 RPC 가 담당한다(이벤트는 push 최적화이지 단일 진실원이 아님). - -## Acceptance Criteria -1. Given 동일 `correlation_id` 의 intent 메시지가 2회 전달, When intent consumer 가 처리, Then 슬롯 전이는 1회만 일어나고 두 번째는 dedup 으로 무시된다(NFR-2). -2. Given intent consumer 가 슬롯 transition 후 ack 직전 크래시 상황을 시뮬레이션, When 재기동 후 동일 메시지가 재전달, Then 멱등 dedup 으로 슬롯이 추가 전이되지 않고 결국 ack 된다(durable commit + ack 순서). -3. Given intent 큐, When 토폴로지를 검사, Then 큐와 메시지가 durable/persistent 이고 consumer 가 단일 + prefetch=1 로 선언되어 있다. -4. Given 전략 queue 가 down 인 상태에서 trex-core 가 이벤트를 발행, When 전략이 복구되어 queue 를 다시 소비, Then 적체된 이벤트를 catch-up 으로 수신한다(durable per-strategy queue). -5. Given 이벤트 페이로드, When 형식을 검사, Then `current state + 단조 시퀀스` 필드가 포함되어 last-wins 적용이 가능하다. -6. Given trex-core 가 이벤트를 발행, When broker 가 거부/확인 실패, Then publisher confirms 가 실패를 감지하고 메시지를 dead-letter 경로로 격리한다. -7. Given TTL 또는 max-length 가 설정된 이벤트 queue, When 한도를 초과, Then 초과 메시지는 drop-oldest 로 제거되고 후속 스냅샷 RPC reconcile 로 누락이 보정된다. - -## Impacted Area -- Docs: docs/runtime-flows.md(신뢰성/dedup/ack/DLQ 흐름), docs/interface-contract.md(메시지 속성/큐 옵션) — REQ-026 에서 정합. -- Modules: REQ-022 의 어댑터/consumer 에 ack 시점·dedup 게이트·publisher confirms·DLQ 라우팅을 통합. dedup 게이트는 dcy-003 의 멱등 store 재사용. -- Tests: 중복 intent 메시지 dedup, crash-after-commit-before-ack 재처리, durable queue + prefetch=1 토폴로지 검사, per-strategy queue catch-up, 이벤트 페이로드 모양, publisher confirms 실패 DLQ, TTL/max-length drop-oldest + reconcile. - -## Notes -- 출처: dcy-006 FR-4, NFR-2, OQ-3, R-2. -- NFR-2(effectively-once) 는 별도 REQ 로 승격하지 않고 본 REQ AC-1/AC-2 로 흡수한다. -- 진짜 exactly-once 는 추구하지 않는다(분산 메시징 일반 원칙). 멱등 + 권위 reconcile 로 실제 효과를 낸다. -- DLQ 라우팅 키/큐 이름 같은 세부 규약은 batch design 에서 확정한다(본 REQ 는 정책 수준). - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 FR-4 로부터 최초 생성(Proposed). NFR-2(effectively-once) 흡수. - -#### Intent -- messaging delivery 신뢰성(intent dedup, durable+FIFO, 이벤트 catch-up, publisher confirms, DLQ, TTL/max-length) backlog 정규화. - -#### Acceptance Criteria Delta -- Added: - - 중복 intent dedup, crash-after-commit-before-ack 재처리, durable+prefetch=1 토폴로지, per-strategy queue catch-up, 이벤트 페이로드 모양, publisher confirms DLQ, TTL/max-length drop-oldest (총 7개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/interface-contract.md -- Modules: - - adapters/messaging consumer/publisher 의 ack/dedup/DLQ/confirms 통합 -- Tests: - - 신뢰성 7가지 시나리오 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- stub/RabbitMQ 어댑터로 dedup/durable/catch-up/confirms/DLQ/TTL 시나리오를 검증. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- messaging delivery 신뢰성(effectively-once + durable + DLQ + publisher confirms) 요구사항을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: REQ-021/022/025 와 함께 batch 편성 - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- messaging delivery 신뢰성(effectively-once + durable + DLQ + publisher confirms + TTL/max-length)을 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_messaging_intent_consumer.py::test_duplicate_correlation_id_dedups_via_intent_log + tests/adapters/messaging/test_rabbitmq_intent_e2e.py::test_intent_admission_rpc_dedups_duplicate_correlation_id_over_rabbitmq (실 RabbitMQ). -- AC-2: ack-after-handler 코드(Daemon._intent_rpc_loop) + dedup 테스트가 ack 순서 전제 통과. -- AC-3: tests/adapters/messaging/test_rabbitmq_intent_e2e.py::test_intent_queue_topology_uses_dlq_pointer. -- AC-4: topology.STRATEGY_QUEUE_ARGS + per-strategy filter 테스트. -- AC-5: tests/application/test_messaging_event_publisher.py::test_fill_emits_slot_filled_event_with_routing_key_and_state. -- AC-6: RabbitMQTransport._publish_dlq + handler exception NACK + tests/adapters/messaging/test_in_memory_transport.py::test_publish_event_with_no_subscriber_routes_to_dlq. -- AC-7: tests/adapters/messaging/test_topology.py::test_strategy_queue_args_have_ttl_and_max_length. diff --git a/docs/srs/Exception/req-031_position-reconciliation-and-warning.md b/docs/srs/Exception/req-031_position-reconciliation-and-warning.md deleted file mode 100644 index 68f722c..0000000 --- a/docs/srs/Exception/req-031_position-reconciliation-and-warning.md +++ /dev/null @@ -1,173 +0,0 @@ -# req-031: 포지션 정합 검증 + warning 마스킹 - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- portfolio-hub 포지션이 실제 브로커 계좌 포지션과 어긋난 자산을 매매에서 보수적으로 제외해, 틀린 baseline 위에서 매매하는 위험을 막는다. - -## Requirement -- 스냅샷 페치 시 각 자산에 대해 portfolio-hub 포지션과 실제 브로커 계좌 포지션을 **수량 정확 일치**(정수 shares, 허용 오차 없음)로 검증한다. 현금은 대상이 아니다(수량만). -- 브로커 포지션은 KIS 주식잔고조회(실전 `TTTC8434R` / 모의 `VTTC8434R`, `GET /uapi/domestic-stock/v1/trading/inquire-balance`)를 BrokerPort 잔고조회로 조회한다(항목당 `pdno`+`hldg_qty`). paper broker 는 결정론 stub. -- 브로커 잔고는 단일 계좌(불변식 B)라 symbol 단위 집계이므로, portfolio-hub per-portfolio 포지션을 symbol 로 합산해 broker per-symbol `hldg_qty` 와 대조한다. -- 불일치 자산은 `warning` 상태로 표시·응답에서 마스킹하고, 다음 정합 성공 시 자동 해제한다. warning raise 는 매번 로깅한다. - -## Acceptance Criteria -1. Given symbol 집계 수량 불일치, When 정합 검증, Then 해당 자산은 `warning` 으로 표시되고 응답에서 마스킹된다. -2. Given 이전에 `warning` 인 자산이 다음 정합에서 수량 일치, When 재검증, Then `warning` 이 자동 해제되어 다시 응답에 포함된다. -3. Given paper broker, When 정합 검증, Then 결정론 잔고 stub 로 검증이 수행된다. - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- Modules: BrokerPort 잔고조회(KIS 주식잔고조회) 확장, 정합 검증 + `warning` 상태 로직 -- Tests: 수량 불일치 주입, warning 자동 해제, symbol 집계 대조 - -## Notes -- 정확 일치(tolerance 없음) — A-6(유일 매매 주체)상 정확 일치가 정상이며 tolerance 는 실제 이상을 가린다. -- 영속 hold 는 pause(REQ-032)로 분리(자동 warning=일시 / 수동 pause=영속). -- 당일 전량매도분이 `hldg_qty=0` 으로 D-2 까지 잔고에 남을 수 있음(KIS 015 문서) → 0 수량 잔고 처리 규칙은 설계 시 확정. -- 참고: docs/broker/kis/rest/국내주식/주문/계좌/015_v1_국내주식-006_주식잔고조회.md. OQ-8 반영. - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-7 + OQ-8 분해로 최초 생성(Proposed). - -#### Intent -- 포지션 정합 검증 + warning 마스킹/자동 해제 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~3 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005 -- Modules: - - BrokerPort 잔고조회, 정합/warning 로직 -- Tests: - - 불일치 주입, 자동 해제 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req - -#### Validation Plan -- 불일치 주입 → warning 마스킹 → 일치 복귀 → 자동 해제 시퀀스 테스트로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. D-2 0수량 잔고 처리 규칙은 설계 단계 확정(비블로커). - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. 구현 전 재검증 불필요. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- 정합 검증(`reconcile_positions`) + warning 자동 set/clear + `BrokerPort.get_balance()` 확장 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 실 KIS 환경에서 D-2 0수량 잔고 케이스 관찰 후 운영 노트화 권장 (별도 작업). - -#### Validation Plan -- AC-1: tests/application/test_reconciliation.py::test_mismatch_raises_warning_and_masks_asset. -- AC-2: ::test_subsequent_match_auto_clears_warning. -- AC-3: ::test_paper_broker_returns_deterministic_balance. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013/issue #9) 부분 체결 후 외부 취소 처리(REQ-013)가 슬롯 qty 를 체결분만큼 차감하고 잔여를 holding 으로 복귀시켜, 슬롯 잔여 수량이 실 브로커 잔고와 정합하도록 보장한다(과매도 방지). 포지션 정합 검증 로직 자체(`reconcile_positions`)는 코드 변경 없음 — 본 change-req 는 정합 불변식이 유지됨을 명시하는 연계 기록. BAT-017 검증 완료 → Implemented 유지. - -#### Intent - -- 부분 체결+취소에서 slot.qty ↔ 실제 held 수량 정합을 보장해 REQ-031 의 포지션 정합 불변식이 깨지지 않게 한다. - -#### Acceptance Criteria Delta - -- (변경 없음 — reconcile 로직 불변. 부분 취소 회계가 정합 입력을 정확히 유지) - -#### Impacted Area Delta - -- Modules: (없음 — REQ-013 `_on_partial_cancel` 이 잔여 qty 를 정확히 갱신) -- Tests: tests/application/test_execution_cancel.py(잔여 qty=6 정합) - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (reconcile 코드 불변, 회계 정확성은 REQ-013 테스트로 커버) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented (연계 change-req) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 부분 취소 후 slot.qty 가 잔여 held 와 일치(과매도 0). diff --git a/docs/srs/Exception/req-055_ledger-gap-ops-check.md b/docs/srs/Exception/req-055_ledger-gap-ops-check.md deleted file mode 100644 index 6ba3708..0000000 --- a/docs/srs/Exception/req-055_ledger-gap-ops-check.md +++ /dev/null @@ -1,87 +0,0 @@ -# req-055: closed 슬롯 ↔ ledger_outbox 누락 운영 점검 쿼리 - -- Status: Implemented -- Type: Exception -- Priority: Medium -- Owner: project maintainers - -## Intent - -portfolio_id 없는 체결의 ledger silent skip(issue #11)으로 발생하는 회계 누락을 운영자가 탐지·집계할 수 있도록, `closed` 슬롯 중 대응 `ledger_outbox` 행이 없는 케이스를 anti-join 으로 식별하는 읽기 전용 운영 점검을 제공한다(dcy-011 FR-5). `daily_slot` 이 durable 하므로 outbox 마커 행 없이도 영구 탐지가 성립한다(dcy-010 OQ-4 위임). - -## Requirement - -- `find_ledger_gap_slots(dsn, *, trading_date=None)` 읽기 전용 함수를 제공한다(`trex_core/adapters/persistence/ops_checks.py`). -- gap 정의: `status='closed'` 슬롯 중 `portfolio_id IS NULL` 이거나 `ledger_outbox` 에 `asset_id = slot.portfolio_id` 행이 없는 슬롯. -- 결과는 `slot_id`/`trading_date`/`symbol`/`portfolio_id`/`broker_order_id` 를 포함한다. `trading_date` 인자로 일자 범위를 한정할 수 있다(미지정 시 전체). -- 스키마 변경 없음(읽기 전용 SELECT). 주기/온디맨드 실행으로 누락을 탐지·집계할 수 있어야 한다. - -## Acceptance Criteria - -1. Given `closed` 슬롯 중 `portfolio_id IS NULL` 인 슬롯, When `find_ledger_gap_slots`, Then 그 슬롯이 결과에 포함된다(S-4). -2. Given `closed` 슬롯 중 portfolio_id 는 있으나 대응 outbox 행이 없는 슬롯, When 호출, Then 결과에 포함된다. -3. Given `closed` 슬롯 + 대응 `ledger_outbox` 행(asset_id 일치)이 있는 슬롯, When 호출, Then 결과에 포함되지 않는다(정상 케이스 0건). -4. Given `holding` 등 비-closed 슬롯, When 호출, Then 결과에서 제외된다(closed 한정). - -## Impacted Area - -- Docs: `docs/runtime-flows.md`(ledger skip 가시화 + 점검 쿼리 참조) -- Modules: `trex_core/adapters/persistence/ops_checks.py`(신규, 읽기 전용) -- Tests: `tests/adapters/persistence/test_ops_checks.py`(PG 게이트) - -## Notes - -- OQ-4 결정: outbox 마커 행 미도입 → slots↔outbox anti-join 으로 영구 탐지(REQ-019 outbox 스키마 변경 없음). -- 알람/주기 실행 연결은 운영 배포 절차로 다룬다(쿼리/함수가 산출물). - -## Change Log - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- dcy-011(confirmed) → REQ-055 생성(draft-req, Proposed). issue #11 제안 ③: closed↔outbox 대조 운영 점검(FR-5). 기존 REQ 가 다루지 않는 신규 기능이라 신규 REQ(OQ-3 결정). - -#### Intent - -- ledger 누락 영구 탐지 수단을 운영자에게 제공. - -#### Acceptance Criteria Delta - -- Added: AC-1~4 전체 - -#### Delivery Trace Delta - -- Batches: BAT-015 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Status Recommendation: keep-current - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). 필수 필드·구체 AC·dcy-011 FR-5 정합 확인. - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Approved → Implemented (confirm-req-implemented, 근거 BAT-015 release-candidate). `find_ledger_gap_slots` 구현 + PG 게이트 단위(gap 탐지/정상 0건) green. 전체 인프라 게이트 314 passed. - -#### Delivery Trace Delta - -- Batches: BAT-015 (release-candidate) -- Releases: none diff --git a/docs/srs/Exception/req-091_publish-retry-and-force-reconnect.md b/docs/srs/Exception/req-091_publish-retry-and-force-reconnect.md deleted file mode 100644 index 2a4ced1..0000000 --- a/docs/srs/Exception/req-091_publish-retry-and-force-reconnect.md +++ /dev/null @@ -1,105 +0,0 @@ -# req-091: 이벤트 발행 재시도 + 강제 재연결 (idle 후 첫 발행 무유실) - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- 발행 전용 RabbitMQ 연결이 idle heartbeat 사망(half-open) 상태에서 `is_open=True`를 신뢰한 채 `basic_publish`가 실패하면 이벤트가 재시도·DLQ 없이 조용히 유실된다. 이 결함을 제거해, idle 후 첫 발행 시도에서 연결이 죽어 있어도 강제 재연결 후 1회 재시도로 이벤트가 유실되지 않도록 한다. tsl(M2) 라이브 선행 조건인 슬롯 전이 이벤트 신뢰성을 보장한다. - -## Requirement -- `publish_event`(`adapters/messaging/rabbitmq.py`)는 아래 재시도 정책을 구현한다. - - 발행(`basic_publish` + publisher confirms 대기) 실패 시 — 예외 종류와 무관하게 — 강제 재연결(`_ensure_connected`가 아니라 명시적 `close` 후 재연결)을 수행한 뒤 동일 메시지를 1회 재시도한다. - - 재시도까지 실패한 경우에만 `TransportUnavailableError`를 호출자에게 올린다. - - `is_open` 플래그 기반 조기 반환(`_ensure_connected` 단독 판단)에 의존하지 않는다 — `is_open=True`라도 발행이 실패하면 강제 재연결 경로를 탄다. -- 재시도 1회에는 명시적 타임아웃 상한을 적용해 execution-loop를 과도하게 블록하지 않는다(NFR-2 흡수). -- 이 변경은 `adapters/messaging/rabbitmq.py`의 `publish_event` 경로에 국소화하며, consume/RPC 경로(`call_rpc`, consumer 스레드), `declare_topology`, 슬롯 도메인/broker 집행 로직을 건드리지 않는다(NFR-3 흡수). -- M1 운영 배포본(`9e35467`)의 wire 계약 — `trex.events` exchange 토폴로지, routing key 형식, 페이로드 스키마, consumer RPC 큐(intent/slots/control) — 은 불변이다(NFR-1 흡수). - -## Acceptance Criteria -1. Given 발행 전용 연결이 idle 60초 이상 경과하여 서버 측 종료(half-open) 상태이고, When `publish_event`가 호출되면, Then 강제 재연결 후 동일 이벤트를 1회 재시도해 발행에 성공하며 `TransportUnavailableError`가 발생하지 않는다(S-1 연결). -2. Given 발행 전용 연결이 half-open이고 재시도 1회도 실패하는 조건에서, When `publish_event`가 호출되면, Then `TransportUnavailableError`가 호출자에 전파되고, 이벤트 유실은 인지된 실패로 처리된다(재시도 없이 삼키던 기존 동작 제거). -3. Given 발행 재시도 타임아웃 상한이 설정되어 있고, When 재연결 또는 재시도 중 타임아웃이 초과하면, Then 해당 publish 시도는 실패로 처리되며 execution-loop 블록이 명시적 상한을 초과하지 않는다(NFR-2 — 타임아웃 값은 design 단계에서 확정). -4. Given 정상 발행(연결 살아 있음) 조건에서, When `publish_event`가 호출되면, Then 기존과 동일하게 publisher confirms + `trex.events` exchange에 발행되며, 재시도 경로가 추가 레이턴시를 유발하지 않는다(회귀 무). -5. Given `call_rpc` / intent consumer / slots-rpc consumer / control-rpc consumer 경로에서, When 연결 상태를 확인하면, Then 이 REQ의 변경이 해당 경로의 동작에 영향을 주지 않는다(consume/RPC 경로 무회귀, NFR-1). -6. Given RabbitMQ 브로커가 재시작된 후 발행 전용 연결이 끊긴 상태에서, When 슬롯 전이 이벤트가 발행 시도되면, Then 강제 재연결 후 재시도로 이벤트가 성공적으로 전달된다(S-2 연결). - -## Impacted Area -- Docs: docs/runtime-flows.md(`슬롯 이벤트 발행/구독` 흐름 — 재시도/재연결 경로 추가 기재, REQ-094에서 갱신) -- Modules: `trex_core/adapters/messaging/rabbitmq.py`(`publish_event` L168-197, `_ensure_connected` L94-103). 슬롯 도메인/broker 집행/consume 경로 불변. -- Tests: `tests/adapters/messaging/` — half-open 연결 simulate 후 publish 성공 확인(AC-1), 재시도 소진 후 실패 확인(AC-2), 타임아웃 상한 확인(AC-3), 정상 발행 회귀(AC-4), consume/RPC 경로 회귀(AC-5), broker 재시작 시나리오(AC-6). `tests/application/test_messaging_event_publisher.py` 기존 회귀 유지. - -## Notes -- 출처: dcy-031 FR-1(B), NFR-1, NFR-2, NFR-3, R-1, S-1, S-2. -- NFR-2(타임아웃 상한 값)와 강제 재연결 구체 구현 방식(close-and-reconnect vs. selective channel reopen)은 draft-batch-design에서 결정한다. 본 REQ는 "1회 재시도 + 타임아웃 상한" 정책만 확정하며 수치 기준은 AC-3 Notes에서 "design 단계 결정"으로 추적한다. -- OQ-1(keepalive 스레드 위치) 결정은 REQ-092에 귀속하며 본 REQ와 독립이다. -- 재시도 at-least-once 의미(중복 발행 가능성)는 OQ-2로 추적 중 — 소비자(orb/tsl) 멱등성은 delivery 검증(DATA) 단계에서 확인한다. -- R-1(M1 라이브 회귀): 본 batch는 opus qc-reviewer 강제(CONFIRM ①, dcy-031 §10). - -## Change Log - -### CHG-20260619-01 -- Date: 2026-06-19 -- Author: trex-core-dev - -#### Change Summary -- dcy-031 FR-1(B) 로부터 최초 생성(Proposed). NFR-1·NFR-2·NFR-3 흡수. S-1·S-2 연결. - -#### Intent -- 이벤트 발행 재시도 + 강제 재연결 정책을 backlog에 정규화한다. - -#### Acceptance Criteria Delta -- Added: - - AC-1: idle 후 첫 발행 재연결+재시도 성공 - - AC-2: 재시도 소진 후 TransportUnavailableError 전파 - - AC-3: 타임아웃 상한으로 execution-loop 과블록 방지 - - AC-4: 정상 발행 경로 회귀 무 - - AC-5: consume/RPC 경로 회귀 무 - - AC-6: broker 재시작 후 발행 복구 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md (재시도/재연결 경로) -- Modules: - - trex_core/adapters/messaging/rabbitmq.py -- Tests: - - tests/adapters/messaging/ (half-open/재시도/타임아웃/회귀) - -#### Delivery Trace Delta -- Batches: - - none (draft-req 단계, batch 미편성) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 suggest-batch-reqs + draft-batch 편성 - -#### Validation Plan -- stub 또는 mock 기반 half-open 시뮬레이션 후 publish 재시도 성공/실패 시나리오 검증. 기존 messaging 회귀 스위트 전체 통과 확인. - -### CHG-20260619-02 -- Date: 2026-06-19 -- Author: 팀장 (confirm-req) - -#### Change Summary -- Proposed → **Approved**. 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 충족, AC 구체·측정 가능, source dcy-031(confirmed, Open OQ 0) 정합, blocker 없음. - -#### Intent -- delivery input 승인. NFR-2 타임아웃 수치는 비핵심 운영 메모로 draft-batch-design 확정(승인 비차단). - -#### Acceptance Criteria Delta -- Added: 없음 -- Updated: 없음 -- Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs + draft-batch (REQ-091~094 단일 batch 편성 예정) diff --git a/docs/srs/Exception/req-092_publish-idle-keepalive.md b/docs/srs/Exception/req-092_publish-idle-keepalive.md deleted file mode 100644 index fe6daeb..0000000 --- a/docs/srs/Exception/req-092_publish-idle-keepalive.md +++ /dev/null @@ -1,102 +0,0 @@ -# req-092: 발행 전용 연결 idle keepalive (서버 측 heartbeat 사망 예방) - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- 발행 전용 RabbitMQ 연결은 consumer 루프(`process_data_events` 0.2s 틱)와 달리 publish 사이에 I/O가 전혀 없어 `heartbeat=30` 설정에도 불구하고 ~60초 idle 후 서버가 연결을 종료한다. keepalive로 idle 구간에도 heartbeat I/O가 나가도록 해 서버 측 연결 종료를 사전 예방하고, REQ-091 재시도 경로를 보완하는 2차 방어선을 구성한다. - -## Requirement -- 발행 전용 연결에 idle keepalive를 도입해, heartbeat 주기(`heartbeat=30`) 내에 `process_data_events`가 최소 1회 이상 호출되도록 보장한다. -- keepalive는 pika `BlockingConnection`의 스레드 안전성 제약을 위반하지 않는다. 즉, 발행 전용 연결을 소유한 스레드에서만 keepalive I/O를 실행한다. -- keepalive의 구체 구현 방식(별도 keepalive 스레드 vs. execution-loop 틱에 삽입)은 draft-batch-design에서 OQ-1 기준으로 확정한다. 본 REQ는 "발행 연결 소유 스레드 내 I/O 보장" 정책만 확정하며, 구현 위치는 설계 단계 결정으로 추적한다. -- 이 변경은 `adapters/messaging/rabbitmq.py` 및 필요 최소 범위의 `runtime/daemon.py`에 국소화하며, consume/RPC 경로·슬롯 도메인·broker 집행 로직을 건드리지 않는다(NFR-3 흡수). -- M1 운영 배포본(`9e35467`)의 wire 계약은 불변이다(NFR-1 흡수). - -## Acceptance Criteria -1. Given idle 구간이 `heartbeat × 2`(60초) 이상 지속될 때, When keepalive가 활성화되어 있으면, Then 발행 전용 연결이 서버에 의해 종료되지 않고 살아 있는 상태를 유지한다(S-1 1차 방어선). -2. Given keepalive 구현이 별도 스레드를 사용하거나 루프 틱에 삽입되는 경우, When 해당 I/O가 실행될 때, Then 발행 전용 연결을 소유한 스레드에서만 `process_data_events`가 호출되며, 다른 스레드가 동일 연결에 직접 I/O를 호출하지 않는다(pika 스레드 안전성 불변식). -3. Given keepalive가 동작 중이고 동시에 발행이 발생할 때, When 두 작업이 겹치면, Then 연결 상태 손상 또는 예외 없이 정상 완료된다(pika 스레드 안전성 불변식 보강). -4. Given keepalive 설정이 적용된 상태에서, When execution-loop의 매도 트리거 처리 타이밍을 확인하면, Then keepalive 틱이 execution-loop를 과도하게 블록하지 않는다(NFR-2 흡수 — 허용 블록 시간 상한은 design 단계 확정). -5. Given consumer 스레드(intent/slots/control RPC)를 확인할 때, When keepalive 변경이 적용된 코드베이스를 검사하면, Then consumer 경로의 동작·큐 바인딩·토폴로지에 변화가 없다(consume/RPC 무회귀, NFR-1). -6. Given keepalive 구현 방식이 설계 단계에서 확정된 후, When batch design 문서를 확인하면, Then OQ-1(keepalive 실행 위치·스레드 소유 모델)이 명시적으로 결정되어 있다(OQ-1 종료 조건 추적). - -## Impacted Area -- Docs: docs/runtime-flows.md(`데몬 lifecycle` 흐름 — keepalive 틱 추가 기재, REQ-094에서 갱신). docs/project-structure.md(keepalive 컴포넌트 추가 시 반영, REQ-094에서 갱신). -- Modules: `trex_core/adapters/messaging/rabbitmq.py`(keepalive 메커니즘 추가). `trex_core/runtime/daemon.py`(keepalive 연동, 최소 변경). 슬롯 도메인/broker 집행/consume 경로 불변. -- Tests: `tests/adapters/messaging/` — idle 60초 후 연결 생존 확인(AC-1), 스레드 소유 검증(AC-2), 동시 발행+keepalive 충돌 없음(AC-3), execution-loop 블록 상한(AC-4), consume 경로 회귀(AC-5). - -## Notes -- 출처: dcy-031 FR-2(A), NFR-1, NFR-2, NFR-3, R-2(keepalive pika 스레드 충돌), S-1. -- OQ-1(keepalive 구현 위치 — 별도 스레드 vs. execution-loop 삽입): 설계 단계 결정. AC-6에서 추적. 종료 조건: batch design 문서에 명시되면 Answered. -- R-2(pika 스레드 비안전성): 이 위험은 AC-2/AC-3으로 검증하며, 설계 단계(OQ-1)에서 구현 방식을 확정해 완화한다. -- keepalive는 REQ-091 재시도와 독립 계층이다. keepalive는 1차 예방(연결 사망 방지), REQ-091은 2차 복구(사망 후 재연결+재시도). 두 REQ는 함께 배치될 예정이지만 독립 구현·독립 검증 가능하다. - -## Change Log - -### CHG-20260619-01 -- Date: 2026-06-19 -- Author: trex-core-dev - -#### Change Summary -- dcy-031 FR-2(A) 로부터 최초 생성(Proposed). NFR-1·NFR-2·NFR-3 흡수. R-2(pika 스레드) OQ-1 인계. S-1 연결. - -#### Intent -- 발행 전용 연결 idle keepalive 정책을 backlog에 정규화하고, OQ-1 설계 결정 추적을 AC-6으로 고정한다. - -#### Acceptance Criteria Delta -- Added: - - AC-1: idle 60초 후 연결 생존 - - AC-2: 발행 소유 스레드에서만 I/O (스레드 안전성) - - AC-3: 동시 발행+keepalive 충돌 없음 - - AC-4: execution-loop 블록 상한 - - AC-5: consume/RPC 경로 무회귀 - - AC-6: OQ-1 종료 조건 추적 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/project-structure.md -- Modules: - - trex_core/adapters/messaging/rabbitmq.py, trex_core/runtime/daemon.py -- Tests: - - tests/adapters/messaging/ (idle/스레드/동시성/블록/회귀) - -#### Delivery Trace Delta -- Batches: - - none (draft-req 단계, batch 미편성) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-091과 함께 단일 batch 편성 예정 - -#### Validation Plan -- time-freeze 또는 sleep mock으로 idle 구간을 시뮬레이션한 후 연결 생존 + 스레드 안전성 검증. 기존 messaging 회귀 스위트 전체 통과 확인. - -### CHG-20260619-02 -- Date: 2026-06-19 -- Author: 팀장 (confirm-req) - -#### Change Summary -- Proposed → **Approved**. 필수 필드 충족, AC 구체, source dcy-031(confirmed, Open OQ 0) 정합. OQ-1(keepalive 실행 위치)은 AC-6에서 design 인계 추적 — 승인 비차단. - -#### Intent -- delivery input 승인. keepalive 구현 위치(별도 스레드 vs execution-loop 틱)는 draft-batch-design에서 확정. - -#### Acceptance Criteria Delta -- Added: 없음 -- Updated: 없음 -- Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-091과 함께 단일 batch 편성 예정 diff --git a/docs/srs/Exception/req-093_dlq-visibility-and-emit-error-alert.md b/docs/srs/Exception/req-093_dlq-visibility-and-emit-error-alert.md deleted file mode 100644 index 5048d71..0000000 --- a/docs/srs/Exception/req-093_dlq-visibility-and-emit-error-alert.md +++ /dev/null @@ -1,104 +0,0 @@ -# req-093: DLQ 가시성/경보 + emit 실패 경보 (trex.events.dlq 관측성) - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: project maintainers - -## Intent -- `trex.events.dlq`(unroutable + 발행 실패 양쪽)는 현재 durable 큐로 보존되지만 소비/알림 주체가 없어 운영에서 조용히 누적된다. 또한 `_make_event_emitter`의 발행 실패는 warning 로그로 삼켜져 운영자가 인지하지 못한다. DLQ 유입/깊이를 관측 가능하게 만들고, 최종 실패 시 식별 가능한 경보 레벨 로그를 남겨 운영 피드백 루프를 닫는다. - -## Requirement -- `trex.events.dlq` 유입(unroutable + 발행 실패 양쪽)과 깊이(queue depth)를 운영에서 관측·경보 가능하게 한다. 구현 방식(메트릭 노출 / 로그 기반 / 소비자 데몬)은 draft-batch-design에서 OQ-3 기준으로 결정한다. -- unroutable 이벤트: `mandatory=True` → `UnroutableError` → `_publish_dlq`로 `trex.events.dlq`에 보존하는 기존 경로는 유지하되, DLQ 유입이 관측 가능해야 한다. -- 발행 실패 이벤트: REQ-091의 재시도까지 실패한 경우 `TransportUnavailableError`가 올라오면, 해당 실패를 DLQ(또는 별도 실패 경보 채널)에 추적 가능한 형태로 기록하거나 메트릭을 카운트한다. 구체 경로는 OQ-3 결정에 따른다. -- `_make_event_emitter`(`runtime/daemon.py`)의 발행 실패 처리를 보강한다: REQ-091 재시도 후에도 실패 시 단순 `WARNING` 삼킴이 아니라 `ERROR` 또는 그에 준하는 경보 레벨 로그를 남긴다. 로그에는 routing key, 슬롯 ID(또는 correlation 정보), 실패 사유가 포함되어 식별 가능해야 한다. -- `_publish_dlq`의 best-effort 실패(DLQ 자체 발행 실패)도 `ERROR` 수준 로그를 남긴다. -- 이 변경은 `adapters/messaging/rabbitmq.py`(`_publish_dlq`)와 `runtime/daemon.py`(`_make_event_emitter`) 및 관측성 메커니즘 추가 범위에 국소화한다. wire 계약·consume/RPC 경로·슬롯 도메인 불변(NFR-1·NFR-3 흡수). - -## Acceptance Criteria -1. Given unroutable 이벤트가 `trex.events.dlq`에 유입될 때, When 관측 지표(로그/메트릭/소비자)를 확인하면, Then DLQ 유입이 운영자가 인지 가능한 형태로 노출된다(S-3 연결). -2. Given `trex.events.dlq` 큐의 깊이(depth)를 확인할 때, When 유입 이벤트가 누적되면, Then 운영자가 접근 가능한 지표(로그/메트릭/RabbitMQ 관리 API 등)로 깊이를 파악할 수 있다(S-3 연결). -3. Given REQ-091 재시도 후에도 발행이 실패한 경우, When `_make_event_emitter`에서 `TransportUnavailableError`를 처리할 때, Then `WARNING` 수준이 아닌 `ERROR`(또는 이상) 수준 로그가 남고, routing key와 슬롯 식별 정보가 포함된다(FR-4 연결). -4. Given `_publish_dlq`의 DLQ 재발행 자체가 실패한 경우, When 예외가 발생하면, Then `ERROR` 수준 로그가 남고 메시지 유실이 silent하게 처리되지 않는다. -5. Given DLQ 가시성 구현 방식이 설계 단계에서 확정된 후, When batch design 문서를 확인하면, Then OQ-3(DLQ 가시성 구현 방식 — 메트릭/로그/소비자 데몬 중 택1)이 명시적으로 결정되어 있다(OQ-3 종료 조건 추적). -6. Given DLQ 유입/경보 메커니즘이 추가된 상태에서, When consumer 경로(intent/slots/control RPC)를 확인하면, Then 해당 경로의 동작·큐 바인딩·토폴로지에 변화가 없다(consume/RPC 무회귀, NFR-1). - -## Impacted Area -- Docs: docs/operations.md(DLQ 가시성/경보 운영 절차 신규 기재, REQ-094에서 함께 갱신 검토). docs/runtime-flows.md(`슬롯 이벤트 발행/구독` DLQ 경로 설명 보강, REQ-094에서 갱신). -- Modules: `trex_core/adapters/messaging/rabbitmq.py`(`_publish_dlq` L199-216 — DLQ best-effort 실패 ERROR 로그 + 가시성 훅). `trex_core/runtime/daemon.py`(`_make_event_emitter` L238-248 — 발행 실패 ERROR 경보). 관측성 구현(OQ-3 결정 후 추가). wire 계약·consume/RPC 경로·슬롯 도메인 불변. -- Tests: `tests/adapters/messaging/` — DLQ 유입 관측 지표 확인(AC-1), DLQ 깊이 노출(AC-2), emit 실패 ERROR 로그(AC-3), DLQ 재발행 실패 ERROR(AC-4), consume 경로 회귀(AC-6). `tests/application/test_messaging_event_publisher.py` 기존 회귀 유지. - -## Notes -- 출처: dcy-031 FR-3(D), FR-4, A-3(dcy-029 관측성 자산 재사용 여부 검토), S-3. -- OQ-3(DLQ 가시성 구현 방식 — 메트릭/로그/소비자 중 택1): 설계 단계 결정. AC-5에서 추적. 종료 조건: batch design에 명시되면 Answered. dcy-029(REQ-084~090) 관측성 자산(구조화 로깅 인프라) 재사용 가능성을 설계 단계에서 검토한다. -- FR-4(emit 실패 경보)는 독립 REQ로 승격하지 않고 본 REQ에 흡수한다 — FR-3(DLQ 가시성)과 같은 코드 경로(`_make_event_emitter`, `_publish_dlq`)를 공유하며 독립 구현·검증 시 오버헤드 대비 가치가 낮다. -- `_publish_dlq` best-effort 정책(실패 시 silent log → ERROR로 강화)은 기존 wire 계약 변경 없이 로그 레벨 조정만으로 구현 가능해 설계 결정 없이도 확정 가능하다. -- 본 변경은 DLQ 소비자 데몬(별도 상시 프로세스)을 강제하지 않는다 — 관측 방식은 OQ-3에서 결정. - -## Change Log - -### CHG-20260619-01 -- Date: 2026-06-19 -- Author: trex-core-dev - -#### Change Summary -- dcy-031 FR-3(D) + FR-4로부터 최초 생성(Proposed). NFR-1·NFR-3 흡수. FR-4를 본 REQ에 흡수. OQ-3 설계 인계. A-3(dcy-029 자산 재사용) 인계. S-3 연결. - -#### Intent -- DLQ 가시성/경보 + emit 실패 경보 정책을 backlog에 정규화하고, OQ-3 설계 결정 추적을 AC-5로 고정한다. - -#### Acceptance Criteria Delta -- Added: - - AC-1: DLQ 유입 관측 가능 - - AC-2: DLQ 깊이 파악 가능 - - AC-3: emit 실패 ERROR 수준 경보 - - AC-4: DLQ 재발행 실패 ERROR - - AC-5: OQ-3 종료 조건 추적 - - AC-6: consume/RPC 경로 무회귀 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: - - docs/operations.md, docs/runtime-flows.md -- Modules: - - trex_core/adapters/messaging/rabbitmq.py, trex_core/runtime/daemon.py -- Tests: - - tests/adapters/messaging/ (DLQ/emit 경보/회귀) - -#### Delivery Trace Delta -- Batches: - - none (draft-req 단계, batch 미편성) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-091·092와 함께 단일 batch 편성 예정 - -#### Validation Plan -- 로그 캡처 기반으로 DLQ 유입 로그/ERROR 레벨 확인. mock `_publish_dlq` 실패 시나리오로 AC-4 검증. OQ-3 결정 후 구현 방식에 맞는 통합 검증 추가. - -### CHG-20260619-02 -- Date: 2026-06-19 -- Author: 팀장 (confirm-req) - -#### Change Summary -- Proposed → **Approved**. 필수 필드 충족, AC 구체, source dcy-031(confirmed, Open OQ 0) 정합. FR-4 흡수 승인(동일 emit 경로). OQ-3(DLQ 가시성 구현 방식)은 AC-5에서 design 인계 추적 — 승인 비차단. - -#### Intent -- delivery input 승인. DLQ 가시성 구현 방식(메트릭/로그/소비자)은 draft-batch-design에서 dcy-029 관측성 자산 재사용성과 함께 확정. - -#### Acceptance Criteria Delta -- Added: 없음 -- Updated: 없음 -- Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-091·092와 함께 단일 batch 편성 예정 diff --git a/docs/srs/Exception/req-095_fill-price-zero-backfill-guard.md b/docs/srs/Exception/req-095_fill-price-zero-backfill-guard.md deleted file mode 100644 index d0d2a63..0000000 --- a/docs/srs/Exception/req-095_fill-price-zero-backfill-guard.md +++ /dev/null @@ -1,77 +0,0 @@ -# req-095: 체결가 0/None 지정가 백필 가드 (슬롯 평단 + 원장 price) - -- Status: Approved -- Type: Exception -- Priority: High -- Owner: trex-core-dev - -## Intent -- KIS 체결조회가 체결수량은 보고하면서 평균체결가(`avg`)를 None 또는 0으로 반환하는 순간 스냅샷이 발생할 수 있다. 현재 코드는 이 케이스를 무경고로 통과시켜 슬롯 평단(`slot.avg_price`)과 원장 cost basis(`_record_ledger` price)가 동시에 0으로 영구 오염된다. 완전체결 시 `clear_dispatch`로 재폴링이 없어 0이 영구화된다. -- 본 REQ는 체결(완전·부분) 처리에서 브로커 평균체결가가 None 또는 ≤0이면 주문 지정가(`limit_price`)로 백필해, 슬롯 평단과 원장 price가 0으로 저장되지 않도록 무결성 가드를 추가한다. -- 이 변경은 wire 계약(slots RPC 스키마) 변경 없이 값 정정만 수행하며, 정상 평균가 수신 경로(happy path)에 영향을 주지 않는다. - -## Requirement -- 체결(완전 체결 및 부분 취소 후 체결) 처리 시, 브로커가 제공한 `fs.avg_price`가 None 또는 ≤0인 경우, 슬롯의 `avg_price` 계산(`_on_filled`, `_on_partial_cancel`)에 사용하는 `fill_price`를 주문 지정가(`slot.limit_price` 또는 동등한 주문 컨텍스트의 지정가)로 대체(백필)해야 한다. -- 동일 조건에서 `_record_ledger`의 `price` 결정도 동일한 백필 가격을 사용해야 하며, price=0인 원장 레코드가 생성되어서는 안 된다. -- 백필 결정 지점(adapter vs application)과 지정가마저 부재/≤0인 경우의 최종 폴백 정책은 design 단계에서 확정한다(아래 Notes 참조). - -## Acceptance Criteria - -1. Given `fs.avg_price`가 None인 체결 이벤트와 `slot.limit_price > 0`인 슬롯, When `_on_filled`의 평단 계산이 실행될 때, Then `fill_price`로 `slot.limit_price`가 사용되고 `slot.avg_price`에 지정가 기반 가중평균이 저장된다(0 저장 없음). -2. Given `fs.avg_price`가 0.0인 체결 이벤트와 `slot.limit_price > 0`인 슬롯, When `_on_partial_cancel`의 평단 계산이 실행될 때, Then `fill_price`로 `slot.limit_price`가 사용되고 `slot.avg_price`가 0이 아닌 값으로 저장된다. -3. Given `fs.avg_price`가 None인 체결 이벤트, When `_record_ledger`의 price 결정이 실행될 때, Then `LedgerWorkItem.price`로 0.0이 아닌 백필 지정가(≥0 초과)가 기록된다. -4. Given `fs.avg_price`가 정상 양수인 체결 이벤트(happy path), When `_on_filled`/`_on_partial_cancel`/`_record_ledger`가 실행될 때, Then 기존과 동일하게 `fs.avg_price`가 사용되며 백필 분기가 개입하지 않는다(회귀 없음). -5. Given wire 계약 관점(slots RPC 응답 스키마), When 백필이 발생할 때, Then RPC 메시지 구조·필드 집합이 변경되지 않는다(NFR-1: 값 정정만, 계약 불변). -6. Given `slot.limit_price`가 None 또는 ≤0인 극단 케이스(지정가마저 부재), When 백필 경로가 진입될 때, Then **체결 처리를 skip(슬롯 전이·원장 기록 미수행)하고 slot_id·symbol·broker_order_id·avg_price·limit_price·`reason=fill_price_unresolvable`를 포함한 강 WARN 로그를 발행한다**(0 저장 0건, 무경고 통과 0건). 슬롯은 PENDING 상태로 남아 watchdog/stale_pending 경보가 운영자 개입을 유도한다. [design BAT-048 KD-2 확정, 팀장 승인 2026-06-21] - -## Impacted Area -- Docs: `docs/runtime-flows.md` — 체결 처리 흐름에 백필 분기 추가 필요 (implementation 후 갱신) -- Modules: - - `trex_core/application/execution.py` — `_on_filled`(`:242-251`), `_on_partial_cancel`(`:305-314`), `_record_ledger`(`:424`) 의 fill_price/price 결정 로직 - - `trex_core/adapters/broker/kis.py` — `:209,219,232,238,243` 평균가 None/0 표면화 지점 (백필 결정을 adapter에서 할지 application에서 할지는 design 확정 대상) -- Tests: - - `tests/application/test_execution.py` (또는 동등 경로) — avg_price=None, avg_price=0, happy path 각각 단위 테스트 - - `tests/integration/` — 체결→원장 기록 통합 경로 회귀 테스트 - -## Notes -- **design 확정 완료 (BAT-048 KD-1/KD-2, 팀장 승인 2026-06-21)**: - - 백필 결정 지점: **application 레이어 단일화** — `application/execution.py`에 `_resolve_fill_price` 헬퍼 신설. `BrokerPort.poll_fill`/adapter/paper 불변, dcy-021 reconcile 경로 자동 일관성(Discovery R-2 해소). - - 지정가마저 부재/≤0일 때 폴백: **skip + 강 WARN**(AC-6 반영). 재폴링은 완전체결 교착, 현재가 조회는 포트 계약 변경이라 기각(Discovery OQ-2 해소). -- AC-6 구체화 완료(위 design 확정 반영). -- NFR-2(happy path 무영향): AC-4로 흡수. NFR-3(백필 추적 가능): REQ-096 WARN 로깅으로 달성. -- Discovery 가정 A-1: orb 매수는 지정가 주문이므로 대다수 케이스에서 `slot.limit_price`가 존재한다. -- 원장 price=0 방지는 REQ-018(체결→원장 기록)의 무결성 연장이며, 원장 skip 가드(`portfolio_id` 부재, `execution_id` 부재)는 본 REQ와 독립적으로 유지된다. -- Discovery: dcy-032_20260621_fill-missing-price-cost-basis-backfill.md (FR-1, FR-2) - -## Change Log - -### CHG-20260621-01 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- 최초 생성 (draft-req dcy-032) - -#### Intent -- KIS 체결가 0/None 무경고 오염 방지를 위한 지정가 백필 가드 신규 요구사항 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~6 신규 정의 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md (implementation 후 갱신) -- Modules: trex_core/application/execution.py, trex_core/adapters/broker/kis.py -- Tests: tests/application/test_execution.py, tests/integration/ - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후 편성) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (최초 생성) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: design 확정 후 AC-6 구체화 필요 diff --git a/docs/srs/Exception/req-096_fill-price-backfill-warn-logging.md b/docs/srs/Exception/req-096_fill-price-backfill-warn-logging.md deleted file mode 100644 index a749020..0000000 --- a/docs/srs/Exception/req-096_fill-price-backfill-warn-logging.md +++ /dev/null @@ -1,70 +0,0 @@ -# req-096: 체결가 백필·0가격 통과 WARN 로깅 - -- Status: Approved -- Type: Exception -- Priority: High -- Owner: trex-core-dev - -## Intent -- REQ-095의 백필 가드가 발동되거나, 백필 없이 price≤0 체결이 하류(원장·슬롯)로 통과하는 경우, 운영자가 사후에도 해당 이벤트를 식별할 수 있도록 구조화된 WARN 로그를 남겨야 한다. -- 현재 원장 skip 가드(`portfolio_id` 부재, `execution_id` 부재)는 각각 WARN을 남기는 반면, "체결됐는데 price≤0" 케이스는 어떤 가드에도 걸리지 않고 조용히 통과한다. 이 가시성 공백을 동일한 수준으로 맞춘다. -- WARN 로그는 슬롯·심볼·주문ID·원가격·대체가격·사유를 포함해 백필 항목이 실제 평균가인지 백필 근사인지 추적 가능(NFR-3)하게 한다. - -## Requirement -- 체결(완전·부분 취소 후) 처리에서 REQ-095의 백필이 발동된 경우, 슬롯 ID·심볼·주문 ID·원 브로커 평균가(None 또는 0)·대체가격(백필 지정가)·사유(`avg_price_backfill`)를 포함한 WARN 수준 구조화 로그를 반드시 남겨야 한다. -- 지정가마저 부재/≤0인 극단 케이스(design 확정 폴백 정책 적용 시점)에서 백필이 불가능해 price≤0 상태가 하류로 넘어가는 경우에도, 동일한 필드 집합을 포함한 WARN(또는 그 이상 수준) 로그를 남겨야 한다. -- 정상 평균가(happy path) 수신 경로에서는 이 WARN 로그가 발생하지 않아야 한다. - -## Acceptance Criteria - -1. Given `fs.avg_price`가 None 또는 ≤0이고 지정가 백필이 적용된 체결, When 집행 루프가 해당 체결을 처리할 때, Then `slot_id`, `symbol`, `broker_order_id`(또는 동등 주문 식별자), 원 `avg_price`(None 또는 0), `backfill_price`(대체가), `reason=avg_price_backfill` 필드를 포함하는 WARN 로그가 정확히 1건 생성된다. -2. Given 지정가마저 부재/≤0인 극단 케이스(폴백 정책 적용), When 집행 루프가 해당 체결을 처리할 때, Then 슬롯·심볼·주문 ID·원가격·사유를 포함한 WARN 이상 수준 로그가 1건 이상 생성된다(0 저장 또는 skip 어느 쪽이든 무경고 통과 없음). -3. Given `fs.avg_price`가 정상 양수인 체결(happy path), When 집행 루프가 처리할 때, Then 백필 관련 WARN 로그가 생성되지 않는다. -4. Given 백필 WARN 로그가 생성된 후 운영자가 로그를 조회할 때, Then 해당 로그로부터 어떤 슬롯의 어떤 체결에서 백필이 발생했는지, 원래 브로커가 제공한 가격이 무엇이었는지, 어떤 가격으로 대체됐는지를 식별할 수 있다(NFR-3: 실제 평균가 vs 백필 근사 구분 가능). - -## Impacted Area -- Docs: `docs/operations.md` — 백필 WARN 발생 시 운영자 점검 절차 추가 권고 (implementation 후 갱신) -- Modules: - - `trex_core/application/execution.py` — `_on_filled`, `_on_partial_cancel`, `_record_ledger` 내 백필 분기 후 WARN 로그 추가 -- Tests: - - `tests/application/test_execution.py` (또는 동등 경로) — 백필 경로에서 WARN 로그 발생 단언, happy path에서 WARN 미발생 단언 - -## Notes -- 이 REQ는 REQ-095(백필 가드)와 독립적으로 측정 가능하다: REQ-095가 값 정정을, REQ-096이 가시성을 담당하며, 둘 다 같은 batch에서 구현해야 함께 의미가 있다. -- 원장 skip 가드(portfolio_id 부재 WARN, execution_id 부재 WARN)와 동일한 운영 가시성 수준을 목표로 한다(Discovery FR-3 기준). -- AC-2의 극단 케이스(지정가마저 부재)의 폴백 정책 세부 사항은 REQ-095와 동일하게 design 확정 대상이다. 본 REQ는 어떤 폴백 정책이 채택되더라도 무경고 통과가 없어야 한다는 원칙을 요구한다. -- NFR-3(백필 추적 가능) — Discovery NFR-3을 본 REQ AC-4로 흡수. -- Discovery: dcy-032_20260621_fill-missing-price-cost-basis-backfill.md (FR-3) - -## Change Log - -### CHG-20260621-01 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- 최초 생성 (draft-req dcy-032) - -#### Intent -- 백필 발생 및 price≤0 체결 통과 시 운영 가시성 확보를 위한 WARN 로깅 요구사항 신규 정의 - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 신규 정의 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/operations.md (implementation 후 갱신) -- Modules: trex_core/application/execution.py -- Tests: tests/application/test_execution.py - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후 편성, REQ-095와 동일 batch 예상) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (최초 생성) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: 없음 diff --git a/docs/srs/Exception/req-107_blocking-reconcile-logging-and-alarm.md b/docs/srs/Exception/req-107_blocking-reconcile-logging-and-alarm.md deleted file mode 100644 index c815a7a..0000000 --- a/docs/srs/Exception/req-107_blocking-reconcile-logging-and-alarm.md +++ /dev/null @@ -1,134 +0,0 @@ -# req-107: 차단/해제 전이 식별 로깅 + 고영향 불일치 알람 - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: trex-core-dev - -## Intent -- reconcile-warning 차단 및 해제 전이마다 슬롯·심볼·원인케이스·수량 before/after·actor를 포함한 **식별 가능한 구조화 로그**를 남겨 운영자가 차단 원인과 이력을 추적할 수 있게 한다. -- 고영향 불일치(케이스 A: 슬롯 보유인데 브로커 0 — phantom holding)는 기존 알람 채널(dcy-033/`trex.alerts`)로 통지한다. -- dcy-034 FR-6 대응. - -## Requirement -- 시스템은 reconcile-warning 차단 전이(`set_warned`) 시 WARNING 레벨 로그를 남겨야 하며, 로그에는 `slot_id`, `portfolio_id`, `symbol`, `case_code`(B 또는 C), `broker_qty`, `slot_qty`(B의 경우 before), `hub_qty`(C의 경우 원장 수량), `actor`="reconcile" 필드가 포함되어야 한다. -- 시스템은 reconcile-warning 해제 전이(`clear_warned`) 시 INFO 레벨 로그를 남겨야 하며, 로그에는 `slot_id`, `portfolio_id`, `symbol`, `actor`(sync/verify/reconcile), `resolved_by`(sync 또는 verify) 필드가 포함되어야 한다. -- 케이스 A(슬롯 보유, 브로커 0 — phantom holding)는 기존 REQ-100(`AlarmEmitter.publish_alarm`) 경로를 통해 `trex.alerts` exchange로 CRITICAL 알람을 발행해야 한다(기존 `reconcile_core_holdings` 내 alarm_emitter 호출 활성 — 이미 구현됨, 여기서는 차단형 전이와의 일관성 확인). -- B/C 케이스 차단 시 WARNING 로그는 구조화 포맷(JSON 또는 key=value)으로 남겨져 Loki/로그 검색에서 `slot_id`, `case_code` 필드로 필터링 가능해야 한다(REQ-084 구조화 로깅 기반과 일관). -- sync 또는 verify로 warning이 해제될 때, actor 필드에 "sync" 또는 "verify"가 기록되어야 한다. - -## Acceptance Criteria -1. Given 케이스 B 불일치 탐지 시, When `set_warned`가 호출되면, Then `level=WARNING`, `slot_id`, `portfolio_id`, `symbol`, `case_code='B'`, `broker_qty`, `slot_qty`(before) 필드를 포함한 로그가 기록된다. -2. Given 케이스 C 불일치 탐지 시, When `set_warned`가 호출되면, Then `level=WARNING`, `slot_id`, `portfolio_id`, `symbol`, `case_code='C'`, `hub_qty`, `broker_qty` 필드를 포함한 로그가 기록된다. -3. Given `trex-core sync`로 warning 해제 시, When `clear_warned`가 호출되면, Then `level=INFO`, `slot_id`, `portfolio_id`, `symbol`, `actor='sync'` 필드를 포함한 로그가 기록된다. -4. Given `trex-core verify`로 warning 해제 시, When `clear_warned`가 호출되면, Then `level=INFO`, `slot_id`, `portfolio_id`, `symbol`, `actor='verify'` 필드를 포함한 로그가 기록된다. -5. Given 케이스 A(슬롯 보유, 브로커 0)에서, When `reconcile_core_holdings`가 실행되면, Then 기존 REQ-100 경로로 `trex.alerts` CRITICAL 알람이 발행된다(기존 동작 회귀 없음). -6. Given 차단/해제 로그가 남겨진 경우, When 로그를 `slot_id` 또는 `case_code`로 필터링하면, Then 해당 이벤트를 특정할 수 있다(Loki 구조화 로그 필터링 가능). - -## Impacted Area -- Docs: `docs/operations.md` (로그 조회 방법 추가) -- Modules: - - `trex_core/application/reconciliation.py` (차단 전이 로그 — set_warned 호출 지점) - - `trex_core/runtime/cli.py` (`sync`/`verify` 해제 로그 — clear_warned 호출 지점) - - `trex_core/adapters/portfolio/reconcile_warning_store.py` (선택: store 자체에 로그 위임 vs 호출부 로그 — batch design에서 결정) -- Tests: - - `tests/application/test_reconciliation.py` (차단 로그 필드 검증) - - `tests/runtime/test_cli.py` (해제 로그 actor 필드 검증) - - 기존 REQ-100 알람 테스트 회귀 확인 - -## Notes -- 케이스 A 알람은 REQ-100 구현(`reconcile_core_holdings` 내 `alarm_emitter.publish_alarm("core_holding_broker_flat", ...)`)이 이미 존재하므로, 이 REQ에서는 B/C 차단 로그 추가와 A 알람 기존 동작 회귀 없음을 확인하면 된다. -- 구조화 로그 포맷은 REQ-084(`trex_core/runtime/logging_setup.py`)와 일관성 있게 작성한다. -- 고영향 B 불일치(large qty 차이 등)에 대해 B 케이스도 `trex.alerts` 알람으로 확대할지는 batch design에서 검토한다(dcy-034는 "고영향(A phantom)은 trex.alerts" 명시, B/C는 WARN 로그로 충분함을 시사). -- dcy-034 FR-6 대응. - -## Change Log - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-050/051(PR #31/#32) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. B/C 케이스 WARNING 로그 필드(slot_id/case_code/qty before-after), sync/verify 해제 INFO 로그(AC-3/4), A케이스 CRITICAL 알람 회귀 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-050 (core-A, AC-1/2/5), BAT-051 (core-B, AC-3/4) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 없음. - -#### Validation Plan -- BAT-050/051 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-107 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). - -#### Intent -- 팀장 confirm-req로 상태 승인. 내용 변경 없음(초안 그대로 승인). - -#### Acceptance Criteria Delta -- Added: 없음. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- 없음. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 후 구현. - -#### Validation Plan -- 차단 로그 필드 검증 테스트 pass / 해제 로그 actor 필드 검증 pass / A 알람 회귀 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-107 신규 생성 (Proposed) — dcy-034 FR-6 대응, draft-req 단계. - -#### Intent -- 차단/해제 전이 가시화. 운영자가 로그만으로 불일치 발생 및 해소 이력을 추적할 수 있는 구조화 로그 신설. - -#### Acceptance Criteria Delta -- Added: AC-1~6 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: operations.md 갱신. -- Modules: reconciliation.py, cli.py. -- Tests: 로그 필드 검증 신규. - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch design. - -#### Validation Plan -- 차단 로그 필드(slot_id/case_code/qty before-after/actor) 검증 테스트 pass / 해제 로그 actor 필드 검증 pass / A 알람 회귀 pass. diff --git a/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md b/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md deleted file mode 100644 index 8c1cd53..0000000 --- a/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md +++ /dev/null @@ -1,112 +0,0 @@ -# req-111: 운영 리포트 — portfolio-hub 미가용 시 실현손익 섹션 에러 degrade - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: trex-core-dev -- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req) - -## Intent -- portfolio-hub가 미가용(네트워크 오류, 5xx, timeout)이면 실현손익 섹션에 에러를 명시하고 나머지 섹션(보유/체결/대기주문)은 정상 출력한다. 폴백(당일 net 대체)은 없다. (dcy-035 FR-5, OQ-5=에러 명시, NFR-3=데몬 본업 격리) - -## Requirement -- `_cmd_report`에서 portfolio-hub WAC 조회(`get_realized_pnl`) 시 `PortfolioHubUnavailableError`가 발생하면: - - 실현손익 섹션에 에러 메시지를 명시한다(`portfolio-hub unavailable — realized PnL unavailable` 또는 동등한 식별 가능한 문구). - - 당일 net 값으로 대체(폴백) 하지 않는다. - - 나머지 리포트 섹션(보유/체결/대기주문)은 정상적으로 조회·출력한다. -- `--json` 모드에서도 동일하게 실현손익 필드를 `null` 또는 `{"error": "..."}` 형태로 표기하고 나머지 섹션은 정상 포함한다. -- portfolio-hub 호출 실패가 `trex-core-daemon`의 슬롯/집행 루프, AMQP 연결, safety-sell 등 데몬 본업에 영향을 주지 않는다(NFR-3: report는 CLI 단독 실행, 데몬 루프 경계 외부). -- portfolio-hub `base_url` 미설정 시도 동일하게 degrade 처리한다(설정 부재 = 미가용과 동일 경로). - -## Acceptance Criteria -1. Given portfolio-hub가 `PortfolioHubUnavailableError`를 raise 할 때, When `report` 실행, Then 텍스트 출력의 실현손익 섹션에 에러 문구가 표시되고 보유/체결/대기주문 섹션은 정상 출력된다. -2. Given portfolio-hub 미가용 상황에서 `--json` 플래그, When `report` 실행, Then JSON 출력의 실현손익 관련 필드가 에러 식별 가능한 값(`null` 또는 `"error"` 키 포함)이고 `holdings`, `pending`, `fills_count` 필드는 정상 포함된다. -3. Given portfolio-hub 미가용, When `report` 실행, Then 당일 net 값으로 대체(폴백)하지 않는다(당일 net 실현손익 값이 출력에 포함되지 않는다). -4. Given portfolio-hub `base_url` 미설정(`cfg.portfolio_hub is None` 또는 `base_url` 빈값), When `report` 실행, Then AC-1/AC-2와 동일한 degrade 경로를 밟는다. -5. Given `report` CLI가 portfolio-hub 호출 실패로 degrade하는 상황, When 데몬 본업(슬롯 RPC, execution 루프, safety-sell 트리거) 확인, Then report CLI의 예외가 데몬 루프로 전파되지 않는다(구조적 분리 — report는 CLI 독립 실행). - -## Impacted Area -- Docs: 없음(운영 설명은 REQ-110의 docs/operations.md 갱신에 포함) -- Modules: - - `trex_core/runtime/cli.py` — `_cmd_report` 내 `PortfolioHubUnavailableError` 및 설정 부재 catch, degrade 분기 -- Tests: - - `tests/runtime/test_cli.py` 또는 `tests/application/test_operator_report.py` — portfolio-hub 미가용 모킹 테스트(AC-1~4), 폴백 미발생 단언(AC-3) - -## Notes -- OQ-5 결정(사장님, 2026-06-26): 폴백 없이 실현손익 섹션 에러 명시. 나머지 섹션 정상. -- AC-5(데몬 본업 격리)는 구조적으로 `_cmd_report`가 `main()`의 `if args.cmd == "report"` 분기에서 독립 실행됨으로써 보장된다. 데몬 루프(`daemon.py`)는 report CLI를 호출하지 않는다. -- NFR-1(wire 계약 불변)은 이 REQ와 무관(report는 read-only, AMQP 미관여). - -## Change Log - -### CHG-20260626-02 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- BAT-052 구현·검증 완료(PR #33, 머지 커밋 81d06d9). confirm-batch-verification + confirm-req-implemented. Approved → Implemented. - -#### Intent -- dcy-035 FR-5 delivery 완료 확인 및 상태 전환. - -#### Acceptance Criteria Delta -- Updated: - - none (모든 AC-1~5 검증 완료, 상태 전환만) -- Added: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: 없음 -- Modules: 변경 없음 (구현 완료 상태 유지) -- Tests: 717 passed / 47 skipped / 0 fail (BAT-052 verification) - -#### Delivery Trace Delta -- Batches: - - BAT-052 (verified, PR #33, 머지 커밋 81d06d9, 2026-06-26) -- Releases: - - none (M3 배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: none - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- dcy-035 FR-5, OQ-5(에러 명시, 폴백 없음), NFR-3(데몬 본업 격리)로부터 최초 생성(Proposed). - -#### Intent -- portfolio-hub 미가용 시 운영 리포트가 안전하게 degrade하고 데몬 본업에 영향이 없음을 요구사항으로 명시. - -#### Acceptance Criteria Delta -- Added: - - AC-1~5 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - cli.py _cmd_report degrade 분기 -- Tests: - - 미가용 모킹 단위 테스트 신규 - -#### Delivery Trace Delta -- Batches: - - none (미배치) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md b/docs/srs/Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md deleted file mode 100644 index 50d2e55..0000000 --- a/docs/srs/Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md +++ /dev/null @@ -1,128 +0,0 @@ -# REQ-120: KIS balance 인증 실패 fail-loud 및 broker/token lifecycle 정책 - -- Status: Implemented -- Type: Exception -- Priority: High -- Owner: trex-core-dev - -## Intent -- 현재 KIS balance 조회의 token/auth/transport 실패가 빈 잔고와 같은 `[]`로 해석되면 `broker_qty_zero`, sync skip 또는 잘못된 reconcile 판단으로 이어질 수 있다. -- 실제 empty holdings와 broker-read 실패를 명확히 분리하고, fetched credential operator 경로의 token/broker lifecycle을 throttle-safe·secret-safe 제약 아래 설계 가능하게 한다. - -## Requirement -- 시스템은 KIS token issuance, authentication/authorization, HTTP transport 및 malformed balance response 실패를 성공적인 empty balance와 구별되는 typed 또는 동등하게 식별 가능한 sanitized failure로 표면화해야 한다. -- `sync`/`verify`를 포함한 operator broker-read 경로는 이러한 실패 시 비0 종료하고 `broker_qty_zero`, sync skip, verify success 또는 slot state write를 생성하지 않아야 한다. -- fetched credential broker/token lifecycle의 구현 설계는 다음 제약을 충족해야 한다: ownership 경계와 재사용 범위가 명시될 것, 반복 read가 매번 fresh broker/token을 강제해 throttle을 유발하지 않을 것, retry 여부·횟수·backoff/즉시 실패 기준이 failure class별로 문서화될 것, token/credential은 로그·CLI JSON·fixture에 기록되지 않을 것. - -## Acceptance Criteria -1. Given KIS token issuance, auth/authorization, transport 또는 malformed response failure fixture, When `get_balance()`와 `sync --dry-run` 또는 `verify`를 실행하면, Then 성공 empty balance와 구별되는 sanitized failure가 반환되고 CLI는 비0으로 종료한다. -2. Given AC-1의 각 failure, When 결과와 persistence/log side effect를 검사하면, Then `broker_qty_zero`, sync skip, verify success, slot state write 또는 empty-holdings 판정이 생성되지 않는다. -3. Given 정상적으로 성공한 KIS balance response가 실제로 holdings 없이 반환됨, When 동일 경로를 실행하면, Then success empty balance semantics는 auth/transport failure와 구별되어 기존 정상 판단이 유지된다. -4. Given 반복 fetched-credential broker read와 token throttle 재현 fixture, When 구현된 lifecycle 정책을 적용해 읽기를 수행하면, Then fresh-object 생성이 반복되어 throttle을 유발하는 경로에 의존하지 않고, throttle이 발생해도 fail-loud semantics가 유지된다. -5. Given lifecycle 설계 검토, When implementation-ready decision을 기록하면, Then ownership/reuse scope, cache 또는 reuse 무효화 조건, failure class별 retry/no-retry 정책 및 regression test seam이 명시되며 process-local reuse, shared cache 또는 다른 공통 provider 중 특정 방식을 사전 근거 없이 요구하지 않는다. -6. Given 오류 출력, 구조화 로그 및 테스트 artifact, When auth/token failure를 검증하면, Then token, authorization header, app key/app secret, raw credential payload 및 raw account number가 노출되지 않는다. - -## Impacted Area -- Docs: `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, `docs/srs/Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md` -- Modules: `trex_core/adapters/broker/kis.py`, `trex_core/runtime/cli.py`, `trex_core/runtime/daemon.py`, 공통 broker provider/factory -- Tests: KIS adapter unit tests; CLI failure/no-write tests; lifecycle/throttle regression fixture; sanitized logging/JSON assertions - -## Notes -- Source Discovery: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (FR-2~3, OQ-2; GitHub #40). -- REQ-115의 day-start/intraday cache 목표와 충돌하지 않아야 한다. 본 REQ는 OQ-2의 구현 방식을 확정하지 않고, 설계 선택의 안전 제약과 검증 가능한 결과를 고정한다. -- Production live sync target 또는 실행은 OQ-3 release gate로서 범위 밖이다. - -## Change Log - -### CHG-20260714-01 -- Date: 2026-07-14 -- Author: delivery-runner - -#### Change Summary -- dcy-037 #40의 auth/token failure empty-balance masking과 OQ-2 lifecycle 제약을 Proposed REQ로 초안화했다. - -#### Intent -- false-zero 기반 상태 수렴을 막고 lifecycle 구현 선택을 근거 기반 설계 단계로 남긴다. - -#### Acceptance Criteria Delta -- Added: - - fail-loud/no-write, true-empty 분리, throttle 회귀, lifecycle decision constraints 및 secret-safe 증거. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - runtime/interface/operations 및 REQ-115 정합 대상. -- Modules: - - KIS adapter와 daemon/CLI broker-read 경계. -- Tests: - - failure injection, no-write, throttle 및 redaction regression. - -#### Delivery Trace Delta -- Batches: - - none (draft only; dcy-037 emergency scope의 향후 입력). -- Releases: - - none (production 접근 또는 live sync 실행 없음). - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 전 현재 KIS token cache와 daemon/CLI lifetime evidence를 설계 결정에 연결한다. - -#### Validation Plan -- mocked KIS failure classes와 repeated-read throttle fixture에서 exit code, semantic result, no-write 및 artifact redaction을 확인한다. - -### CHG-20260714-02 -- Date: 2026-07-14 -- Author: Justin (TREX lead) - -#### Change Summary -- Proposed에서 Approved로 승인했다. - -#### Intent -- dcy-037 #40의 false-zero 방지와 OQ-2 lifecycle 설계 제약을 구현 가능한 safety contract로 확정한다. - -#### Acceptance Criteria Delta -- Added: - - 없음. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - 없음. -- Modules: - - 없음. -- Tests: - - 없음. - -#### Delivery Trace Delta -- Batches: - - dcy-037의 #39/#40/#41 단일 emergency batch (아직 미생성). -- Releases: - - none; `sync --live`는 별도 release approval gate 유지. - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: batch design에서 lifecycle ownership/reuse와 failure-class별 retry 정책을 code evidence로 결정한다. - -#### Validation Plan -- batch verification과 독립 QC에서 failure/no-write·true-empty·throttle·redaction AC를 실제 fixture로 재검증한다. - -### CHG-20260715-03 -- Date: 2026-07-15 -- Author: TREX lead - -#### Change Summary -- Approved → Implemented (`confirm-req-implemented`). - -#### Delivery Trace -- BAT-057 verification confirmed; PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`. -- Independent QC: PASS-WITH-NOTES, acceptance blocker 0. Production live sync/deploy remains a separate release gate. diff --git a/docs/srs/Installation/req-001_trex-core-bootstrap.md b/docs/srs/Installation/req-001_trex-core-bootstrap.md deleted file mode 100644 index 50cf3b2..0000000 --- a/docs/srs/Installation/req-001_trex-core-bootstrap.md +++ /dev/null @@ -1,154 +0,0 @@ -# req-001: trex-core Python 프로젝트 부트스트랩 - -- Status: Implemented -- Type: Installation -- Priority: High -- Owner: project maintainers - -## Intent -- 빈 베이스인 trex-core 를 설치/테스트 가능한 Python 패키지로 세워, 이후 slot 순수 도메인 이식과 테스트가 가능한 기반을 만든다. -- 패키지 네임스페이스와 툴체인을 dcy-001 결정(`trex_core`, trex-orbit 동등 툴체인)에 맞춰 고정한다. - -## Requirement -- `pyproject.toml`(배포명 `trex-core`, `requires-python >=3.12`, setuptools build backend, dev extras 에 `pytest`·`ruff`)과 패키지 레이아웃(`trex_core/__init__.py`, `trex_core/domain/__init__.py`), `tests/` 스캐폴딩, pytest/ruff 설정을 생성한다. - -## Acceptance Criteria -1. Given 클린 가상환경, When `pip install -e .[dev]` 를 실행, Then 설치가 성공하고 `import trex_core` 가 가능하다. -2. Given 부트스트랩 완료 상태, When `pytest` 를 실행, Then 테스트 수집/실행이 에러 없이 동작한다(테스트 0개여도 수집 자체는 성공). -3. Given `pyproject.toml`, When 내용을 확인, Then `name = "trex-core"`, `requires-python >=3.12`, setuptools build-backend, dev extras 에 `pytest`·`ruff` 가 포함된다. -4. Given 부트스트랩 완료 상태, When `ruff check .` 를 실행, Then 린트가 설정대로 동작한다(설정 부재 에러 없음). - -## Impacted Area -- Docs: docs/project-structure.md (레이아웃 baseline — REQ-004 에서 정합 갱신) -- Modules: pyproject.toml, trex_core/__init__.py, trex_core/domain/__init__.py -- Tests: tests/ 디렉터리 스캐폴딩(`tests/__init__.py`, `tests/domain/slot/__init__.py`) - -## Notes -- 네임스페이스 `trex_core` 는 dcy-001 OQ-1 결정. 툴체인 동등(Python >=3.12 + setuptools + pytest + ruff)은 dcy-001 confirm 확정. -- 본 REQ 는 REQ-002(도메인 이식)·REQ-003(테스트)의 선행 조건이다. -- 출처: dcy-001 FR-1. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-001 FR-1 로부터 최초 생성(Proposed). - -#### Intent -- trex-core 베이스 부트스트랩 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 설치 성공 및 import, pytest 수집, pyproject 메타, ruff 동작 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md (레이아웃 baseline) -- Modules: - - pyproject.toml, trex_core 패키지 스캐폴딩 -- Tests: - - tests/ 스캐폴딩 - -#### Delivery Trace Delta -- Batches: - - none (미배치) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 Approved 전환 후 batch 편성 - -#### Validation Plan -- 클린 환경에서 `pip install -e .[dev]` + `pytest` + `ruff check` 실행 결과로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-001(confirmed, handoff 승인)과 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-001 release-candidate). - -#### Intent -- BAT-001 verification.md 의 acceptance mapping + evidence(설치/pytest/ruff/메타)로 AC1~AC4 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-001 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-001 verification evidence 재참조로 충분. diff --git a/docs/srs/Installation/req-005_slot-core-daemon-runtime.md b/docs/srs/Installation/req-005_slot-core-daemon-runtime.md deleted file mode 100644 index 66211ee..0000000 --- a/docs/srs/Installation/req-005_slot-core-daemon-runtime.md +++ /dev/null @@ -1,275 +0,0 @@ -# req-005: 슬롯 코어 데몬 런타임 부트스트랩 - -- Status: Implemented -- Type: Installation -- Priority: High -- Owner: project maintainers - -## Intent -- trex-core 를 장기 실행 데몬으로 기동하는 런타임 기반(엔트리포인트 + lifecycle)을 만들어, 이후 HTTP API·영속화 레이어가 그 위에서 동작하도록 한다. - -## Requirement -- `pyproject.toml` 에 FastAPI·uvicorn·psycopg 의존성과 console_scripts 엔트리포인트를 추가하고, 데몬을 기동/정상종료하는 lifecycle 과 `GET /v1/healthz` 를 제공한다. 거래일 슬롯 사전할당(preallocate)은 공개 엔드포인트가 아니라 데몬 lifecycle(기동/스케줄) 내부에서 수행한다. - -## Acceptance Criteria -1. Given 설치된 trex-core, When 콘솔 스크립트로 데몬을 기동, Then 프로세스가 떠서 `GET /v1/healthz` 가 200 을 반환한다. -2. Given 기동된 데몬, When 종료 시그널, Then 진행 중 요청을 정리하고 graceful shutdown 한다. -3. Given 데몬 lifecycle, When 거래일 시작(기동/스케줄), Then 슬롯 preallocate 가 멱등하게 1회 수행된다(공개 엔드포인트 없이). -4. Given `pyproject.toml`, When 확인, Then FastAPI·uvicorn·psycopg 의존성과 데몬 console_scripts 가 존재한다. -5. (CHG-20260611-01) Given `safety_sell_enabled=false`, When `daemon.start()`, Then 스레드 목록에 `trex-safety-sell` 이 없고 RUNNING 로그에 `safety_sell_enabled=false` + `no autonomous liquidation` 경고가 포함된다. `true`(기본)이면 기존과 동일. -6. (CHG-20260611-01) Given `safety_sell_enabled` 임의 값, When 데몬 동작, Then `ExecutionService`·운영자 CLI exit/cancel·kill-switch·전략 sell intent 는 영향받지 않는다(자율 청산 워커만 게이팅). - -## Impacted Area -- Docs: docs/runtime-flows.md(데몬 흐름; CHG-20260611-01 safety-sell 비활성 모드 플로우), docs/project-structure.md(런타임 레이어) — REQ-009 에서 정합 -- Modules: pyproject.toml, trex_core/ 하위 데몬 런타임/엔트리포인트(애플리케이션 레이어), (CHG-20260611-01) `trex_core/runtime/daemon.py` `start()` 워커 조건부 구성 -- Tests: 데몬 기동/healthz/shutdown 통합테스트, (CHG-20260611-01) safety-sell 워커 게이팅 - -## Notes -- 프레임워크 FastAPI+uvicorn(dcy-002 OQ-2). API 엔드포인트 자체는 REQ-007/008, 영속화는 REQ-006. -- REQ-006(영속화)·REQ-007/008(API)의 선행 런타임 기반이다. -- 출처: dcy-002 FR-1. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-002 FR-1 로부터 최초 생성(Proposed). - -#### Intent -- 슬롯 코어 데몬 런타임 부트스트랩 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 기동/healthz, graceful shutdown, lifecycle preallocate, deps/entrypoint 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/project-structure.md -- Modules: - - pyproject.toml, 데몬 런타임/엔트리포인트 -- Tests: - - 데몬 lifecycle 통합테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- 데몬 기동 + healthz + shutdown 통합테스트로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-002(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-002 release-candidate). - -#### Intent -- BAT-002 verification.md 의 acceptance mapping + evidence(pytest 74 pass / PG 통합 / 데몬 E2E)로 모든 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-002 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-002 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-014/issue #14) 데몬 부트스트랩의 슬롯 preallocation 을 시스템 UTC 날짜 1회 생성에서 **설정 타임존(기본 `Asia/Seoul`) 기준 `trading_date` 일일 멱등 생성**으로 전환. `DailyPreallocationScheduler`(REQ-030 SnapshotScheduler 패턴 준용) 신규 + `setup()` 배선 + `trex-preallocation` 스레드. timestamp 는 UTC 유지. BAT-018 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- `trading_date` 가 KST 캘린더 일자와 일치하고, 재시작 없이 TZ 일자 경계마다 다음날 슬롯이 자동 생성되도록(issue #14). - -#### Acceptance Criteria Delta - -- Updated: - - setup preallocation 이 UTC 날짜 → 설정 TZ `trading_date` 기준. 일일 멱등 preallocation 스레드 추가(TZ 일자당 1회). - -#### Impacted Area Delta - -- Modules: `trex_core/application/preallocation_scheduler.py`(신규), `trex_core/runtime/daemon.py`(setup TZ preallocation + 스레드 배선) -- Tests: tests/application/test_preallocation_scheduler.py - -#### Delivery Trace Delta - -- Batches: BAT-018 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-018 에서 완료) -- Existing Implementation Invalidated: yes (UTC 날짜 1회 preallocation 경로) -- Status Recommendation: Implemented (change-req → BAT-018 재검증) -- Follow-up Action: confirm-req-implemented(BAT-018). - -#### Validation Plan - -- BAT-018: KST/UTC 경계 trading_date, 일자 경계 자동 생성+멱등, 설정 TZ, 트리거 시각 게이팅 단위. - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020) 일일 슬롯 preallocation(`preallocate_daily_slots`/00:00 트리거)이 **폐기 예정** — REQ-058(universe reconcile 기반 슬롯 생성/소멸)로 대체. `TREX_TOTAL_SLOTS`(풀 크기)는 REQ-060(`max_active_slots` per-instance TOML)로 대체. trading_date KST 계산(REQ-005 NFR-1)은 일일 회계 경계용으로 **부분 존속**. 현재 코드 불변이라 Implemented 유지(keep-current), dcy-020 delivery 배치(reconcile)에서 preallocation 제거·확정. - -#### Intent - -- preallocation→universe reconcile 전환의 추적성 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(dcy-020 delivery 배치에서 preallocation AC 폐기) - -#### Impacted Area Delta - -- Docs: dcy-020, REQ-058/060 -- Modules: (예정) application/preallocation_scheduler.py 폐기, runtime/config.py(TREX_TOTAL_SLOTS 제거) -- Tests: (예정) test_preallocation_scheduler 폐기/대체 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-020 reconcile 배치 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점) -- Existing Implementation Invalidated: no (코드 불변) -- Status Recommendation: keep-current -- Follow-up Action: dcy-020 reconcile 배치에서 preallocation 폐기 + status 전환. trading_date KST 계산은 존속. - -#### Validation Plan - -- dcy-020 reconcile 배치 verification 으로 대체. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-024) 데몬 부트스트랩 `start()` 의 workers 구성을 **`safety_sell_enabled`(REQ-048 CHG-20260611-01) 조건부**로 한다 — `false` 면 `trex-safety-sell` 워커 스레드를 workers 리스트(`daemon.py:335`)에 포함하지 않는다(스레드 미기동이 물리적으로 자율 청산 차단, run_once if-가드보다 강함). 기동 RUNNING 로그에 `safety_sell_enabled=` 를 출력하고 `false` 면 `no autonomous liquidation` 경고를 남긴다. `ExecutionService`·운영자 CLI(`enter`/`exit`/`cancel`)·kill-switch·messaging 핸들러는 불변(자율 청산 워커만 OFF). `docs/runtime-flows.md` 에 비활성 모드 플로우 반영. - -#### Intent - -- 관찰 전용·중장기 보유 인스턴스에서 자율 청산(forced_close/SL·TP/장마감)을 끄고 전략·운영자 지시 매도만 허용(dcy-024 FR-2/FR-3, issue #15). - -#### Acceptance Criteria Delta - -- Added: - - AC-5: `safety_sell_enabled == False` 이면 `daemon.start()` 후 스레드 목록에 `trex-safety-sell` 이 없고, RUNNING 로그에 `safety_sell_enabled=false` + `no autonomous liquidation` 경고가 포함된다. `True`(기본)이면 기존과 동일 기동 + 회귀 없음. - - AC-6: `safety_sell_enabled` 와 무관하게 `ExecutionService`·운영자 CLI exit/cancel·kill-switch·전략 sell intent 는 정상 동작한다(자율 청산 워커만 게이팅). -- Updated/Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/daemon.py`(`start()` workers 조건부 구성 + 기동 로그) -- Docs: `docs/runtime-flows.md`(safety-sell 비활성 모드 플로우) -- Tests: tests/runtime/(enabled=False → `trex-safety-sell` 스레드 부재, 로그 경고; enabled=True 회귀) - -#### Delivery Trace Delta - -- Batches: 신규 batch(REQ-048 CHG-20260611-01 과 동일 배치, dcy-024) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 AC-5/6 검증) -- Existing Implementation Invalidated: no (기본 true 시 기존 동작 불변, 조건부 게이팅 additive) — 단 신규 AC 미구현 -- Status Recommendation: Implemented → Approved (신규 AC-5/6 미구현, batch 재검증 후 Implemented 복귀) -- Follow-up Action: draft-batch → run-batch-implementation → verification → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: `enabled=False` config 주입 + `daemon.start()` 후 thread 이름에 `trex-safety-sell` 부재 단위, RUNNING 로그 경고, run-the-app 스모크(비활성 모드 holding 유지·전략 sell 정상), enabled=True 회귀. diff --git a/docs/srs/Integration/req-011_broker-port-and-paper-double.md b/docs/srs/Integration/req-011_broker-port-and-paper-double.md deleted file mode 100644 index fd7afad..0000000 --- a/docs/srs/Integration/req-011_broker-port-and-paper-double.md +++ /dev/null @@ -1,245 +0,0 @@ -# req-011: BrokerPort 추상화 + paper/stub async 더블 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 브로커를 코어에서 추상화하는 `BrokerPort` 를 정의하고(불변식 B 의 단일 경계), 결정적 검증용 paper/stub 어댑터를 제공한다. 이후 KIS 어댑터(REQ-012)와 집행 루프(REQ-013)가 이 포트 위에서 동작한다. - -## Requirement -- `BrokerPort`(application 포트)에 `submit_order(side, symbol, qty, ...) -> broker_order_id`, `cancel_order(broker_order_id)`, `poll_fill(broker_order_id) -> FillStatus(accepted/partially_filled/filled/rejected/cancelled, avg_price, filled_qty)` 를 정의한다. paper/stub 어댑터는 동기 단축 없이 submit→poll 비동기 계약을 따르되 체결 시점·부분체결·거부를 프로그래밍 가능한 결정적 더블로 제공한다. 어댑터는 adapters 레이어에 두고 도메인은 의존하지 않는다. -- paper 어댑터의 order seq 발번은 optional seq store(주입 가능) 위에서 동작한다. seq store 가 주입되면 order seq 는 데몬 재시작 간에도 단조 증가해야 하며(Redis 영속, REQ-016 `RedisTokenCache` 패턴 재사용·best-effort), 미주입 시 기존 in-memory 카운터로 폴백한다. 이는 재시작 후 결정적 `execution_id` 재생성으로 인한 원장 `correlation_id` 충돌(매도 원장 누락)을 방지하기 위한 paper 한정 결정성 보강이다(issue #4). - -## Acceptance Criteria -1. Given `BrokerPort` 정의, When paper 어댑터로 `submit_order`, Then `accepted` + broker_order_id 를 반환하고 즉시 `filled` 로 단축하지 않는다(다음 `poll_fill` 에서 체결). -2. Given paper 더블의 fill 시나리오 설정(즉시/N틱 후/부분/거부), When `poll_fill` 반복, Then 설정대로 결정적으로 상태가 진행된다. -3. Given 코드, When import 스윕, Then `trex_core.domain.slot` 은 broker/네트워크 의존이 0이고 BrokerPort 구현은 adapters 레이어에만 있다. (NFR-1/NFR-2) -4. Given paper 어댑터에 seq store 가 주입된 상태, When 데몬 재시작(seq store 영속)을 모사해 두 번째 인스턴스에서 `submit_order`, Then order seq 는 첫 인스턴스 이후로 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입 시 기존 in-memory `PAPER-000001` 발번으로 폴백하며 AC-1~3 비동기 계약은 불변이다. (issue #4) - -## Impacted Area -- Docs: docs/interface-contract.md(브로커 경계) — REQ-015 에서 정합 -- Modules: trex_core/application(BrokerPort), trex_core/adapters/broker(paper/stub, `seq_store.py` 신규), trex_core/runtime(config `PaperBrokerConfig.redis_url`, daemon `_build_broker` wiring) -- Tests: paper 더블 fill 시나리오 테스트, paper seq store 재시작 단조성 + correlation_id 비충돌 + in-memory 폴백 테스트 - -## Notes -- 불변식 B 의 단일 경계. KIS 어댑터는 REQ-012, 집행 루프는 REQ-013. -- 출처: dcy-003 FR-1(포트+paper), NFR-1/NFR-2 흡수. -- (issue #4/CHG-20260612-01) paper seq Redis 영속화는 paper 한정 테스트 하네스 결정성 보강이며 프로덕션(KIS `odno` 고유) 버그가 아니다. 원장 `correlation_id` 에 side 를 추가하는 대안 대신, 발번 단조성 보강(영향 최소·계약 불변)을 선택했다. Redis 인프라는 REQ-016 token_cache 패턴을 재사용한다. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-003 FR-1(포트+paper) 로부터 최초 생성(Proposed). - -#### Intent -- BrokerPort + paper async 더블 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - submit→poll 비동기 계약, 결정적 fill 시나리오, 도메인 순수성 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md -- Modules: - - application(BrokerPort), adapters/broker(paper) -- Tests: - - paper 더블 시나리오 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- paper 더블 비동기 체결 시나리오 단위테스트로 검증. -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate). - -#### Intent -- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-004 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~004) - -#### Validation Plan -- BAT-004 verification evidence 재참조로 충분. - -### CHG-20260612-01 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #4) paper 브로커 order seq 를 **재시작 간 단조 증가**하도록 영속화한다. 현재 `_seq` in-memory 카운터는 데몬 재시작 시 0 으로 리셋되어 `PAPER-000001` 부터 재발번 → 결정적 `execution_id`(`paper:{order_id}:{filled_qty}`) 재생성 → 원장 멱등 키 `correlation_id = {asset_id}:{order_id}:{execution_id}`(side 미포함)가 직전 run 의 매수와 충돌 → `ON CONFLICT DO NOTHING` dedup 으로 **정상 매도 원장 행이 조용히 누락**. paper 어댑터에 optional seq store(Redis-backed, REQ-016 `RedisTokenCache` 패턴 재사용)를 주입해 해소하고, Redis 미구성 시 기존 in-memory 동작으로 폴백. 신규 AC-4 가 추가 구현·검증을 필요로 하므로 **Implemented → Approved 되돌림**. - -#### Intent - -- paper 모드 테스트 하네스의 재시작 후 멱등 키 충돌(매도 원장 누락)을 제거한다. 실 KIS 는 broker order_id(`odno`)가 주문마다 고유해 충돌이 없으므로 **프로덕션 버그가 아닌 paper 한정 결정성 문제**다. 기존 BrokerPort/paper 비동기 계약(AC-1~3)은 불변, seq 발번 영속화만 추가. - -#### Acceptance Criteria Delta - -- Added: - - AC-4: paper 브로커에 seq store 가 주입되면 order seq 는 재시작 후에도 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 의 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입(Redis 미구성) 시 기존 in-memory `_seq` 동작으로 폴백하며 비동기 fill 계약(AC-1~3)은 불변이다. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Docs: - - docs/srs/index.md (Register Implemented → Approved) -- Modules: - - `trex_core/adapters/broker/paper.py`(optional seq store 주입 + `submit_order` seq 발번 경로) - - `trex_core/adapters/broker/seq_store.py`(신규: `SeqStore` 포트 + `RedisSeqStore`(INCR 기반, best-effort) + `InMemorySeqStore` 폴백, token_cache 패턴 정합) - - `trex_core/runtime/config.py`(`PaperBrokerConfig.redis_url` 추가) - - `trex_core/runtime/daemon.py`(`_build_broker` paper 경로 seq store wiring) -- Tests: - - 재시작 시뮬레이션(seq store 공유) 후 매도 correlation_id 비충돌 + 폴백 동작 단위 테스트 - -#### Delivery Trace Delta - -- Batches: - - BAT-039 (예정, issue #4) -- Releases: - - none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-039 에서 신규 AC-4 + AC-1~3 회귀) -- Existing Implementation Invalidated: no (기존 비동기 계약/도메인 순수성 불변, 발번 경로에 optional 영속화만 추가). 단 신규 AC-4 미구현으로 Implemented 유지 불가 → Approved. -- Status Recommendation: Implemented → **Approved** (구현·검증 후 confirm-req-implemented 로 복귀) -- Follow-up Action: confirm-req → draft-batch(BAT-039) → 구현·검증 → confirm-req-implemented → issue #4 close - -#### Validation Plan - -- BAT-039: (a) 같은 seq store 를 공유한 두 PaperBroker 인스턴스(재시작 모사)에서 두 번째 인스턴스의 첫 order_id 가 첫 인스턴스 이후로 단조 증가, (b) 매수 후 재시작 매도 시 correlation_id 비충돌(원장 dedup 미발생), (c) seq store 미주입 시 기존 `PAPER-000001` 부터 in-memory 동작 폴백, (d) AC-1~3 기존 paper 테스트 회귀 없음. `pytest` 전체 + `ruff check` clean. - -### CHG-20260612-02 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- Status 변경: Approved → **Implemented** (confirm-req-implemented, 근거 batch BAT-039 release-candidate). - -#### Intent - -- BAT-039 verification.md 의 acceptance mapping + evidence(434 passed / seq store 재시작 단조·correlation_id 비충돌·in-memory 폴백 / fake redis INCR 단조)로 AC-1~4 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 (상태 전이만) - -#### Impacted Area Delta - -- Docs/Modules/Tests: 없음 (상태 전이만) - -#### Delivery Trace Delta - -- Batches: - - BAT-039 (release-candidate) -- Releases: - - none (release 편성은 별도 진행) - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: draft-release 로 release 편성(BAT-039 포함) + issue #4 close - -#### Validation Plan - -- BAT-039 verification evidence 재참조로 충분. - - AC-4: `test_shared_seq_store_is_monotonic_across_restart`, `test_post_restart_sell_does_not_collide_with_prior_buy`, `test_in_memory_fallback_starts_at_one`, `test_redis_seq_store_incr_is_monotonic_and_shared`. - - AC-1~3: 전체 회귀 434 passed(기존 paper 더블 시나리오 + 도메인 순수성 회귀 없음). diff --git a/docs/srs/Integration/req-012_kis-broker-adapter.md b/docs/srs/Integration/req-012_kis-broker-adapter.md deleted file mode 100644 index 38baa4a..0000000 --- a/docs/srs/Integration/req-012_kis-broker-adapter.md +++ /dev/null @@ -1,191 +0,0 @@ -# req-012: KIS Open API 브로커 어댑터 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- `BrokerPort`(REQ-011)를 구현하는 실제 KIS(한국투자증권) Open API 어댑터를 추가해, 코어가 실제 주문 송신/취소/체결 조회를 수행할 수 있게 한다. 슬라이스에서는 KIS 모의투자 서버로 검증한다. - -## Requirement -- KIS Open API 로 `submit_order`/`cancel_order`/`poll_fill`(체결 조회)을 구현하고, 접속 토큰 발급·갱신, 레이트리밋, 모의투자/실계좌 base URL 전환을 어댑터 내부에서 처리한다. 자격증명은 env/.env(REQ-010 패턴)로 주입한다. 실계좌 라이브 전환은 config 게이팅(기본 모의투자)으로 둔다. - -## Acceptance Criteria -1. Given KIS 모의투자 자격증명, When 어댑터 기동, Then 토큰을 발급/갱신하고 인증된 호출이 가능하다. -2. Given 매수/매도 주문, When `submit_order`, Then KIS 모의투자에 주문이 접수되고 broker_order_id(주문번호)를 반환한다. -3. Given 접수된 주문, When `poll_fill`, Then KIS 체결 조회로 체결/부분체결/거부 상태를 반환한다. -4. Given config, When 실계좌/모의투자 전환, Then 기본은 모의투자이며 실계좌는 명시적 게이팅 없이는 활성화되지 않는다. -5. Given 자격증명 미설정/토큰 실패, When 호출, Then 명확한 오류로 실패하고 코어 슬롯 상태를 오염시키지 않는다. - -## Impacted Area -- Docs: docs/interface-contract.md(브로커 경계/KIS), docs/runtime-flows.md — REQ-015 -- Modules: trex_core/adapters/broker(kis), config(KIS 자격증명/base URL/게이팅), pyproject(HTTP 의존) -- Tests: KIS 모의투자 통합테스트(자격증명 가용 시), 토큰/오류 처리 단위테스트 - -## Notes -- 실거래 리스크(dcy-003 R-5): 슬라이스는 모의투자만, 실계좌 라이브는 Out of Scope(게이팅). -- KIS 모의투자 자격증명이 검증 환경에 없으면 통합테스트는 skip, 단위(토큰/오류)만 수행 — verification 에서 분리 판단. -- 출처: dcy-003 FR-1(KIS), OQ-1. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-003 FR-1(KIS 어댑터) 로부터 최초 생성(Proposed). - -#### Intent -- 실제 KIS Open API 어댑터 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 토큰, 주문 접수, 체결 조회, 모의투자/실계좌 게이팅, 오류 처리 5개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md -- Modules: - - adapters/broker(kis), config, pyproject -- Tests: - - KIS 모의투자 통합 + 토큰/오류 단위 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- KIS 모의투자 통합테스트(가용 시) + 토큰/오류 단위테스트로 검증. -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate). - -#### Intent -- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-004 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~004) - -#### Validation Plan -- BAT-004 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013/issue #9) KIS `poll_fill` 이 `inquire-daily-ccld` 응답의 `cncl_yn="Y"` 를 파싱해 외부 취소를 감지한다. 체결 0 취소 → `FillStatus(state="cancelled")`, 부분 체결 후 취소 → `state="partially_filled" + cancelled=True`(execution_id/fee 운반). 미지/부재 값은 보수적 비취소. BAT-017 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 계좌 직접 취소가 `accepted` 로 오판되어 슬롯이 pending_sell 에 영구 갇히던 갭(issue #9) 제거 — KIS 어댑터가 취소를 종료 상태로 정확히 매핑. - -#### Acceptance Criteria Delta - -- Added: - - poll_fill 이 cncl_yn 을 파싱: 체결 0 취소 → cancelled, 부분 체결 후 취소 → partially_filled+cancelled. - -#### Impacted Area Delta - -- Modules: `trex_core/adapters/broker/kis.py`(poll_fill), `trex_core/application/broker_port.py`(FillStatus.cancelled/is_partial_cancel) -- Tests: tests/adapters/broker/test_kis_cancel.py - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-017 에서 완료) -- Existing Implementation Invalidated: yes (취소를 accepted 로 오판하던 poll_fill) -- Status Recommendation: Implemented (change-req → BAT-017 재검증) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 취소 fixture(full/partial/non-cancel/full-fill) → FillStatus 매핑 단위. diff --git a/docs/srs/Integration/req-013_order-execution-loop.md b/docs/srs/Integration/req-013_order-execution-loop.md deleted file mode 100644 index 52826f3..0000000 --- a/docs/srs/Integration/req-013_order-execution-loop.md +++ /dev/null @@ -1,279 +0,0 @@ -# req-013: 주문 집행 루프 (claim·송신·체결·롤백) - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 코어 데몬 내 백그라운드 execution 루프가 pending 슬롯을 집행해 브로커에 주문을 송신하고 체결을 슬롯 상태로 반영한다(불변식 A 단일 writer 유지). intent API 는 pending 전이까지만, 집행은 루프가 비동기 수행. - -## Requirement -- 데몬 프로세스 내 별도 execution 루프가 주기적으로: ① `pending_buy`/`pending_sell` 슬롯을 `FOR UPDATE SKIP LOCKED`(broker_order_id IS NULL)로 claim → `BrokerPort.submit_order` → `broker_order_id` 기록(옵션 A), ② broker_order_id 있는 pending 슬롯을 `poll_fill` 로 비동기 관측해 매수 체결 시 `pending_buy→holding`(avg_price/qty), 매도 체결 시 `pending_sell→closed`(recovery 재진입 +1), ③ 거부/취소 시 `pending_buy→idle` 롤백을 수행한다. 모든 슬롯 쓰기는 SlotService(advisory lock) 단일 경로. - -## Acceptance Criteria -1. Given pending_buy 슬롯(paper), When 집행 루프 1회, Then 매수 주문 송신 + broker_order_id 기록, broker_order_id 채워진 슬롯은 재디스패치되지 않는다(멱등). (NFR-2 단일 writer) -2. Given 송신된 매수 주문이 체결(poll), When 루프, Then `pending_buy→holding`(avg_price/qty 반영). -3. Given 매수 주문 거부(poll), When 루프, Then `pending_buy→idle` 롤백. -4. Given pending_sell 슬롯, When 집행+체결, Then 매도 주문 송신 → `pending_sell→closed` + 재진입 +1. -5. Given 슬롯 쓰기 경로, When 점검, Then 집행 루프도 SlotService(advisory lock)로만 슬롯을 전이한다(브로커 직접 슬롯 쓰기 없음). - -## Impacted Area -- Docs: docs/runtime-flows.md(집행/체결 흐름) — REQ-015 -- Modules: trex_core/application 또는 runtime(execution 루프), SlotService 재사용 -- Tests: paper 더블로 매수/매도 lifecycle, 멱등 재디스패치, 거부 롤백, 체결→전이 - -## Notes -- OQ-3(옵션 A 실행큐)·OQ-4(별도 루프)·OQ-2(비동기 poll) 결정 반영. 폴링 주기/배치 크기는 config. -- reconciliation·크래시 복구는 REQ-014. KIS 어댑터는 REQ-012, paper 는 REQ-011. -- 출처: dcy-003 FR-2, FR-3, FR-4. NFR-2 흡수. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-003 FR-2·FR-3·FR-4 로부터 최초 생성(Proposed). NFR-2 흡수. - -#### Intent -- 주문 집행 루프(매수/매도 claim·송신·체결·롤백) 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 매수 claim/멱등, 매수 체결→holding, 거부 롤백, 매도 체결→closed, 단일 writer 5개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md -- Modules: - - application/runtime(execution 루프) -- Tests: - - paper lifecycle/멱등/롤백/체결 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- paper 더블 매수/매도 lifecycle + 멱등 + 롤백 테스트로 검증. -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-003(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-004 release-candidate). - -#### Intent -- BAT-004 verification.md 의 acceptance mapping + evidence(pytest 92 pass / 도메인 순수성 / 데몬 E2E / KIS VTS 모의투자 round-trip)로 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-004 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성(BAT-001~004) - -#### Validation Plan -- BAT-004 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012/issue #10) `_dispatch` 의 매도 가격 버그 정정: side 분기로 `entry_price`(매수가)를 지정가로 재사용하던 로직을 제거하고, side 무관 슬롯 `limit_price` **단일 필드**만 읽어 주문유형/가격을 결정한다 — 값 있으면 `order_type="00"`(그 지정가), 없으면 `order_type="01"`(시장가). BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 매도가 매수 지정가에 걸린 지정가로 나가 미체결되는 정합성 버그(issue #10) 제거. dispatch 의 가격 결정을 `slot.limit_price` 로 일원화. - -#### Acceptance Criteria Delta - -- Updated: - - dispatch 가격/주문유형은 `slot.limit_price` 한 필드로 결정(side 분기 없음, 매수가 재사용 금지). - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_dispatch`) -- Tests: tests/application/test_sell_pricing.py(지정가 매수 슬롯의 시장가/지정가 매도, 회귀) - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: yes (entry_price 재사용 dispatch 경로) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: 지정가 매수 슬롯 + 가격 미전달 매도 → 시장가(entry_price 미사용 증명), + 가격 전달 매도 → 그 지정가, 시장가 매수 슬롯 회귀. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013/issue #9) 집행 루프에 외부 취소 처리 추가: 전량 취소(`is_terminal_reject`)는 기존 롤백 경로 재사용, 부분 체결 후 취소(`is_partial_cancel`)는 신규 `_on_partial_cancel` — 체결분 원장 1회 기록 + 슬롯 qty 차감(매도 잔여 held / 매수 체결분) + holding 복귀(OQ-1 A안). BAT-017 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 부분 체결 후 취소에서 과매도·이중기록 없이 잔여 포지션만 재매도 가능하게(NFR-3). 전량 취소는 자동 holding 복귀. - -#### Acceptance Criteria Delta - -- Added: - - `_poll` 이 is_partial_cancel 분기로 부분 체결 회계 수행(원장 1회 + qty 차감 + holding 복귀). 전량 취소는 롤백. - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_poll`, `_on_partial_cancel`, ExecutionResult.stale_pending) -- Tests: tests/application/test_execution_cancel.py - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-017 에서 완료) -- Existing Implementation Invalidated: no (신규 분기 추가, 기존 filled/reject 경로 불변) -- Status Recommendation: Implemented (change-req → BAT-017 재검증) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 전량 취소 → holding 복귀, 부분 취소 → 원장 1건 + qty 차감 + cost basis 유지 단위. - -### CHG-20260610-03 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-022 실행 정책) 집행 루프에 두 가지 확장: ① **slippage dispatch**: `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 `entry_price*(1±pct)` 실효 지정가로 주문을 제출(매수 올림/매도 내림, KRX 호가단위). ② **timeout 취소**: `_poll` 이 dispatched-pending 슬롯의 `timeout_sec` 초과 시 `broker.cancel_order` 를 송신(best-effort, 취소-체결 race는 기존 is_terminal_reject/is_partial_cancel 경로로 수렴). Implemented → Approved 되돌림(신규 집행 로직 미구현, BAT-032 에서 재구현). - -#### Intent - -- 실행 정책(slippage + timeout)을 집행 루프에 배선해 미체결 고착 예방 + 과도한 불리 체결 방지. - -#### Acceptance Criteria Delta - -- Added: - - dispatch 시 슬롯 `slippage_pct` 가 있으면 `limit_price*(1+pct)` (buy) / `limit_price*(1-pct)` (sell) 실효 지정가로 제출한다(`entry_price` 없으면 무시). - - dispatched-pending 슬롯이 `timeout_sec` 를 초과하면 `broker.cancel_order` 를 송신하고, 다음 poll 에서 is_terminal_reject / is_partial_cancel 경로로 롤백/부분 보존이 수렴한다. - - timeout/slippage 미지정 슬롯은 기존 dispatch·poll 경로와 동일(회귀 없음). -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_dispatch` slippage, `_poll` timeout cancel), ExecutionResult(+timeout_cancelled) -- Tests: tests/application/test_execution.py(slippage dispatch), test_execution_cancel.py(timeout cancel) - -#### Delivery Trace Delta - -- Batches: BAT-032 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-032 에서 완료 예정) -- Existing Implementation Invalidated: no (신규 분기 추가, 기존 dispatch/poll 경로 보존) -- Status Recommendation: revert-to-approved -- Follow-up Action: BAT-032 구현 후 confirm-req-implemented. - -#### Validation Plan - -- BAT-032: slippage 실효 지정가 단위 + timeout 취소 → 롤백/부분 보존 단위 + 회귀. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Integration/req-016_kis-token-redis-cache.md b/docs/srs/Integration/req-016_kis-token-redis-cache.md deleted file mode 100644 index d87a773..0000000 --- a/docs/srs/Integration/req-016_kis-token-redis-cache.md +++ /dev/null @@ -1,153 +0,0 @@ -# req-016: KIS 인증 토큰 Redis 캐시 - -- Status: Implemented -- Type: Integration -- Priority: Medium -- Owner: project maintainers - -## Intent -- KIS 접속 토큰을 Redis 에 캐시해 데몬 재기동·다중 프로세스에서도 토큰을 공유/생존시켜, KIS 토큰 발급 rate-limit(약 1회/분, EGW00133)을 피한다. trex-orbit 의 Redis 토큰 캐시 패턴을 trex-core 로 이식한다. - -## Requirement -- KISBroker 에 선택적 토큰 캐시(`TokenCache`)를 주입한다. `_ensure_token` 은 ① 인메모리(L1) → ② Redis 캐시(L2, 키 `trex:broker:kis:{account_ref}:token`, 토큰+만료+app_key suffix JSON, `ex=ttl`) → ③ 신규 발급 순으로 해석한다. Redis 미설정/미설치/오류 시 기존 인메모리 동작으로 graceful fallback 한다. config 는 `TREX_KIS_REDIS_URL` 로 캐시를 활성화한다. - -## Acceptance Criteria -1. Given Redis 캐시에 유효 토큰이 있음, When `_ensure_token`, Then HTTP 발급 없이 캐시 토큰을 사용한다. -2. Given 두 KISBroker 가 같은 캐시를 공유, When 하나가 발급·저장 후 다른 하나가 호출, Then 재발급 없이 캐시 토큰을 재사용한다(재기동/다중 프로세스 생존). -3. Given Redis 미설정/미설치/오류, When `_ensure_token`, Then 기존 인메모리 발급 동작으로 동작한다(회귀 없음). -4. Given 캐시된 토큰의 app_key suffix 가 현재와 불일치, When load, Then 캐시를 무시하고 재발급한다(자격증명 교체 안전). - -## Impacted Area -- Docs: docs/interface-contract.md(KIS 어댑터 노트) — 필요 시 -- Modules: trex_core/adapters/broker/token_cache.py(신규), kis.py(token_cache 주입), runtime/config.py(TREX_KIS_REDIS_URL), runtime/daemon.py(캐시 wiring), pyproject(redis 의존) -- Tests: tests/adapters/broker/test_token_cache.py (캐시 히트/공유/폴백/suffix) - -## Notes -- minor-change: 기존 BrokerPort 뒤 토큰 캐시 계층 추가(외부 인터페이스/슬롯 흐름 변경 없음). batch-lite, design 불요. -- dcy-003 가 단일 인스턴스를 가정해 슬라이스에서 분산 캐시를 미포팅했던 갭의 후속 보완. 실 Redis 없을 때도 폴백으로 안전. -- 맥락: REQ-012(KIS 어댑터) 운영 견고성 개선. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- 최초 생성(Proposed) — KIS 토큰 Redis 캐시(재기동/다중프로세스 생존) Integration 요구사항. - -#### Intent -- orbit Redis 토큰 캐시 패턴 이식으로 토큰 rate-limit·재발급 위험 완화. - -#### Acceptance Criteria Delta -- Added: - - 캐시 히트, 공유 재사용, graceful fallback, app_key suffix 안전 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md(필요 시) -- Modules: - - adapters/broker/token_cache.py, kis.py, runtime/config.py, runtime/daemon.py, pyproject -- Tests: - - tests/adapters/broker/test_token_cache.py - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: batch-lite 편성 - -#### Validation Plan -- 캐시 히트/공유/폴백/suffix 단위테스트(인메모리 캐시 더블)로 검증. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (minor-change 저위험 단일 REQ, 외부 인터페이스/런타임 흐름 변경 없음). - -#### Intent -- 승인 게이트 충족. batch-lite 로 즉시 delivery. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch-lite 편성 예정) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-batch 로 batch-lite 생성 - -#### Validation Plan -- batch-lite verification 에서 AC 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-005 release-candidate). - -#### Intent -- BAT-005 verification 의 AC↔evidence(캐시 히트/공유/폴백/suffix, 96 pass)로 모든 AC 충족. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-005 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-005 verification evidence 재참조로 충분. diff --git a/docs/srs/Integration/req-017_ledger-port-and-portfolio-hub-adapter.md b/docs/srs/Integration/req-017_ledger-port-and-portfolio-hub-adapter.md deleted file mode 100644 index 338721d..0000000 --- a/docs/srs/Integration/req-017_ledger-port-and-portfolio-hub-adapter.md +++ /dev/null @@ -1,269 +0,0 @@ -# req-017: LedgerPort 추상화 + portfolio-hub 어댑터 + stub 원장 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 체결을 외부 회계/포지션 원장(portfolio-hub)에 기록할 수 있도록, 도메인/네트워크 의존을 어댑터로 격리한 `LedgerPort` 계약과 portfolio-hub 어댑터, 그리고 결정론 검증용 stub/in-memory 원장을 제공한다. - -## Requirement -- `LedgerPort.record_trade(asset_id, order_id, execution_id, side, qty, price, fee, tax, executed_at)` Protocol 을 application 레이어에 정의한다. -- `PortfolioHubLedger` 어댑터는 라이브 portfolio-hub 계약(`LedgerTradeRequest`)을 따라 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 로 거래를 기록한다. 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용하며, `execution_id` 로 멱등하고, portfolio-hub 가 반환하는 409 충돌을 `LedgerConflictError` 로 표면화한다. (정정: CHG-20260606-01 — 구 orbit 가정 `POST /v1/assets/{asset_id}/...`, body `qty`/`fee`/`tax` 폐기) -- 결정론 테스트용 stub/in-memory 원장 구현을 제공해 네트워크 없이 체결→기록 lifecycle 을 검증할 수 있게 한다. -- 어댑터 자격증명/URL 은 .env(REQ-010 패턴, `TREX_PORTFOLIO_HUB_*`)로 주입한다. - -## Acceptance Criteria -1. Given in-memory stub 원장, When `record_trade(...)` 를 1회 호출, Then 원장에 거래 1건이 적재되고 인자(asset_id/side/qty/price/fee/tax/executed_at)가 보존된다. -2. Given portfolio-hub 어댑터, When `record_trade(...)`, Then `POST /v1/portfolios/{portfolio_id}/ledger/trades` 가 `execution_id` 를 포함해 호출되고 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용한다(라이브 `LedgerTradeRequest` 계약, 성공 응답 201). (정정: CHG-20260606-01) -3. Given portfolio-hub 가 409(중복/충돌)를 반환, When 어댑터가 응답을 처리, Then `LedgerConflictError` 로 표면화된다(예외를 삼키지 않는다). -4. Given 정적 import 스윕, When `trex_core/domain` 을 점검, Then portfolio-hub/네트워크 의존이 0 이다(LedgerPort 는 application 계약, 어댑터는 adapters 레이어 격리). (NFR-1) - -## Impacted Area -- Docs: docs/interface-contract.md(LedgerPort), docs/project-structure.md(ledger 어댑터) — REQ-020 에서 정합 -- Modules: 신규 `trex_core/application` LedgerPort 계약, 신규 `trex_core/adapters/ledger`(portfolio-hub 어댑터 + stub), `trex_core/runtime/config.py`(portfolio-hub URL/토큰) -- Tests: stub 원장 record_trade 단위테스트, 어댑터 요청 계약/409 처리 테스트, 도메인 import 스윕 - -## Notes -- 출처: dcy-004 FR-1, NFR-1. -- A-1 가정(폐기, CHG-20260606-01): orbit 동일 계약(`POST /v1/assets/{asset_id}/ledger/trades`, body `qty`/`fee`/`tax`) 가정은 라이브 portfolio-hub 실측과 불일치(404)로 확인돼 폐기. 실 계약은 `POST /v1/portfolios/{portfolio_id}/ledger/trades` + `LedgerTradeRequest`(`quantity`/`fee_amount`/`tax_amount`), 성공 201. execution_id 멱등은 유지 가정(중복 응답 코드는 후속 확인). -- 도메인 순수성(NFR-1)을 별도 REQ 로 승격하지 않고 본 REQ 의 AC-4(import 스윕)로 흡수한다. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-004 FR-1 로부터 최초 생성(Proposed). NFR-1(도메인 순수성) 흡수. - -#### Intent -- 체결을 외부 원장에 기록하기 위한 LedgerPort 계약 + portfolio-hub 어댑터 + 결정론 stub 을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - stub record_trade 적재, 어댑터 POST 계약, 409→LedgerConflictError, 도메인 import 스윕 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/project-structure.md -- Modules: - - LedgerPort 계약, portfolio-hub 어댑터 + stub, config -- Tests: - - stub/어댑터/도메인 import 스윕 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- stub 원장 단위테스트 + 어댑터 요청 계약/409 테스트 + 도메인 import 스윕으로 검증. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- LedgerPort 계약 + portfolio-hub 어댑터 + stub 원장을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - 없음 -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반. - -#### Intent -- LedgerPort/portfolio-hub adapter/in-memory stub 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 (변경은 상태 전이만) - -#### Delivery Trace Delta -- Batches: - - BAT-006 (release-candidate) -- Releases: - - none (배포 미생성) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/adapters/ledger/test_in_memory_ledger.py::test_record_trade_preserves_fields_and_is_idempotent_on_execution_id. -- AC-2: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_post_contract_includes_execution_id_and_path_uses_asset_id. -- AC-3: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_409_surfaces_as_ledger_conflict_error + tests/application/test_execution_ledger.py::test_conflict_blocks_without_auto_retry. -- AC-4: tests/domain/test_domain_import_sweep.py::test_domain_has_no_forbidden_imports. - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- 실 portfolio-hub API 와의 ledger write 계약 불일치를 정정: 엔드포인트 `POST /v1/assets/{asset_id}/ledger/trades`(부재, 404) → **`POST /v1/portfolios/{portfolio_id}/ledger/trades`**, 요청 body 키 `qty`/`fee`/`tax` → **`quantity`/`fee_amount`/`tax_amount`**. `asset_id` 값은 슬롯 `portfolio_id`(REQ-018: asset_id == portfolio_id)라 `{portfolio_id}` path 세그먼트에 직접 매핑된다. 어댑터/테스트는 정정 구현·재검증 완료. - -#### Intent -- 가정(A-1, orbit `post_ledger_trade` 동일 계약)으로 작성됐던 ledger write 계약을 라이브 portfolio-hub 실측(openapi `LedgerTradeRequest`)에 맞춰 정정하고, 정정 구현의 재검증 evidence(신규 contract 테스트 + 실 e2e 201)를 연결한다. - -#### Acceptance Criteria Delta -- Added: none -- Updated: - - AC-2: portfolio-hub 어댑터 `record_trade(...)` 는 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 를 호출하고, 요청 body 는 `side`/`quantity`/`price`/`fee_amount`/`tax_amount`/`executed_at`/`execution_id`/`order_id` 키를 사용한다(`execution_id` 포함, `execution_id` 멱등). 성공 응답은 201. -- Removed: none - -#### Impacted Area Delta -- Docs: - - 본 REQ Requirement/AC-2/Notes 정정 - - BAT-006 design.md/verification.md 계약 정정 반영(같은 변경 묶음) -- Modules: - - trex_core/adapters/ledger/portfolio_hub.py (경로 + body 키 정정) -- Tests: - - tests/adapters/ledger/test_portfolio_hub_ledger.py (실 계약으로 갱신: path /v1/portfolios, body 키) - -#### Delivery Trace Delta -- Batches: - - BAT-006 (release-candidate) — ledger write 계약 정정 반영, verification 재확인 대상 -- Releases: - - REL-001 (confirmed) — BAT-006 포함. ledger write 경로가 live portfolio-hub 대상으로 동작하지 않던 release blocker 를 해소. release 문서는 직접 덮어쓰지 않고 본 항목으로 영향·후속 action 기록(rollout/verification 시 ledger POST 201 확인 항목 유효). - -#### Revalidation Impact -- Reverification Needed: yes — 완료. 정정 어댑터에 대해 신규 contract 테스트 통과 + 실 portfolio-hub e2e(매수 체결 → `POST /v1/portfolios/{portfolio_id}/ledger/trades` 201 Created, outbox COMPLETED) 확인. 직전 404 로 쌓였던 outbox 항목이 정정 후 재기동 시 무유실 드레인되어 durability(REQ-019) 도 함께 실증. -- Existing Implementation Invalidated: no — 정정 후 현재 코드가 실 계약과 일치. 무효화된 것은 구 가정(A-1)에 기반한 엔드포인트/필드 기술뿐. -- Status Recommendation: Implemented 유지. -- Follow-up Action: 멱등/중복 응답 시그널(현 어댑터는 2xx body `duplicate` 플래그 + 409 처리 유지) 은 실 API 가 중복 execution_id 에 어떤 코드를 반환하는지 확인되면 후속 정합. - -#### Validation Plan -- AC-2: tests/adapters/ledger/test_portfolio_hub_ledger.py::test_post_contract_includes_execution_id_and_path_uses_portfolio_id (path `/v1/portfolios/PF-77/ledger/trades` + body `quantity`/`fee_amount`/`tax_amount` 단언, `qty`/`fee`/`tax` 부재 단언). -- AC-1/AC-3/AC-4: 기존 테스트 회귀(16 passed) 유지. -- 실 e2e: 매수 intent → 체결 → ledger POST 201 + outbox completed. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-015/issue #13) portfolio-hub 어댑터 오류 분류 분리: **404/400/422 → `LedgerPermanentError`**(신규, 재시도로 해소 불가), 409→conflict(불변), 408/429/5xx/network→retryable(불변). LedgerPort 에 `LedgerPermanentError` 타입 추가 + 어댑터 계약 헤더 갱신. BAT-019 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 미등록 포트폴리오 404 가 retryable 로 오분류되어 영구 재시도되던 근본 원인 제거 — 영속(클라이언트) 오류를 명시 타입으로 분리해 outbox 가 터미널 처리하게 한다. - -#### Acceptance Criteria Delta - -- Added: - - 어댑터가 404/400/422 를 `LedgerPermanentError` 로 매핑. LedgerPort 에 permanent 오류 타입. - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_port.py`(LedgerPermanentError), `trex_core/adapters/ledger/portfolio_hub.py`(분류), `trex_core/adapters/ledger/in_memory.py`(permanent_executions 주입) -- Tests: tests/adapters/ledger/test_portfolio_hub_ledger.py(404/400/422 permanent, 429 retryable) - -#### Delivery Trace Delta - -- Batches: BAT-019 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-019 에서 완료) -- Existing Implementation Invalidated: yes (404 등을 retryable 로 분류하던 경로) -- Status Recommendation: Implemented (change-req → BAT-019 재검증) -- Follow-up Action: confirm-req-implemented(BAT-019). - -#### Validation Plan - -- BAT-019: 404/400/422→LedgerPermanentError, 429/5xx→retryable, 409→conflict 단위. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-018) `record_trade` 반환을 `LedgerWriteResult` → **`LedgerWriteOutcome{result, cash_balance_after?, asset_quantity_after?}`** 로 확장. portfolio-hub 응답 root flat `cash_balance_after`/`asset_quantity_after`(option D, portfolio-hub#8)를 `new`(201)·`duplicate`(200)에서 파싱(누락 시 None). 409/422 는 기존대로 예외. BAT-023 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- ledger write 응답의 권위 cash/quantity 를 운반해 SnapshotStore write-back(REQ-019/030)을 가능하게 한다(매수/매도 직후 현금 stale 제거). - -#### Acceptance Criteria Delta - -- Added: - - `record_trade` → `LedgerWriteOutcome` 반환(result + `*_after`). 어댑터가 응답 root 의 `cash_balance_after`/`asset_quantity_after` 파싱(new+duplicate, 누락 시 None). - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_port.py`(LedgerWriteOutcome), `trex_core/adapters/ledger/portfolio_hub.py`(파싱), `trex_core/adapters/ledger/in_memory.py`(outcome 반환 + cash/qty 주입) -- Tests: tests/adapters/ledger/test_portfolio_hub_ledger.py(*_after new/duplicate/누락) - -#### Delivery Trace Delta - -- Batches: BAT-023 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-023 에서 완료) -- Existing Implementation Invalidated: yes (record_trade 반환 타입) -- Status Recommendation: Implemented (change-req → BAT-023 재검증) -- Follow-up Action: confirm-req-implemented(BAT-023). - -#### Validation Plan - -- BAT-023: 응답 *_after 파싱(new/duplicate/누락) 단위. diff --git a/docs/srs/Integration/req-018_fill-to-ledger-recording.md b/docs/srs/Integration/req-018_fill-to-ledger-recording.md deleted file mode 100644 index 53affe1..0000000 --- a/docs/srs/Integration/req-018_fill-to-ledger-recording.md +++ /dev/null @@ -1,372 +0,0 @@ -# req-018: 체결→원장 기록 (집행 루프 fill hook) - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 코어 집행 루프가 체결(매수/매도) 시 해당 거래를 portfolio-hub 원장에 기록하도록, fill 전이 경로에 ledger 기록 hook 을 더하고 체결 데이터(execution_id/fee)를 브로커 계약으로 운반한다. - -## Requirement -- 집행 루프가 매수 체결(`pending_buy→holding`)·매도 체결(`pending_sell→closed`) 시 LedgerPort 로 거래를 기록한다. ledger path 의 `asset_id` 는 슬롯 `portfolio_id`(= asset_id)를 직접 사용하고 side/qty/price/execution_id/fee/tax/executed_at 을 함께 보낸다. -- `FillStatus`(broker_port) 가 `execution_id`, `fee`, `tax` 를 운반하도록 계약을 확장한다. `execution_id` 는 결정적으로 합성한다(KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`, paper: `paper:{broker_order_id}:{filled_qty}`). `fee` 는 `calculate_fee(side, amount)`(매수·매도 공통 수수료율), `tax` 는 `calculate_tax(side, amount)`(매도 정책 증권거래세율, 매수 0)로 계산한다. 수수료율·세율은 단일 정책 지점에서 env(`TREX_KIS_FEE_BPS`=1.5, `TREX_KRX_TAX_SELL_BPS`=20.0)로 관리하고 paper·KIS 어댑터가 동일 정책을 사용한다(`tax=0.0` 하드코딩 금지). KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고 없을 때만 정책 폴백한다. (dcy-026) -- 원장 기록 주체는 코어 집행 경로 단일로 한정한다(전략/도메인은 원장에 접근하지 않는다). - -## Acceptance Criteria -1. Given idle 슬롯에 매수 intent → 체결, When 집행 루프 fill 전이, Then 슬롯이 holding 으로 전이되고 원장에 buy 거래 1건이 기록된다(asset_id = 슬롯 portfolio_id). -2. Given holding 슬롯에 매도 intent → 체결, When 집행 루프 fill 전이, Then 슬롯이 closed 로 전이되고 원장에 sell 거래 1건이 기록된다. -3. Given 체결된 주문, When fill 을 읽음, Then `FillStatus` 가 결정적 `execution_id`, 계산된 `fee`, 계산된 `tax`(매도 정책 세율, 매수 0)를 운반한다. -4. Given 슬롯에 `portfolio_id` 가 없는 체결, When fill 전이, Then 원장 기록을 건너뛰거나(또는 정의된 정책으로 처리) 슬롯 전이 자체는 정상 수행된다. -5. Given 전략/도메인 코드, When 점검, Then 원장 기록은 코어 집행 경로에서만 발생한다(단일 기록 주체). (NFR-3) -6. Given 매도 체결(amount=price×qty), When fill 을 읽음, Then `tax` = `amount × TREX_KRX_TAX_SELL_BPS / 10_000`(기본 0.2%)로 계산되고, 매수 체결의 `tax` 는 0 이다. -7. Given 수수료/세율 정책, When paper·KIS 어댑터로 동일 `(side, amount)` 입력, Then 동일 정책 결과를 산출한다(단일 정책 지점 `calculate_fee`/`calculate_tax`, env 설정). `tax=0.0` 하드코딩이 코드에 남지 않는다. -8. Given KIS 체결 응답에 수수료/세금 필드 존재, When fill 파싱, Then 그 값을 정본으로 사용하고, 필드 부재 시에만 정책 추정값으로 폴백한다. -9. Given issue #24 4건 체결(BUY 291,500 / SELL 301,000 / BUY 295,000 / SELL 298,500, qty=1), When fee/tax 계산, Then `fee+tax` 합계가 1,376.9원(fee 177.9 + tax 1,199)에 수렴한다. - -## Impacted Area -- Docs: docs/runtime-flows.md(체결→원장 기록 + fee/tax 산출 흐름), docs/interface-contract.md(FillStatus 확장) — REQ-020 에서 정합 -- Modules: `trex_core/application/execution.py`(fill 전이에 ledger 기록 단계), `trex_core/application/broker_port.py`(FillStatus.execution_id/fee/tax + `calculate_fee`/`calculate_tax` 단일 정책 지점 + env 세율 상수), `trex_core/adapters/broker/{paper,kis}.py`(execution_id 합성 + fee/tax 계산, KIS 응답 정본 우선) -- Tests: stub 원장으로 매수/매도 체결 시 buy/sell 1건 + 슬롯 전이, FillStatus execution_id/fee/tax 운반, portfolio_id 부재 처리, 매도 세율 + issue #24 4건 회귀(1,376.9원), paper·KIS 동일 정책 + KIS 응답 우선 - -## Notes -- 출처: dcy-004 FR-2, OQ-1(asset_id == portfolio_id, orbit 근거), OQ-3(execution_id/fee/tax 출처), NFR-3. -- OQ-1: 슬롯은 이미 buy intent 가 `portfolio_id` 를 공급해 캐시하므로 추가 매핑/조회 없이 path param 으로 사용한다. -- `portfolio_id` 부재 시 기록 skip vs 거부 정책은 구현 시 확정(AC-4 는 둘 다 허용하되 슬롯 전이 일관성을 요구). -- 멱등/무유실/재시도(correlation_id, outbox)는 REQ-019 에서 다룬다. 본 REQ 는 기록 호출 경로와 데이터 운반에 한정한다. -- (dcy-026/CHG-20260612-01) 매도 증권거래세 정책 확정: 매도세 0.2%(`TREX_KRX_TAX_SELL_BPS`=20.0), 수수료 0.015%(`TREX_KIS_FEE_BPS`=1.5, 매수·매도 공통). 변수명에 `KIS`/`KRX` 도메인 라벨이 있으나 단일 정책 지점으로 paper·KIS 가 동일 공유. dcy-004 OQ-3 "정밀 세금 모델" 후속 해소. -- 과거 잘못 저장된 ledger 행 소급 보정은 Out of Scope(dcy-026 OQ-3). 배포 후 신규 체결부터 정확값 저장. -- REQ-011(BrokerPort 추상화)는 본 변경 대상 아님: fee/tax 는 REQ-018 의 `FillStatus` 확장 + `calculate_fee`/`calculate_tax` 정책 영역이며, REQ-011 의 포트 계약(submit/poll/cancel async 더블 + 도메인 순수성)은 영향받지 않음. - -## Change Log - -### CHG-20260604-01 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- dcy-004 FR-2 로부터 최초 생성(Proposed). OQ-1/OQ-3 결정 반영, NFR-3(단일 기록 주체) 흡수. - -#### Intent -- 집행 루프 fill 전이에서 체결을 원장에 기록하는 경로와 FillStatus 확장(execution_id/fee)을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 매수/매도 체결 기록+전이, FillStatus execution_id/fee, portfolio_id 부재, 단일 기록 주체 5개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/interface-contract.md -- Modules: - - execution.py(ledger hook), broker_port.py(FillStatus), paper/kis 어댑터(execution_id/fee) -- Tests: - - 체결→기록 통합테스트, FillStatus 운반, portfolio_id 부재 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- stub 원장으로 매수/매도 체결 lifecycle 통합테스트 + FillStatus 확장 테스트로 검증. - -### CHG-20260604-02 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- 집행 루프 fill 전이의 ledger 기록 hook 과 FillStatus 확장을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - 없음 -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260604-03 -- Date: 2026-06-04 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-006 release-candidate verification evidence 기반. - -#### Intent -- 집행 루프 fill→ledger hook + FillStatus.execution_id/fee 확장 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 (변경은 상태 전이만) - -#### Delivery Trace Delta -- Batches: - - BAT-006 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_execution_ledger.py::test_buy_fill_enqueues_one_ledger_item_and_writer_records_it. -- AC-2: tests/application/test_execution_ledger.py::test_sell_fill_enqueues_sell_ledger_item. -- AC-3: 위 buy 테스트가 FillStatus.execution_id/fee/tax 운반 직접 검증. -- AC-4: tests/application/test_execution_ledger.py::test_portfolio_id_absent_skips_ledger_but_transitions_slot. -- AC-5: tests/domain/test_domain_import_sweep.py + grep 결과(trex_core/application/execution.py 단일 enqueue 지점). - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-011/issue #11) `_record_ledger` 의 ledger skip 을 무성에서 **가시화**로 전환: portfolio_id/execution_id 누락 skip 시 WARN 로그 + `ExecutionResult.ledger_skipped` 카운터 노출(슬롯 전이는 그대로). BAT-015 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- portfolio_id 없는 체결의 ledger silent skip(회계 누락)을 운영자가 탐지 가능하게(issue #11 제안 ②). OQ-4 결정으로 outbox 마커 행은 도입하지 않고 로그+메트릭만; 영구 탐지는 REQ-055 점검 쿼리에 위임. - -#### Acceptance Criteria Delta - -- Updated: - - AC-4: skip 시 무성 반환 → WARN 로그 + 카운터 가시화(두 skip 경로 모두). - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_record_ledger` 로그 + `_log` 추가) -- Docs: `docs/runtime-flows.md` -- Tests: tests/application/test_execution_ledger.py::test_ledger_skip_emits_warning - -#### Delivery Trace Delta - -- Batches: BAT-015 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-015 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가, 전이 동작 불변) -- Status Recommendation: Implemented (change-req → BAT-015 재검증) -- Follow-up Action: confirm-req-implemented(BAT-015). - -#### Validation Plan - -- BAT-015: ledger skip WARN 로그 단위(caplog) + 회귀. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-013/issue #9) 부분 체결 후 외부 취소 시 체결분(`ccld_qty`)을 원장에 **1회** 기록한다. `_on_partial_cancel` 이 `_record_ledger`(기존)를 재사용해 partial fill 의 execution_id/fee/qty 를 정확히 운반. BAT-017 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 전량 체결만 다루던 원장 기록을 부분 체결+취소에도 확장해 체결분 누락/이중기록을 방지(정확 회계, OQ-1 A안). - -#### Acceptance Criteria Delta - -- Updated: - - AC-1/2 의 fill→원장 기록을 부분 체결 후 취소(체결분 1회)에도 적용. - -#### Impacted Area Delta - -- Modules: `trex_core/application/execution.py`(`_on_partial_cancel` → `_record_ledger` 재사용) -- Tests: tests/application/test_execution_cancel.py::test_partial_fill_then_cancel_records_and_keeps_remainder - -#### Delivery Trace Delta - -- Batches: BAT-017 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-017 에서 완료) -- Existing Implementation Invalidated: no (기록 경로 재사용, 호출 지점만 확장) -- Status Recommendation: Implemented (change-req → BAT-017 재검증) -- Follow-up Action: confirm-req-implemented(BAT-017). - -#### Validation Plan - -- BAT-017: 부분 취소 시 sell 원장 1건(qty=체결분, price=체결가) 단위. - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-015/issue #13) ledger 터미널 실패 가시화 확장: `delivery_failed`(permanent/dead-letter)를 `WriterStats.delivery_failed` 카운터 + WARN 로그 + 구분 status 로 노출(dcy-011 `ledger_skipped` 가시화 패턴 정합). dead-letter 는 운영자 재처리 가능 상태이며 `find_ledger_gap_slots`(REQ-055)·skip 가시화와 연계. BAT-019 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- ledger 가 끝내 기록되지 않는 실패(404 영속·dead-letter)가 무성 pending 으로 남지 않고 운영자에게 노출되도록(NFR-2 가시성). - -#### Acceptance Criteria Delta - -- Updated: - - 원장 미기록(터미널 delivery_failed) 시 WARN 로그 + 카운터 + 구분 status 가시화. - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_outbox.py`(WARN + WriterStats.delivery_failed) -- Tests: tests/application/test_ledger_outbox_backoff.py::test_permanent_failure_emits_warning - -#### Delivery Trace Delta - -- Batches: BAT-019 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-019 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가) -- Status Recommendation: Implemented (change-req → BAT-019 재검증) -- Follow-up Action: confirm-req-implemented(BAT-019). - -#### Validation Plan - -- BAT-019: delivery_failed WARN 로그(caplog) + 카운터 단위. - -### CHG-20260612-01 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-026/issue #24) 매도 증권거래세 누락 정정. `FillStatus.tax` 가 매도 체결에 대해 정책 세율(증권거래세)로 계산되도록 계약을 정정한다. 기존 `tax 는 기본 0 으로 한다` 규정과 `tax=0.0` 하드코딩(paper/kis 어댑터)을 제거하고, 수수료율·세율을 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 env 설정으로 관리한다. KIS 어댑터는 브로커 응답 fee/tax 가 있으면 정본 우선, 없으면 정책 폴백. 기존 `tax=0` 전제 구현/검증 evidence 무효화 → **Implemented → Approved 되돌림**. - -#### Intent - -- 한국 주식 매도 증권거래세(약 0.2%)가 전 체결에서 `tax=0.0` 으로 저장되어 실현 손익·일일 수익률이 과대 계산되던 운영 버그(issue #24, 2026-06-11 4건 체결 증거)를 닫는다. `broker_port.py` 가 dcy-004 OQ-3 로 미뤄둔 "정밀 세금 모델"을 정식 정책으로 확정한다. - -#### Acceptance Criteria Delta - -- Updated: - - AC-3: `FillStatus` 가 결정적 `execution_id` 와 계산된 `fee`(tax=0) 운반 → `fee` 와 **매도 정책 세율로 계산된 `tax`**(매수 tax=0) 운반. -- Added: - - AC-6: 매도 체결의 `tax` 는 `amount × TREX_KRX_TAX_SELL_BPS / 10_000`(기본 20.0bps = 0.2%)로 계산되고, 매수 체결의 `tax` 는 0 이다. - - AC-7: 수수료율·세율은 단일 정책 지점(`calculate_fee`/`calculate_tax`)에서 env(`TREX_KIS_FEE_BPS`=1.5, `TREX_KRX_TAX_SELL_BPS`=20.0)로 관리되고 paper·KIS 어댑터가 동일 정책을 사용한다. `tax=0.0` 하드코딩은 제거된다. - - AC-8: KIS 어댑터는 브로커 체결 응답에 수수료/세금 필드가 있으면 그 값을 정본으로 사용하고, 없을 때만 정책 추정값으로 폴백한다. - - AC-9: issue #24 4건 체결(BUY 291,500 / SELL 301,000 / BUY 295,000 / SELL 298,500, qty=1) 재현 시 `fee+tax` 합계가 §4 공식 기대값 **1,376.9원**(fee 177.9 + tax 1,199)에 수렴한다(회귀 테스트). - -#### Impacted Area Delta - -- Docs: - - docs/runtime-flows.md(체결→ledger fee/tax 산출·저장 흐름 갱신) -- Modules: - - `trex_core/application/broker_port.py`(`calculate_tax` 신규 + 세율 상수, env 로딩) - - `trex_core/adapters/broker/paper.py`(`tax=0.0` → `calculate_tax(...)`) - - `trex_core/adapters/broker/kis.py`(`tax=0.0` → 브로커 응답 파싱 + 정책 폴백) -- Tests: - - 매수/매도 fee·tax 단위 테스트 + issue #24 4건 회귀(1,376.9원) + KIS 응답 우선 경로 + paper·KIS 동일 정책 일치 - -#### Delivery Trace Delta - -- Batches: - - BAT-038 (예정, dcy-026 fee/tax) -- Releases: - - none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-038 에서 매도 tax 계산·회귀 재검증) -- Existing Implementation Invalidated: yes (`tax=0` 전제 — AC-3 의 tax=0 evidence 및 paper/kis `tax=0.0` 하드코딩 무효) -- Status Recommendation: Implemented → **Approved** (구현/검증 후 confirm-req-implemented 로 복귀) -- Follow-up Action: confirm-req → draft-batch(BAT-038) → 구현·검증 → confirm-req-implemented - -#### Validation Plan - -- BAT-038: 매수/매도 fee·tax 단위 테스트(매도 0.2% 세율, 매수 tax=0), issue #24 4건 회귀(`fee+tax`=1,376.9원), KIS 응답 fee/tax 우선·폴백 경로, paper·KIS 동일 입력 동일 출력. `pytest` 전체 회귀 + `ruff check` clean. - -### CHG-20260612-02 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- Status 변경: Approved → **Implemented** (confirm-req-implemented, 근거 batch BAT-038 release-candidate). - -#### Intent - -- BAT-038 verification.md 의 acceptance mapping + evidence(429 passed / fee·tax 정책 단위 / issue #24 1,376.9원 회귀 / `tax=0.0` 하드코딩 제거 grep / runtime-flows 동기화)로 AC-1~9 충족 → 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 (상태 전이만) - -#### Impacted Area Delta - -- Docs/Modules/Tests: 없음 (상태 전이만) - -#### Delivery Trace Delta - -- Batches: - - BAT-038 (release-candidate) -- Releases: - - none (release 편성은 별도 진행) - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: draft-release 로 release 편성(BAT-038 포함) - -#### Validation Plan - -- BAT-038 verification evidence 재참조로 충분. - - AC-3/6: `test_paper_sell_fill_carries_policy_tax`, `test_calculate_tax_sell_only`, `test_paper_buy_fill_has_zero_tax`. - - AC-7: `test_calculate_fee_same_rate_buy_and_sell`, `test_fee_and_tax_zero_for_nonpositive_amount`, `tax=0.0` grep 0건. - - AC-8: design Key Decision 3(KIS daily-ccld fee/tax 필드 부재 → 정책 폴백) + 코드 리뷰. - - AC-9: `test_issue_24_four_fill_regression` (1,376.9원). - - AC-1/2/4/5: BAT-006 evidence 유효 + 전체 회귀 429 passed. diff --git a/docs/srs/Integration/req-021_messaging-topology-and-contract-mapping.md b/docs/srs/Integration/req-021_messaging-topology-and-contract-mapping.md deleted file mode 100644 index 2d7f4dd..0000000 --- a/docs/srs/Integration/req-021_messaging-topology-and-contract-mapping.md +++ /dev/null @@ -1,307 +0,0 @@ -# req-021: messaging 토폴로지 + 메시지 계약 매핑 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- trex-core ↔ 전략 모듈 통신을 RabbitMQ(AMQP) 메시지 기반으로 정의해, 스냅샷·intent admission 은 request-reply 로, 체결/슬롯 상태는 이벤트(pub/sub)로 전달한다. 기존 logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id 필터, intent 스키마)은 transport-무관하게 보존하고 메시지 payload/속성/routing key 로 의미 손실 없이 매핑한다. - -## Requirement -- 통신 토폴로지를 아래 3 패턴으로 확정한다(dcy-006 OQ-2). - 1. 스냅샷 RPC(request-reply): 요청 queue `trex.snapshot.req`(direct/default exchange), 응답은 RabbitMQ direct reply-to(`amq.rabbitmq.reply-to`) + `correlation_id`. 요청 payload 에 `strategy_id` 와 `view` 모드(dcy-005 OQ-11/FR-8). - 2. intent admission RPC(request-reply): 요청 queue `trex.intent.req`, 응답 = admission 결과(`status`, `slot_id`, `reason`). `correlation_id` = intent 멱등키(dcy-003 패턴) 로 RPC 상관과 dedup 일원화. 체결/종료(fill/close) 결과는 응답이 아니라 이벤트(③)로 전달해 admission 동기 + fill 비동기 2단계 시맨틱을 보존한다. - 3. 이벤트 pub/sub: trex-core 가 체결·슬롯 상태 변화를 topic exchange `trex.events` 에 발행한다. routing key 는 `{event_type}.{strategy_id}.{symbol}` 형식이며, 전략 queue 는 `*.{strategy_id}.#` 패턴으로 바인딩해 자기 자산 이벤트만 수신한다(broker 단 필터). -- 매핑 원칙을 다음과 같이 고정한다: 요청 메시지(①·②) 의 `view` 모드와 `strategy_id` 는 message **payload** 에, 이벤트(③) 의 `strategy_id`·`symbol` 은 **routing key** 에 싣는다. -- 위 토폴로지/매핑은 transport-무관 logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id, intent 스키마)을 변경하지 않는다. - -## Acceptance Criteria -1. Given trex-core daemon, When 기동, Then 스냅샷 RPC 요청 큐(`trex.snapshot.req`), intent RPC 요청 큐(`trex.intent.req`), 이벤트 토픽 익스체인지(`trex.events`)가 선언되어 있다. -2. Given stub/실 어댑터로 발행한 스냅샷 RPC 요청, When trex-core 가 응답, Then 응답 메시지가 HTTP 응답과 동일한 logical 필드(필드/마스킹/view 모드 적용)를 운반하고 `correlation_id` 가 요청과 일치한다. -3. Given intent admission RPC 요청 메시지, When trex-core 가 처리, Then 슬롯 전이 후 admission 응답(`status, slot_id?, reason?`)이 reply-to 큐로 반환되고 체결/종료는 응답이 아니라 후속 이벤트로 발행된다. -4. Given 동일 종목·이벤트가 전략 A 와 전략 B 의 queue 에 각각 `*.A.#`/`*.B.#` 패턴으로 바인딩된 상태, When trex-core 가 strategy_id=A 의 이벤트를 발행, Then A 의 queue 만 수신하고 B 의 queue 는 수신하지 않는다. -5. Given 요청 메시지, When 페이로드를 검사, Then `view`/`strategy_id` 가 payload 에 있고 routing key 에 들어가 있지 않다(요청 매핑 원칙). - -## Impacted Area -- Docs: docs/interface-contract.md(메시지 계약), docs/runtime-flows.md(RPC/이벤트 흐름) — REQ-026 에서 정합. -- Modules: 신규 `trex_core/adapters/messaging/` 토폴로지 선언(exchange/queue/binding) 및 routing key 생성 로직. -- Tests: stub 어댑터로 RPC 요청-응답 매핑, 이벤트 routing key 바인딩 필터링, 요청/이벤트 매핑 원칙 검사. - -## Notes -- 출처: dcy-006 FR-1, FR-2, OQ-2. -- 토폴로지 선언과 어댑터 구현 분리: 본 REQ 는 "무엇을/어떻게 매핑하나" 계약을 다루고, RabbitMQ 어댑터 + 런타임 통합 자체는 REQ-022 에서 다룬다. -- logical 계약(스냅샷 필드/마스킹/view 모드/strategy_id, intent 스키마)은 dcy-005/002/003 소관이며 본 REQ 가 변경하지 않는다. -- dcy-005 의 스냅샷 read 계약이 아직 REQ 로 정규화되지 않았으면 본 REQ 의 AC-2 검증은 stub 응답 셰이프 수준에서 시작하고, 정식 계약 확정 후 evidence 를 보강한다. - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 FR-1/FR-2 로부터 최초 생성(Proposed). 통신 토폴로지(스냅샷 RPC + intent RPC + 이벤트 topic) 와 요청/이벤트 매핑 원칙(view·strategy_id payload, event routing key)을 backlog 로 정규화. - -#### Intent -- transport 추상화의 메시지 계약과 토폴로지 결정을 별도 REQ 로 분리해 어댑터 구현(REQ-022) 및 신뢰성(REQ-023)과 독립 진척. - -#### Acceptance Criteria Delta -- Added: - - 토폴로지 선언 4종, RPC 응답 매핑 1종, 이벤트 routing key 필터링 1종, 매핑 원칙 검사 1종 (총 5개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md -- Modules: - - adapters/messaging 토폴로지 선언 -- Tests: - - RPC 요청-응답, 이벤트 routing key 필터링, 매핑 원칙 검사 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- stub 어댑터 통합테스트로 RPC 매핑·이벤트 routing key 필터링·매핑 원칙을 검증. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- messaging 토폴로지 + 메시지 계약 매핑을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: REQ-022/023/025 와 함께 batch 편성(통신 단절 R-5 회피) - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- messaging 토폴로지 + 메시지 계약 매핑 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/adapters/messaging/test_rabbitmq_transport.py::test_declare_topology_creates_core_entities. -- AC-2: tests/application/test_messaging_snapshot_consumer.py + tests/adapters/messaging/test_rabbitmq_transport.py::test_rpc_roundtrip. -- AC-3: tests/application/test_messaging_intent_consumer.py + test_messaging_event_publisher.py 조합. -- AC-4: tests/application/test_messaging_event_publisher.py::test_event_for_strategy_a_does_not_reach_strategy_b_queue + tests/adapters/messaging/test_rabbitmq_transport.py::test_event_publish_and_per_strategy_consume. -- AC-5: tests/adapters/messaging/test_topology.py. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-011) buy intent **메시지 계약**에서 `symbol` 필드 제거, `portfolio_id` 필수화. buy 핸들러는 payload 에서 symbol 을 읽지 않고 portfolio_id 만 매핑(서버 resolve 는 REQ-007). BAT-015 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 요청 매핑 원칙(payload 식별자)을 정식 식별자(portfolio_id) 기준으로 정정(issue #6). buy 핸들러 payload 파싱 변경. - -#### Acceptance Criteria Delta - -- Updated: - - buy payload 계약: `symbol` 제거, `portfolio_id` 필수. - -#### Impacted Area Delta - -- Modules: `trex_core/application/messaging_handlers.py`(buy 핸들러) -- Docs: `docs/interface-contract.md` -- Tests: tests/application/test_messaging_intent_consumer.py + tests/adapters/messaging/test_rabbitmq_intent_e2e.py - -#### Delivery Trace Delta - -- Batches: BAT-015 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-015 에서 완료) -- Existing Implementation Invalidated: yes (symbol 기반 buy payload) -- Status Recommendation: Implemented (change-req → BAT-015 재검증) -- Follow-up Action: confirm-req-implemented(BAT-015). - -#### Validation Plan - -- BAT-015: buy payload portfolio_id 기반 핸들러 단위 + RabbitMQ e2e. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-016/REQ-034 연계) snapshot 요청 payload 에 워치독 억제 플래그 추가: `touch_watchdog`(기본 true) / `actor="operator"`. `make_snapshot_rpc_handler` 가 플래그 시 holding 슬롯 `last_seen_at` 갱신을 건너뛴다. 기본값=현행(전략 폴링=워치독 갱신) 유지(하위호환). BAT-020 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 운영자 CLI 조회가 전략 생존 신호로 오인되어 safety-sell 을 무력화하는 것 방지(R-4). 메시지 계약에 억제 플래그를 정식화. - -#### Acceptance Criteria Delta - -- Added: - - snapshot 요청 payload `touch_watchdog`/`actor` 플래그(기본 미지정=touch). 운영자 조회는 억제. - -#### Impacted Area Delta - -- Modules: `trex_core/application/messaging_handlers.py`(make_snapshot_rpc_handler 억제 분기) -- Docs: `docs/interface-contract.md` -- Tests: tests/application/test_messaging_snapshot_consumer.py(touch vs suppress) - -#### Delivery Trace Delta - -- Batches: BAT-020 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-020 에서 완료) -- Existing Implementation Invalidated: no (기본값 현행 유지, 플래그 추가) -- Status Recommendation: Implemented (change-req → BAT-020 재검증) -- Follow-up Action: confirm-req-implemented(BAT-020). - -#### Validation Plan - -- BAT-020: 기본 요청 touch / operator 요청 suppress 단위(caplog/recorder). - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017/REQ-034 연계) snapshot RPC 응답에 `watchdog` 필드(holding 슬롯 `{slot_id, portfolio_id, symbol, expired, last_seen}` 배열) 추가(best-effort, 실패 시 생략). 메시지 계약 확장. BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 워치독 소유 상태를 운영자 CLI 가 관측할 수 있도록 스냅샷 응답 계약에 read-only 필드 정식화. - -#### Acceptance Criteria Delta - -- Added: - - snapshot 응답 optional `watchdog[]` 필드(read-only). - -#### Impacted Area Delta - -- Modules: `trex_core/application/messaging_handlers.py`(_attach_watchdog) -- Docs: `docs/interface-contract.md` -- Tests: tests/application/test_messaging_snapshot_consumer.py - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (additive 필드) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: snapshot 응답 watchdog 필드 shape 단위. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-025) 토폴로지의 스냅샷 RPC 큐를 **`trex.snapshot.req` → `trex.slots.req`** 로 교체한다(즉시 교체, 과도기 없음 — dcy-025 OQ-1). 요청 payload 매핑 원칙도 갱신: `view`/`strategy_id` 대신 **`filter`/`spy`**(REQ-073). 응답은 slots[]+health 만(REQ-073/074). intent RPC·이벤트 pub/sub 토폴로지는 불변. 전략 모듈(trex-orb-v1) 소비 측 lockstep cutover 필요. - -#### Intent - -- 자산 중심 모델의 read 표면 재설계(dcy-025)에 따른 토폴로지·매핑 계약 정합. - -#### Acceptance Criteria Delta - -- Updated: - - AC-1: 기동 시 선언되는 read 큐가 `trex.snapshot.req` 가 아니라 `trex.slots.req`(per-instance)다. - - AC-2: read RPC 응답의 logical 필드 기준이 snapshot envelope(assets/view) 가 아니라 REQ-073/074 의 slots 계약이다. - - AC-5: 요청 payload 매핑 원칙의 필드가 `view`/`strategy_id` → `filter`/`spy` 로 바뀐다(payload 에 싣는 원칙은 유지). -- Added/Removed: none - -#### Impacted Area Delta - -- Modules: trex_core/adapters/messaging/topology.py(SNAPSHOT_REQ_QUEUE→SLOTS_REQ_QUEUE), messaging_handlers.py -- Docs: docs/interface-contract.md(REQ-075) -- Tests: 토폴로지 선언/큐명 단위 - -#### Delivery Trace Delta - -- Batches: 신규 batch(REQ-073/074/075 와 동일 배치, dcy-025) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 갱신 AC 검증) -- Existing Implementation Invalidated: yes (snapshot 큐·view 매핑의 기존 evidence 가 표면 교체로 무효화) -- Status Recommendation: Implemented → Approved (batch 재구현·재검증 후 복귀) -- Follow-up Action: draft-batch → 구현/검증 → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: 토폴로지 선언(slots.req 존재·snapshot.req 부재) 단위 + filter/spy payload 매핑 단위. diff --git a/docs/srs/Integration/req-022_messaging-transport-port-and-rabbitmq-adapter.md b/docs/srs/Integration/req-022_messaging-transport-port-and-rabbitmq-adapter.md deleted file mode 100644 index 6de0ea4..0000000 --- a/docs/srs/Integration/req-022_messaging-transport-port-and-rabbitmq-adapter.md +++ /dev/null @@ -1,193 +0,0 @@ -# req-022: messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 메시징 접근을 application 경계의 transport 포트로 추상화하고, `pika`(동기) RabbitMQ 어댑터와 결정론 stub 을 adapters 레이어에 격리해 도메인 순수성을 유지하면서 메시지 기반 통신을 가능하게 한다. uvicorn 제거 후 데몬을 sync+thread 런타임으로 재구성한다. - -## Requirement -- application 레이어에 `MessagingTransport` (또는 동등한) Protocol 을 정의해 publish/subscribe/RPC consume 진입점을 추상화한다. -- 신규 `trex_core/adapters/messaging/` 패키지에 두 구현체를 둔다. - - `RabbitMQTransport`: `pika`(동기) BlockingConnection 기반. consumer/publisher 스레드별 connection/channel 또는 `add_callback_threadsafe` 규약을 따른다(pika 는 thread-safe 가 아님). - - `InMemoryTransport` (stub): 결정론 테스트용 in-process publish/subscribe/RPC. -- 데몬을 sync+thread 런타임으로 재구성한다(dcy-006 OQ-5): consumer 스레드(intent RPC, prefetch=1 단일 FIFO; snapshot RPC) + 기존 execution/ledger 루프 스레드 + 이벤트 publisher(execution 루프가 자기 channel 로 발행). graceful shutdown 은 기존 `stop` event 패턴을 확장해 connection close 까지 포함한다. -- pika 자동 재연결이 없는 특성을 보정하기 위해 connection drop 시 백오프 기반 재연결 루프를 직접 구현하고 REQ-024 degrade 정책과 연동한다. -- `trex_core/domain` 은 transport/AMQP 의존 0 을 유지한다(NFR-1, import 스윕). - -## Acceptance Criteria -1. Given in-process `InMemoryTransport` stub, When 테스트가 publish/subscribe/RPC 진입점을 호출, Then 외부 broker 없이 메시지 송수신이 동작한다. -2. Given `RabbitMQTransport`, When `TREX_RABBITMQ_URL` 로 broker 에 접속, Then connection/channel 이 열리고 declared 큐/익스체인지로 메시지 송수신이 가능하다(integration 수준, 인프라 미가용 시 skip 게이트 허용). -3. Given 데몬 기동, When lifecycle 을 시작, Then intent RPC consumer 스레드, snapshot RPC consumer 스레드, execution/ledger 스레드가 동시에 동작하고, shutdown 시 `stop` event 후 모든 스레드와 connection 이 정상 종료된다(`uvicorn.run` 의존 0). -4. Given broker connection 이 끊긴 상태, When 어댑터가 감지, Then 백오프 재연결을 직접 수행하고, REQ-024 degrade 정책(버퍼링 금지·보수적 중단)에 따라 신규 처리를 중단한다. -5. Given 정적 import 스윕, When `trex_core/domain` 을 점검, Then `pika`/AMQP/`trex_core.adapters.messaging` 의존이 0 이다(NFR-1). - -## Impacted Area -- Docs: docs/project-structure.md(adapters/messaging 위치 + 런타임 스레드 구성), docs/runtime-flows.md(consumer 스레드/이벤트 publisher 흐름) — REQ-026 에서 정합. -- Modules: - - 신규 `trex_core/application/messaging.py`(`MessagingTransport` Protocol) - - 신규 `trex_core/adapters/messaging/` (`rabbitmq.py`, `in_memory.py`) - - 변경 `trex_core/runtime/daemon.py`(uvicorn 제거, consumer 스레드 추가, graceful shutdown 확장) - - 변경 `trex_core/runtime/config.py` (`TREX_RABBITMQ_URL` 로딩; REQ-025 와 함께 `TREX_API_HOST`/`TREX_API_PORT` 폐기) - - 영향 `trex_core/application/intents.py`, dcy-005 스냅샷 application 서비스 — transport-무관 포트로 호출되도록 재배선 -- Tests: - - in-process stub 통합테스트(publish/subscribe/RPC) - - RabbitMQ integration 테스트(`TREX_RABBITMQ_URL` 게이트, 미가용 시 skip) - - 데몬 lifecycle 테스트(스레드 기동/종료) - - `tests/domain/test_domain_import_sweep.py` 가 `pika`·`adapters.messaging` 추가 금지 prefix 로 가드 - -## Notes -- 출처: dcy-006 FR-3, NFR-1, OQ-5, OQ-6. -- NFR-1(도메인 순수성) 은 REQ-017 AC-4 와 동일한 패턴으로 본 REQ AC-5 에 흡수한다(별도 REQ 미승격). -- HTTP 어댑터 제거와 `TREX_API_HOST/PORT` 폐기는 REQ-025 본문에서 책임진다(본 REQ 는 신규 transport 도입 범위). -- 실 RabbitMQ broker 인스턴스/자격증명 provision 은 delivery/운영 작업으로 위임됨(DATA, 비차단). - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 FR-3 로부터 최초 생성(Proposed). NFR-1(도메인 순수성) 흡수. - -#### Intent -- 메시징 transport 포트 + RabbitMQ 어댑터 + stub + sync+thread 데몬 재구성 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: - - stub publish/sub/RPC, RabbitMQ integration, 데몬 lifecycle, 재연결+degrade 연동, 도메인 import 스윕 (총 5개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/project-structure.md, docs/runtime-flows.md -- Modules: - - application/messaging.py, adapters/messaging/{rabbitmq,in_memory}, runtime/daemon.py, runtime/config.py -- Tests: - - in-process stub, RabbitMQ integration(게이트), 데몬 lifecycle, import 스윕 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- stub 기반 통합테스트 + RabbitMQ integration 테스트(미가용 시 skip) + import 스윕으로 검증. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon 재구성을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: REQ-021/023/025 와 함께 batch 편성 - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- MessagingTransport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon 재구성을 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/adapters/messaging/test_in_memory_transport.py 5종. -- AC-2: tests/adapters/messaging/test_rabbitmq_transport.py 5종 (실 RabbitMQ 통합). -- AC-3: tests/runtime/test_daemon_no_http_surface.py::test_daemon_module_has_no_fastapi_or_uvicorn_import + Daemon 4 스레드 코드. -- AC-4: RabbitMQTransport 재연결 백오프 코드 + tests/adapters/messaging/test_in_memory_transport.py::test_unavailable_transport_raises_on_publish_and_rpc. -- AC-5: tests/domain/test_domain_import_sweep.py. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-016) 운영자 CLI 가 transport 의 request-reply RPC 클라이언트(`call_rpc`)를 재사용해 snapshot/intent 큐로 요청한다(전략 모듈과 동일 클라이언트, NFR-3). CLI 는 config 기반으로 transport 를 빌드하고 `call_rpc` 후 close 한다. BAT-020 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- CLI 와 전략 모듈이 동일 transport RPC 계약을 공유해 표면 분기 없는 일관 동작. - -#### Acceptance Criteria Delta - -- Added: - - CLI 가 `MessagingTransport.call_rpc(queue, payload, correlation_id, timeout)` 로 RPC 수행(주입 가능, 테스트는 fake rpc). - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(transport 빌드 + call_rpc), 기존 `RabbitMQTransport.call_rpc` 재사용 -- Tests: tests/runtime/test_cli_operator.py(주입 rpc) - -#### Delivery Trace Delta - -- Batches: BAT-020 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-020 에서 완료) -- Existing Implementation Invalidated: no (클라이언트 재사용) -- Status Recommendation: Implemented (change-req → BAT-020 재검증) -- Follow-up Action: confirm-req-implemented(BAT-020). - -#### Validation Plan - -- BAT-020: CLI RPC 호출 큐/payload/타임아웃 + 실패 표시 단위. diff --git a/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md b/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md deleted file mode 100644 index 1149ef3..0000000 --- a/docs/srs/Integration/req-027_portfolio-hub-read-port-and-adapter.md +++ /dev/null @@ -1,179 +0,0 @@ -# req-027: portfolio-hub read 포트 + 어댑터 + stub - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 전략 모듈에 제공할 종목 스냅샷의 원천인 portfolio-hub universe(자신이 관리하는 portfolio 집합)를 trex-core 가 read 할 경로를 만든다. dcy-004 write 어댑터/자격증명을 재사용하고 도메인 순수성을 유지한다. - -## Requirement -- `PortfolioHubReadPort.get_portfolios(daemon_id)` application 포트 + portfolio-hub read 어댑터(portfolio-hub `GET /v1/portfolios`, `list_portfolios_v1`) + 결정론 stub 를 제공한다. `/v1/portfolios` 는 토큰 스코프 조회라 `managed_by_daemon_id` 쿼리 파라미터가 없으므로, 어댑터는 응답 항목의 `managed_by_daemon_id == daemon_id` 로 **클라이언트측 필터**한다. (정정: CHG-04) -- 어댑터는 dcy-004 의 portfolio-hub 자격증명(.env `TREX_PORTFOLIO_HUB_*`)을 재사용한다. base URL 에 스킴이 없으면 `http://` 를 기본 적용한다. -- `daemon_id` 는 portfolio-hub 자격증명/인증 토큰에서 도출한다(단일 진실원). 별도 `TREX_DAEMON_ID` 설정값을 1차 출처로 두지 않으며, `GET /v1/user_info` 응답의 `daemon_id` 로 1회 도출·캐시한다. (정정: CHG-04, `/v1/auth/whoami` 부재) -- 각 universe 항목은 symbol, asset_id(=portfolio_id), cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque JSON)를 담고 가격(price)은 제외한다. 전략파라미터는 trex-core 가 해석하지 않고 통과시킨다. 단, 실 `/v1/portfolios`(`PortfolioItem`)는 `active_strategy_id`/`strategy_params` 를 제공하지 않아 두 필드는 None/`{}` 로 채워진다(실데이터 채움 이월 — CHG-04). - -## Acceptance Criteria -1. Given daemon 토큰, When `get_portfolios` 를 호출, Then 토큰 스코프 응답을 `managed_by_daemon_id == daemon_id` 로 필터해 portfolio 집합을 항목당 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 형태로 반환하고 가격 필드는 없다. (실 list 응답에 전략 필드가 없으면 None/`{}` — CHG-04) -2. Given 결정론 stub 어댑터, When 호출, Then 네트워크 없이 고정된 universe 를 반환한다. -3. Given import 스윕, When `trex_core/domain` 을 검사, Then portfolio-hub/네트워크 의존이 0 이다(NFR-1). -4. Given `TREX_DAEMON_ID` 미설정, When daemon 기동·universe 조회, Then `GET /v1/user_info` 응답에서 도출한 daemon_id 로 동작한다(OQ-7, CHG-04). - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/interface-contract.md -- Modules: trex_core/application (read 포트), trex_core/adapters/portfolio (read 어댑터 + stub), trex_core/runtime/config (daemon_id 토큰 도출) -- Tests: stub read 통합테스트, 도메인 import 스윕, daemon_id 토큰 도출 - -## Notes -- (정정, CHG-04) 라이브 portfolio-hub 실측 계약: `GET /v1/user_info` → `UserInfoResponse{daemon_id,…}`; `GET /v1/portfolios` → `PortfolioListResponse{portfolios:[PortfolioItem], total}` (토큰 스코프, `managed_by_daemon_id` 쿼리 없음 → 클라이언트측 필터). `PortfolioItem` 은 cash_balance/asset_quantity/managed_by_daemon_id 는 포함하나 `active_strategy_id`/`strategy_params` 는 미포함(detail 의 `strategy_set` 에만 존재). 구 가정(`/v1/auth/whoami`, `{items:[…]}`, `managed_by_daemon_id` 쿼리 스코프)은 폐기. -- 전략 필드 부재는 단일 전략(orb-v1, `strategy_key` 미사용) 단계에서 무해(REQ-029 CHG-04). 2번째 전략 도입 시 strategy_set 보강 또는 별도 change-req 로 해소. -- NFR-1(도메인 순수성)을 본 REQ 의 AC-3 로 흡수한다. - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-4 + OQ-7 분해로 최초 생성(Proposed). NFR-1 흡수. - -#### Intent -- portfolio-hub universe read 경로를 포트/어댑터/stub 로 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005, interface-contract 참조 -- Modules: - - application read 포트, adapters/portfolio -- Tests: - - stub read 통합테스트, import 스윕 - -#### Delivery Trace Delta -- Batches: - - none (미배치) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인 게이트 점검 - -#### Validation Plan -- stub read 통합테스트 + 도메인 import 스윕으로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. 구현 전 재검증 불필요. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- `PortfolioHubReadPort` + 어댑터(`PortfolioHubReadAdapter`, `StubPortfolioRead`) 구현과 검증 evidence 확보 후 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_scoped_by_managed_by_daemon_id + test_stub_read.py. -- AC-2: tests/adapters/portfolio/test_stub_read.py 전체. -- AC-3: tests/domain/test_domain_import_sweep.py. -- AC-4: tests/adapters/portfolio/test_hub_read.py::test_whoami_returns_token_daemon_id. - -### CHG-20260606-04 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- 실 portfolio-hub API 와의 계약 불일치를 정정: whoami 엔드포인트는 `/v1/auth/whoami`(부재) → **`GET /v1/user_info`**, list 응답 envelope 는 `{items:[…]}` → **`{portfolios:[…], total}`**, `/v1/portfolios` 는 토큰 스코프라 `managed_by_daemon_id` 쿼리 파라미터가 없어 **클라이언트측 필터**로 전환. 실 `PortfolioItem` 에 `active_strategy_id`/`strategy_params` 가 없어 None/`{}` 기본값으로 통과(전략 필드 실데이터 채움은 이월). 어댑터/테스트는 이미 정정 구현·재검증됨. - -#### Intent -- 가정 기반(confirm-discovery 미검증)으로 작성됐던 read 계약을 라이브 API 실측에 맞춰 정정하고, 정정 구현의 재검증 evidence(신규 테스트 + 실 e2e)를 연결한다. - -#### Acceptance Criteria Delta -- Added: none -- Updated: - - AC-1: list 조회는 토큰 스코프 + 클라이언트측 `managed_by_daemon_id` 필터로 동작. 반환 항목은 동일 필드 형태(가격 없음)를 유지하되, 실 `/v1/portfolios`(`PortfolioItem`)가 `active_strategy_id`/`strategy_params` 를 제공하지 않아 해당 두 필드는 None/`{}` 로 채워진다(실데이터 채움은 이월 — REQ-029 CHG-04, detail `strategy_set` 보강 또는 별도 change-req). - - AC-4: 토큰 daemon_id 도출은 `GET /v1/user_info` 응답의 `daemon_id` 로 한다(`/v1/auth/whoami` 아님). -- Removed: none - -#### Impacted Area Delta -- Docs: - - 본 REQ Requirement/AC-1/AC-4/Notes 정정 - - BAT-008 design.md/verification.md 계약 정정 반영(같은 변경 묶음) -- Modules: - - trex_core/adapters/portfolio/hub_read.py (정정 구현 완료, commit 43bad24) -- Tests: - - tests/adapters/portfolio/test_hub_read.py (실 계약으로 갱신, commit 43bad24) - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) — 계약 정정 반영, verification 재확인 대상 -- Releases: - - none (첫 릴리즈 묶음 대상) - -#### Revalidation Impact -- Reverification Needed: yes — 완료. 정정 어댑터에 대해 신규 contract 테스트 통과 + 실 portfolio-hub e2e(whoami→get_portfolios→scheduler run_once `degraded=false`, 삼성전자 1건 수신) 확인. -- Existing Implementation Invalidated: no — 현재 코드가 실 계약과 일치(정정 후). 무효화된 것은 구 verification 산출물의 테스트명/엔드포인트 기술뿐이며 BAT-008 verification 갱신으로 동기화. -- Status Recommendation: Implemented 유지. -- Follow-up Action: 전략 필드 실데이터 채움(2번째 전략 도입 시) 재검토. portfolio-hub#9(user_info 500) 수정 완료로 daemon_id 도출 가능. - -#### Validation Plan -- AC-1: tests/adapters/portfolio/test_hub_read.py::test_get_portfolios_parses_envelope_and_filters_by_daemon + ::test_get_portfolios_includes_items_without_daemon_field + test_stub_read.py. -- AC-4: ::test_whoami_returns_token_daemon_id (`/v1/user_info` 단언) + ::test_base_url_without_scheme_defaults_to_http. -- 실 e2e: SnapshotScheduler.run_once() against live portfolio-hub → degraded=None, universe 1건. diff --git a/docs/srs/Integration/req-030_daily-snapshot-scheduler-and-option-d-cash-position.md b/docs/srs/Integration/req-030_daily-snapshot-scheduler-and-option-d-cash-position.md deleted file mode 100644 index b0f53fd..0000000 --- a/docs/srs/Integration/req-030_daily-snapshot-scheduler-and-option-d-cash-position.md +++ /dev/null @@ -1,173 +0,0 @@ -# req-030: 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- universe 멤버십·전략파라미터는 일일 스냅샷으로 고정하고, 현금/포지션은 portfolio-hub 가 ledger write 응답으로 돌려주는 권위 값으로 유지해 로컬 누적 보정 drift 를 제거한다(옵션 D). - -## Requirement -- 설정 가능한 시각(기본 07:30 KST, .env)에 스케줄러(신규 daemon 컴포넌트)가 `daemon_id` universe 를 페치해 스냅샷을 갱신한다. universe 멤버십·전략파라미터는 그 일일 스냅샷으로 고정한다. -- 현금/포지션은 스냅샷 baseline 에 더해, 체결 시 portfolio-hub ledger write 응답의 `cash_balance_after` / `asset_quantity_after` 권위 값으로 해당 자산을 갱신한다. - -## Acceptance Criteria -1. Given 설정 시각 도래, When 스케줄러 tick, Then universe 를 페치해 스냅샷(멤버십·전략파라미터·baseline 현금/포지션)을 갱신한다. -2. Given 체결 후 ledger write 응답이 `*_after` 를 반환, When 처리, Then 해당 자산의 현금/포지션을 그 권위 값으로 갱신한다. -3. Given `duplicate` write 응답(idempotent), When 처리, Then 현재 권위 `*_after` 값으로 동일하게 갱신한다. - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/runtime-flows.md -- Modules: 일일 스냅샷 스케줄러(런타임 신규), ledger write 응답 소비/스냅샷 갱신 로직, config(스냅샷 페치 시각) -- Tests: 스케줄 tick 페치, `*_after` 갱신, duplicate idempotent 갱신 - -## Notes -- **의존: portfolio-hub#8 — ledger write 응답에 `cash_balance_after`/`asset_quantity_after` 반환(회계 단일 트랜잭션). portfolio-hub 배포(REL-003 등) 전까지 본 REQ 의 옵션 D 갱신 구현은 대기(dcy-005 A-7).** 그 전까지는 baseline + 스케줄러 페치까지만 선구현 가능. -- A-6(유일 매매 주체·장중 외부 변동 없음) 전제. -- NFR-2(일관성)를 본 REQ 로 흡수한다. - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-6 + OQ-2 분해로 최초 생성(Proposed). NFR-2 흡수. - -#### Intent -- 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~3 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005, runtime-flows -- Modules: - - 스냅샷 스케줄러, write 응답 소비 로직, config -- Tests: - - 스케줄 tick, *_after 갱신 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: portfolio-hub#8 배포 추적 후 옵션 D 구현 착수 - -#### Validation Plan -- 스케줄 tick 페치 + `*_after` 갱신/idempotent 갱신 테스트로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. 옵션 D 구현은 portfolio-hub#8 배포 후 착수(Notes 의존 유지, 비블로커). - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch; 옵션 D 구현은 portfolio-hub#8 배포 트래킹 후 - -#### Validation Plan -- 승인 기록. 스케줄러 baseline 구현 → portfolio-hub#8 배포 후 옵션 D 갱신 통합 검증. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 유지: Approved. BAT-008 에서 AC-1(일일 스냅샷 스케줄러 baseline)은 구현 완료 + evidence 확보, AC-2/AC-3(옵션 D `*_after` 갱신)은 외부 의존(portfolio-hub#8) 배포 대기로 **deferred**. 본 REQ 는 AC 일부 충족이므로 `Implemented` 전환을 보류한다(SRS `Status Transition Evidence` 의 "미충족 Acceptance Criteria 가 있으면 Implemented 로 전환하지 않는다" 규정 준수). - -#### Intent -- 스케줄러 baseline 부분 구현 완료를 추적 + 옵션 D 후속 작업의 wiring point 가 노출되어 있음을 기록. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 (AC-1 baseline 은 BAT-008 에서 구현 + 검증 완료, AC-2/AC-3 wiring point 노출만 추가) - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate; AC-1 evidence) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: yes (portfolio-hub#8 배포 후 AC-2/AC-3 통합 검증) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Approved 유지) -- Follow-up Action: portfolio-hub#8 배포 완료 알림 수신 시 별도 minor-change batch 로 옵션 D 구현 (LedgerWriter 의 ledger write 응답 소비 경로에서 `cash_balance_after`/`asset_quantity_after` → `SnapshotStore.update_cash_position(...)` 호출 1줄 추가) + AC-2/AC-3 통합 evidence 확보 → REQ-030 Approved → Implemented 전환. - -#### Validation Plan -- AC-1 (현재 완료): tests/application/test_snapshot_scheduler.py::test_run_once_fetches_universe_and_swaps_store + ::test_swap_preserves_warning_for_carried_over_portfolio + 실 daemon boot 후 첫 fetch 동작 확인. -- AC-2 (deferred): portfolio-hub#8 배포 후 ledger write 응답 소비 경로에 wiring 연결 + integration 테스트. -- AC-3 (deferred): duplicate write 응답 idempotent 갱신 테스트. - -#### Notes -- BAT-008 verification.md `Human Approval Memo` 에 deferred residual risk 로 명시되어 있고, batch 전체는 release-candidate 로 promote 됨. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-018) **Status Approved → Implemented**. 연기됐던 option D(AC-2/AC-3: ledger write 응답 `*_after` 로 현금/포지션 갱신)를 portfolio-hub#8 배포·동작 확인 후 실제 구현. `LedgerWriter` 가 `*_after` 를 `SnapshotStore.update_cash_position` 으로 write-back(REQ-019), 어댑터가 `*_after` 파싱(REQ-017). REQ-030 Change Log Follow-up(CHG-20260606-03 "portfolio-hub#8 배포 후 minor-change 로 option D 구현") 예고대로 닫음. - -#### Intent - -- option D 현금/포지션 갱신을 실제 활성화해 AC-2/AC-3 충족 → Approved→Implemented 전환. - -#### Acceptance Criteria Delta - -- (변경 없음 — AC-2/AC-3 의 deferred 해소; ledger write 응답 `*_after` 소비 경로 구현) - -#### Impacted Area Delta - -- Modules: `trex_core/application/ledger_outbox.py`(write-back), `trex_core/adapters/ledger/portfolio_hub.py`(*_after 파싱), `trex_core/runtime/daemon.py`(SnapshotStore 배선) -- Tests: tests/application/test_ledger_option_d.py - -#### Delivery Trace Delta - -- Batches: BAT-023 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-023 에서 AC-2/AC-3 통합 검증) -- Existing Implementation Invalidated: no (AC-1 baseline 불변, deferred AC 구현) -- Status Recommendation: **Implemented** (option D 활성화 완료) -- Follow-up Action: confirm-req-implemented(BAT-023) 로 Approved→Implemented 동기화. - -#### Validation Plan - -- BAT-023: ledger write `*_after` → SnapshotStore 갱신 단위 + 실 데몬 e2e(option D). diff --git a/docs/srs/Integration/req-037_forced-close-at-safety-sell-loop.md b/docs/srs/Integration/req-037_forced-close-at-safety-sell-loop.md deleted file mode 100644 index b0b6607..0000000 --- a/docs/srs/Integration/req-037_forced-close-at-safety-sell-loop.md +++ /dev/null @@ -1,189 +0,0 @@ -# req-037: forced_close_at 만기 자동 매도 루프 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent - -슬롯에 설정된 `forced_close_at` 만기 시각이 도달했을 때 데몬이 자율적으로 매도를 제출하는 백그라운드 루프를 추가한다. 워치독이 만료된 슬롯에 한해 동작하므로 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-1, NFR-1). - -## Requirement - -- 데몬은 `TREX_FORCED_CLOSE_CHECK_INTERVAL(초)` 주기로 `holding` 상태이며 **워치독이 만료된** 슬롯 중 `forced_close_at <= now(KST)`인 슬롯을 감지해야 한다. -- 조건에 해당하는 슬롯에 대해 `SlotService.sell()`(REQ-036)을 통해 내부 매도를 제출해야 한다. -- 이 루프는 daemon.py의 safety-sell worker 스레드에서 실행된다. 기존 6개 스레드에 safety-sell worker(`trex-safety-sell`)를 추가한다. -- `forced_close_at`이 `None`인 슬롯은 이 루프의 대상이 아니다. -- 워치독 활성(만료되지 않은) 슬롯은 이 루프의 대상이 아니다. - -## Acceptance Criteria - -1. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다. -2. Given `holding` 상태이고 워치독 만료이며 `forced_close_at = now() + 1hour`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다. -3. Given `holding` 상태이고 워치독 **활성**이며 `forced_close_at = now() − 1s`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S에 대해 `sell()`이 호출되지 않는다(워치독 보호). -4. Given `forced_close_at = None`인 슬롯 S가 있을 때, When checker 1회가 수행되면, Then S는 만기 체크 대상에서 제외된다. -5. Given 대상 슬롯이 이미 `pending_sell`일 때, When checker 1회가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency 적용). -6. Given `TREX_FORCED_CLOSE_CHECK_INTERVAL=60`일 때, When daemon이 정상 실행 중이면, Then checker는 60±허용오차 초 주기로 반복 실행된다(NFR-1: 감지 지연 ≤ 60초). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — forced_close_at 만기 감지 루프 흐름 추가 (REQ-042 참조) - - `docs/project-structure.md` — `trex-safety-sell` 스레드 추가 반영 (REQ-042 참조) -- Modules: - - `trex_core/runtime/daemon.py` — `trex-safety-sell` worker 스레드 추가, `_safety_sell_loop()` 메서드 구현 - - `trex_core/domain/slot/service.py` — forced_close_at 조건 평가 메서드(또는 checker 내 인라인) -- Tests: - - forced_close_at checker 단위 테스트 — 만기 슬롯 감지 + sell() 호출 확인 - - 워치독 활성 슬롯 통과 확인 - - idempotency 확인 (pending_sell 슬롯 중복 처리 없음) - -## Notes - -- safety-sell worker(`trex-safety-sell`)는 forced_close_at checker(REQ-037), stop-loss·take-profit checker(REQ-038), 장 마감 청산(REQ-039)을 하나의 통합 루프로 묶어 처리할 수 있다. 구체적인 스레드 분리/통합 방식은 batch planning에서 결정한다. -- 체크 로직은 DB 조회(forced_close_at 필드)와 in-memory 워치독 store 비교만 수행하므로 KIS API 호출이 없다. 낮은 연산 비용. -- `TREX_FORCED_CLOSE_CHECK_INTERVAL` env var는 REQ-041 참조. -- `SlotService.sell()` 구현은 REQ-036 참조. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-037 초안 생성 - -#### Intent - -- forced_close_at 만기 자동 매도 루프(FR-1)를 독립 Integration REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, project-structure.md (REQ-042에서 처리) -- Modules: daemon.py (스레드 추가), service.py (forced_close 조건 평가) -- Tests: checker 단위 테스트 3종 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). - -#### Intent - -- BAT-011 verification.md 의 AC↔evidence(SafetySellService forced_close checker — 만기/미만기/워치독 보호/None 제외/idempotency/캐던스)로 AC-1~6 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-011 배포 편성. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017/issue #12) safety-sell 가시화: 워치독 소유 진입(전략 이탈)=WARN/이탈(복귀)=INFO(`_update_ownership`, run_once 매 tick), forced_close 발동=WARN(`_log_fire`) + in-process 카운터(`SafetySellService.counters`). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). best-effort(가시화 실패가 청산을 막지 않음). 트리거 판정 동작 불변. BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 전략 이탈→안전망 소유, forced_close 발동을 무성에서 가시화(OQ-2 차등 매트릭스). - -#### Acceptance Criteria Delta - -- Added: - - 워치독 소유 진입/이탈 + forced_close 발동 로그/카운터. per-tick 스킵은 카운터만. - -#### Impacted Area Delta - -- Modules: `trex_core/application/safety_sell.py`(_update_ownership/_log_fire/counters/instance_key), `trex_core/runtime/daemon.py`(instance_key 배선) -- Tests: tests/application/test_safety_sell_visibility.py - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가, 트리거 불변) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: 소유 진입 WARN/이탈 INFO/forced_close WARN + 카운터 단위(caplog). diff --git a/docs/srs/Integration/req-038_stop-loss-take-profit-kis-polling.md b/docs/srs/Integration/req-038_stop-loss-take-profit-kis-polling.md deleted file mode 100644 index 8c28a46..0000000 --- a/docs/srs/Integration/req-038_stop-loss-take-profit-kis-polling.md +++ /dev/null @@ -1,276 +0,0 @@ -# req-038: stop-loss·take-profit KIS 가격 polling + 트리거 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent - -워치독이 만료된 슬롯에 한해 KIS API(`TTTC8397R` 실전 / `VTTC8397R` 모의)로 현재가를 주기적으로 polling하고, `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 자동 매도를 제출한다. 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-2, NFR-3, A-3). - -## Requirement - -- 데몬은 `TREX_STOP_LOSS_POLL_INTERVAL(초)` 주기로, `stop_loss_price` 또는 `take_profit_price`가 설정되고 **워치독이 만료된** `holding` 슬롯들의 현재가를 KIS 주식현재가 시세 API(`/uapi/domestic-stock/v1/quotations/inquire-price`, tr_id `FHKST01010100`, 실전/모의 공용)로 polling해야 한다. (CHG-20260607-04: 시세 tr_id 정정 — 구 `TTTC8397R`/`VTTC8397R`는 실 KIS 에서 "없는 서비스 코드".) -- 현재가 평가 기준값은 `stck_prpr`(직전 체결가) 필드를 사용한다. -- `현재가 <= stop_loss_price` 조건 충족 시 손절 매도를 제출해야 한다. -- `현재가 >= take_profit_price` 조건 충족 시 익절 매도를 제출해야 한다. -- 두 조건은 동일 polling 루프에서 평가한다. -- 워치독 활성(만료되지 않은) 슬롯과 임계값 미설정(`None`) 슬롯은 polling 대상이 아니다. -- KIS API 호출 상한은 `워치독 만료 대상 슬롯 수 × (3600 / TREX_STOP_LOSS_POLL_INTERVAL)`로 산정한다. 이 상한이 KIS 쿼터를 초과할 경우 polling 간격 조정 AC를 이 REQ의 Notes에 명시하고 REQ 단계에서 상한 경고를 구현한다. -- 자동 매도 제출은 `SlotService.sell()`(REQ-036)을 사용한다. - -## Acceptance Criteria - -1. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=None`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=49000`이면, When 가격 체크가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다(손절 트리거). -2. Given `holding`, 워치독 만료, `stop_loss_price=None`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=81000`이면, When 가격 체크가 수행되면, Then S에 대해 `sell()`이 호출된다(익절 트리거). -3. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 `stck_prpr=65000`이면, When 가격 체크가 수행되면, Then `sell()`이 호출되지 않는다(두 조건 모두 미충족). -4. Given `holding`, 워치독 **활성**인 슬롯 S가 있을 때, When 가격 polling이 수행되면, Then S에 대한 KIS API 호출이 발생하지 않는다(워치독 보호). -5. Given `stop_loss_price=None`이고 `take_profit_price=None`인 슬롯 S가 있을 때, When polling이 수행되면, Then S는 polling 대상에서 제외된다. -6. Given 이미 `pending_sell` 상태인 슬롯 S에 조건이 충족될 때, When 가격 체크가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency). -7. Given `broker=kis` 일 때, When 가격 polling을 수행하면, Then KIS 주식현재가 시세 endpoint(`/uapi/domestic-stock/v1/quotations/inquire-price`)를 tr_id `FHKST01010100`(실전/모의 공용)로 호출하고 `output.stck_prpr` 를 파싱한다. 실 KIS LIVE 호출이 `rt_cd=0` + `stck_prpr` 를 반환함을 확인한다(구 `TTTC8397R` 는 `rt_cd=1` "없는 서비스 코드" 로 거부됨). -8. Given `TREX_STOP_LOSS_POLL_INTERVAL=30`이고 워치독 만료 대상 슬롯이 N개일 때, When 정상 동작 중이면, Then 1시간당 KIS API 호출 수가 `N × 120`회를 넘지 않는다(NFR-3 쿼터 산정). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — stop-loss·take-profit 가격 체크 흐름 추가 (REQ-042 참조) - - `docs/interface-contract.md` — 현재가 polling 어댑터 계약 명시 (REQ-042 참조) -- Modules: - - 신규 `trex_core/adapters/broker/kis_price.py` 또는 `kis.py` 확장 — `TTTC8397R`/`VTTC8397R` 현재가 polling, `stck_prpr` 파싱 - - `trex_core/runtime/daemon.py` — stop-loss·take-profit 가격 체크를 safety-sell worker에 통합 - - `trex_core/domain/slot/service.py` — stop-loss·take-profit 조건 평가 메서드(또는 worker 내 인라인) -- Tests: - - KIS 현재가 polling 어댑터 단위 테스트 — `stck_prpr` 파싱 확인, 실전/모의 분기 확인 - - 가격 체크 단위 테스트(mock polling) — 손절/익절/미충족/워치독 보호 시나리오 - -## Notes - -- KIS 현재가 polling 어댑터는 기존 `trex_core/adapters/broker/kis.py`에 `get_current_price(symbol)` 메서드로 추가하거나, 별도 모듈로 분리한다. batch planning에서 결정한다. -- paper broker 모의 환경에서는 stub 구현(`set_current_price`/`get_current_price`)으로 현재가를 반환한다(KIS 시세 호출 없음). 실 KIS(`broker=kis`) 는 `FHKST01010100` 시세 tr 사용. -- KIS 쿼터 초과 방지: 워치독 만료 슬롯 수가 임계값을 넘으면 polling 간격 자동 조정 또는 경고 로그를 구현한다. 구체적인 쿼터 수치는 KIS API 문서 기준으로 batch 설계에서 확정한다. -- `TREX_STOP_LOSS_POLL_INTERVAL` env var는 REQ-041 참조. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-038 초안 생성 - -#### Intent - -- stop-loss·take-profit KIS 가격 polling + 트리거(FR-2)를 독립 Integration REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~8 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리) -- Modules: kis 현재가 polling 어댑터(신규/확장), daemon.py, service.py -- Tests: polling 어댑터 단위 + 가격 체크 시나리오 단위 (mock) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). - -#### Intent - -- BAT-011 verification.md 의 AC↔evidence(KIS `get_current_price` TTTC8397R/VTTC8397R·`stck_prpr` 파싱 + 손절/익절/미충족/워치독 보호/쿼터 산정/가격 미수신 skip)로 AC-1~8 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-011 배포 편성. - -### CHG-20260607-04 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- 현재가 시세 tr_id 정정: `TTTC8397R`(실전)/`VTTC8397R`(모의) → `FHKST01010100`(주식현재가 시세, 실전/모의 공용). Status Implemented → Approved (AC-7 evidence 무효). - -#### Intent - -- 배포 전 실 KIS LIVE 진단(읽기 전용 시세 호출)에서 구 tr_id 가 `rt_cd=1` "없는 서비스 코드 입니다"(OPSQ0002) 로 거부됨을 확인. 이 경우 `get_current_price` 가 항상 None 을 반환 → stop-loss/take-profit 가 LIVE 에서 **조용히 미발동**. 표준 시세 tr `FHKST01010100` 는 `rt_cd=0` + `stck_prpr=329000`(005930) 정상 수신. mock/샌드박스로는 잡히지 않는 결함이라 LIVE 검증으로 포착. - -#### Acceptance Criteria Delta - -- Updated: - - AC-7: 시세 endpoint/tr_id 를 `FHKST01010100`(실전/모의 공용)로 정정. 실 KIS LIVE `rt_cd=0` + `stck_prpr` 수신 검증 추가. -- Added: - - 없음 -- Removed: - - 없음 (TTTC8397R/VTTC8397R 분기 폐기) - -#### Impacted Area Delta - -- Docs: - - 없음 (REQ 본문 + 후속 baseline 은 verification 단계 갱신) -- Modules: - - `trex_core/adapters/broker/kis.py` (`_tr_price` → `FHKST01010100`, live/mock 분기 제거) -- Tests: - - `tests/adapters/broker/test_kis_price.py` (AC-7 tr_id 단언 정정) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate — AC-7 재구현/재검증) -- Releases: REL-003 (confirmed — 본 정정 반영 후 재확인 필요) - -#### Revalidation Impact - -- Reverification Needed: yes (AC-7 한정; AC-1~6/8 은 paper/단위 evidence 유효) -- Existing Implementation Invalidated: yes (AC-7 — 구 tr_id 코드/테스트) -- Status Recommendation: Implemented → Approved (AC-7 재검증 전까지) -- Follow-up Action: kis.py 수정 + LIVE 시세 재검증 + 단위 정정 → confirm-req-implemented 로 Implemented 재전환 → REL-003 재확인. - -#### Validation Plan - -- 실 KIS LIVE `get_current_price('005930')` → Decimal(약 329000) 반환 확인(읽기 전용). -- `test_kis_price.py` AC-7: `_tr_price()` == `FHKST01010100` 단언 + `stck_prpr` 파싱. -- 단위 전체 회귀 + ruff. - -### CHG-20260607-05 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, CHG-04 정정 재검증 완료). - -#### Intent - -- CHG-04 의 tr_id 정정(`FHKST01010100`)을 구현·재검증: `kis.py _tr_price()` 수정, `test_kis_price.py` AC-7 정정(7 passed), 단위 전체 236 passed/ruff clean, **실 KIS LIVE `get_current_price('005930')=Decimal('329000')`(rt_cd=0)** 확인. AC-7 evidence 복구 → 전체 AC 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: REL-003 (confirmed — 정정 반영 후 재확인) - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: REL-003 재확인 후 배포. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017/issue #12) SL/TP 발동 가시화: `stop_loss` 발동=WARN, `take_profit` 발동=INFO(`_log_fire`) + 카운터(`fire:stop_loss`/`fire:take_profit`). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). 판정·KIS polling 동작 불변(관측만). BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 손절/익절 발동을 무성에서 가시화(OQ-2: stop_loss=WARN 주의사건, take_profit=INFO). - -#### Acceptance Criteria Delta - -- Added: - - stop_loss(WARN)/take_profit(INFO) 발동 로그 + 카운터. - -#### Impacted Area Delta - -- Modules: `trex_core/application/safety_sell.py`(check_prices 발동 가시화) -- Tests: tests/application/test_safety_sell_visibility.py::test_take_profit_fire_logs_info - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: take_profit 발동 INFO + 카운터 단위. diff --git a/docs/srs/Integration/req-039_market-close-liquidation.md b/docs/srs/Integration/req-039_market-close-liquidation.md deleted file mode 100644 index fd23cb9..0000000 --- a/docs/srs/Integration/req-039_market-close-liquidation.md +++ /dev/null @@ -1,186 +0,0 @@ -# req-039: 장 마감 강제 청산 (TREX_MARKET_CLOSE_LIQUIDATION_TIME) - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent - -`TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST)이 설정된 경우, 해당 시각에 전략 모듈 활성 여부와 워치독 만료 여부에 관계없이 모든 `holding` 슬롯에 매도를 제출해 장 마감 전 자동 청산을 보장한다. 미설정 시에는 동작하지 않는다(dcy-007 FR-6). - -## Requirement - -- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`(HH:MM KST 형식, optional)이 설정된 경우, 데몬은 해당 시각이 도래하면 워치독 만료 여부와 무관하게 모든 `holding` 슬롯에 `SlotService.sell()`(REQ-036)을 통해 매도를 제출해야 한다. -- `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않은 경우 이 기능은 동작하지 않는다. -- 장 마감 청산은 같은 거래일에 한 번만 발동해야 한다(중복 발동 방지). -- 청산 대상은 발동 시각의 `holding` 슬롯 전체이며 워치독 상태를 조건으로 사용하지 않는다. - -## Acceptance Criteria - -1. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 `holding` 슬롯이 N개 있을 때, When KST 15:10이 도래하면, Then N개 슬롯 모두에 `SlotService.sell()`이 호출된다. -2. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME=15:10`이고 워치독 **활성** 슬롯이 포함되어 있을 때, When KST 15:10이 도래하면, Then 워치독 활성 슬롯도 청산 대상에 포함된다(워치독 무관). -3. Given `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 설정되지 않았을 때, When 어느 시각이든, Then 장 마감 청산 루프가 매도를 제출하지 않는다. -4. Given 장 마감 청산이 같은 거래일 내에 이미 발동된 적이 있을 때, When 같은 거래일 내 청산 로직이 다시 평가되면, Then 중복 발동이 발생하지 않는다. -5. Given 이미 `pending_sell` 상태인 슬롯 S가 청산 대상에 포함될 때, When 장 마감 청산이 발동되면, Then S에 대한 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency). -6. Given grace period(REQ-034) 중에 `TREX_MARKET_CLOSE_LIQUIDATION_TIME`이 도래할 때, When 장 마감 청산이 발동되면, Then grace period 여부와 무관하게 청산이 실행된다(FR-6: 워치독 무관). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — 장 마감 강제 청산 흐름 추가 (REQ-042 참조) -- Modules: - - `trex_core/runtime/daemon.py` — safety-sell worker 내 장 마감 청산 로직 추가 - - `trex_core/runtime/config.py` — `TREX_MARKET_CLOSE_LIQUIDATION_TIME` 파싱 (REQ-041 참조) -- Tests: - - 장 마감 청산 단위 테스트(시각 mock) — 설정/미설정 분기, 전체 holding 청산 확인 - - 중복 발동 방지 테스트 - - grace period 무관 동작 테스트 - -## Notes - -- 장 마감 청산 발동은 safety-sell worker 루프(`trex-safety-sell`)에서 KST 시각을 주기적으로 체크하는 방식으로 구현한다. 발동 여부는 in-memory `last_liquidation_date`로 추적한다. -- 발동 시각 정밀도는 worker 루프 주기에 의존한다. `TREX_FORCED_CLOSE_CHECK_INTERVAL` 또는 별도 설정으로 루프 주기를 결정한다. batch에서 확정한다. -- `TREX_MARKET_CLOSE_LIQUIDATION_TIME` env var는 REQ-041 참조. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-039 초안 생성 - -#### Intent - -- 장 마감 강제 청산(FR-6)을 독립 Integration REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md (REQ-042에서 처리) -- Modules: daemon.py (safety-sell worker 확장), config.py -- Tests: 단위 테스트 3종 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). - -#### Intent - -- BAT-011 verification.md 의 AC↔evidence(시각 도래 전체 holding 청산·워치독 무관·미설정 무동작·거래일 1회 가드·grace 무관·발동 전 무동작)로 AC-1~6 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-011 배포 편성. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017/issue #12) market_close 청산 발동 가시화: `market_close_liquidation` 발동=INFO(`_log_fire`) + 카운터. 구조화 필드 포함. 청산 동작(워치독/grace 무관, 1일 1회) 불변. BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 장 마감 청산 발동을 가시화(OQ-2: market_close=INFO). - -#### Acceptance Criteria Delta - -- Added: - - market_close 발동 INFO 로그 + 카운터. - -#### Impacted Area Delta - -- Modules: `trex_core/application/safety_sell.py`(check_market_close 발동 가시화) -- Tests: tests/application/test_safety_sell_visibility.py(가시화 전반) + 기존 market_close 회귀 - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: market_close 발동 시 INFO + 카운터(기존 청산 회귀 동반). diff --git a/docs/srs/Integration/req-050_direct-broadcast-exchanges-and-binding.md b/docs/srs/Integration/req-050_direct-broadcast-exchanges-and-binding.md deleted file mode 100644 index 0df74d5..0000000 --- a/docs/srs/Integration/req-050_direct-broadcast-exchanges-and-binding.md +++ /dev/null @@ -1,147 +0,0 @@ -# req-050: trex.direct(topic) + trex.broadcast(fanout) exchange + 바인딩 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent - -인스턴스 지정 라우팅과 전역 브로드캐스트를 위해 두 exchange 를 도입한다. `trex.direct`(topic)는 strategy/운영자가 특정 인스턴스에만 메시지를 보낼 때 routing key `{instance_key}.{channel_type}` 으로 라우팅하고, `trex.broadcast`(fanout)는 global kill-switch 등 전체 인스턴스 대상 제어를 모든 control 큐에 동시 전달한다(dcy-009 FR-3, FR-4). routing key 계약을 인터페이스로 정의한다. - -## Requirement - -- `topology.py` 에 `DIRECT_EXCHANGE = "trex.direct"`(type topic, durable), `BROADCAST_EXCHANGE = "trex.broadcast"`(type fanout, durable) 상수와 routing key 빌더(`direct_routing_key(instance_key, channel_type)` → `{instance_key}.{channel_type}`)를 추가한다. -- `declare_topology(instance_key)` 가 두 exchange 를 선언하고, 각 인스턴스의 intent/control/snapshot 큐를 `trex.direct` 에 routing key `{instance_key}.intent`/`.control`/`.snapshot` 으로 바인딩한다(FR-3). -- 각 인스턴스의 **control 큐**를 `trex.broadcast`(fanout)에 추가 바인딩(dual binding)한다(FR-4). 전역 제어 메시지는 `trex.broadcast` 로 publish 하면 모든 인스턴스 control 큐에 전달된다. -- `default` 인스턴스 하위호환: default 의 큐(리터럴명, REQ-049)도 동일 규칙으로 `trex.direct`(`default.intent` 등) + control dual binding 된다. 기존 직접 큐 publish 경로(reply-to RPC)는 유지되어 회귀가 없어야 한다(FR-5). -- routing key 계약은 `docs/interface-contract.md` 에 명시한다(REQ-051): directed = `{instance_key}.{channel_type}`, broadcast = fanout(키 무시). - -## Acceptance Criteria - -1. Given `declare_topology("inst-1")`, When 선언, Then `trex.direct`(topic) + `trex.broadcast`(fanout) exchange 가 durable 로 선언된다. -2. Given `declare_topology("inst-1")`, When 바인딩 확인, Then `trex.intent.req.inst-1`/`...control...`/`...snapshot...` 가 `trex.direct` 에 routing key `inst-1.intent`/`inst-1.control`/`inst-1.snapshot` 으로 바인딩된다(FR-3). -3. Given `direct_routing_key("inst-1","control")`, When 호출, Then `"inst-1.control"` 을 반환한다. -4. Given `inst-1`/`inst-2` 두 인스턴스 큐가 선언됨, When `trex.direct` 에 `inst-1.intent` 로 publish, Then `trex.intent.req.inst-1` 에만 전달되고 `...inst-2` 에는 전달되지 않는다(S-2). -5. Given control 큐 dual binding, When `trex.broadcast` 에 publish, Then `trex.control.req.inst-1` 과 `...inst-2` control 큐 모두에 전달된다(FR-4, S-3). -6. Given `instance_key="default"`, When 전체 회귀, Then 기존 RPC(reply-to) 경로 동작 동일·회귀 없음(FR-5). - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — routing key 계약(`{instance_key}.{channel_type}`, broadcast) (REQ-051 처리) - - `docs/runtime-flows.md` — directed vs broadcast 라우팅 흐름 (REQ-051 처리) -- Modules: - - `trex_core/adapters/messaging/topology.py` — DIRECT/BROADCAST exchange + routing key 빌더 - - `trex_core/adapters/messaging/rabbitmq.py` — exchange 선언 + 큐 바인딩(direct per-instance + control dual) + broadcast publish 경로 -- Tests: - - `tests/adapters/messaging/` — exchange 선언 + 바인딩 + directed 라우팅 격리 + broadcast 전달(인프라 게이트) - -## Notes - -- R-3(broadcast 중복 처리): kill-switch/pause 핸들러는 idempotent(REQ-036 패턴/advisory lock)라 중복 전달 무해. -- strategy 모듈(orb-v1) publish 코드 변경은 Out of Scope — 본 REQ 는 exchange/binding/routing key 계약만 정의(CONFIRM_STRATEGY_MODULE_OUT_OF_SCOPE). -- broadcast publish helper 는 transport 에 추가하거나 기존 publish 경로 재사용 — 구현에서 결정. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-009(confirmed) → REQ-050 초안 생성. - -#### Intent - -- trex.direct/trex.broadcast exchange + 바인딩 + routing key 계약(FR-3/FR-4)을 Integration REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md (REQ-051) -- Modules: topology.py, rabbitmq.py -- Tests: tests/adapters/messaging (인프라 게이트) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-009(confirmed) FR-3/FR-4 와 일치. strategy publish 변경은 Out of Scope(계약만 정의) — blocker 아님. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-013 release-candidate). - -#### Intent - -- BAT-013 verification 의 AC↔evidence(routing key 단위 + 인프라 게이트: trex.direct/trex.broadcast 선언 + directed 라우팅 격리 S-2 + broadcast dual S-3, 301 passed)로 AC-1~6 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-013 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-013 배포 편성. strategy publish 변경은 별도(Out of Scope). diff --git a/docs/srs/Integration/req-052_safety-sell-per-slot-cooldown.md b/docs/srs/Integration/req-052_safety-sell-per-slot-cooldown.md deleted file mode 100644 index 184e5b5..0000000 --- a/docs/srs/Integration/req-052_safety-sell-per-slot-cooldown.md +++ /dev/null @@ -1,147 +0,0 @@ -# req-052: safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent - -`SafetySellService`가 브로커 영속 거부(휴장/잔고부족/규제) 상황에서 동일 슬롯에 매도를 무한 재제출하는 문제(2026-06-07 실 KIS LIVE SL/TP e2e 발견, REL-003 Feedback)를 막는다. 슬롯별 마지막 시도 시각·시도 횟수를 인메모리로 추적해 `min_retry_interval_sec` 이내 재시도를 건너뛰고, 반복 거부 시 운영자 경고를 발행한다. 추가 스레드·DB 스키마 변경 없음(dcy-010 FR-1~4, NFR-1/2/4). - -## Requirement - -- `SafetySellService`는 슬롯별 `last_attempt_at: dict[UUID, datetime]`, `attempt_count: dict[UUID, int]`를 인메모리로 추적한다. -- 각 트리거(`forced_close`/`stop_loss`/`take_profit`)가 `_safe_sell()`을 호출하기 전, 해당 슬롯이 `now - last_attempt_at < min_retry_interval_sec`이면 **건너뛴다**(재시도 안 함). market_close 는 `_last_liquidation_date`로 이미 하루 1회 보호되므로 쿨다운 대상에서 제외(dcy-010 A-3). -- 쿨다운 만료(`>= min_retry_interval_sec`) 후 트리거 조건이 여전히 충족되면 재시도하고, 성공 여부와 무관하게 `last_attempt_at`을 갱신하고 `attempt_count`를 증가시킨다. -- `attempt_count >= max_retry_warn_count`인 슬롯은 재시도 직전 `WARNING` 로그를 발행한다(포함: `slot_id`, `attempt_count`, `reason`, `last_attempt_at`). -- 슬롯이 `_holding()` 목록에서 사라지면(CLOSED/IDLE 등) 해당 슬롯의 `last_attempt_at`/`attempt_count` 항목을 제거한다(상태 누수 방지). -- `min_retry_interval_sec`/`max_retry_warn_count`는 `SafetySellConfig`(REQ-053)에서 주입받는다. 단일 `trex-safety-sell` 스레드 호출이라 별도 락 없음. - -## Acceptance Criteria - -1. Given paper 브로커 `reject_symbols`로 거부 시뮬레이션 + stop-loss 트리거 슬롯, When `run_once()`를 쿨다운 간격 이내에 N회 호출, Then `_safe_sell`(또는 `SlotService.sell`) 재호출이 첫 1회 이후 발생하지 않는다(S-1). -2. Given 슬롯 `last_attempt_at`을 `min_retry_interval_sec` 이전 과거로 설정 + 트리거 조건 유지, When `run_once()`/`check_*` 호출, Then 재시도 1회 실행 + `last_attempt_at` 갱신(S-2). -3. Given `attempt_count = max_retry_warn_count - 1` 상태에서 다음 재시도, When 재시도 직전, Then `WARNING` 로그가 발행되고 `slot_id`/`attempt_count`/`reason` 포함(S-3). -4. Given 슬롯이 CLOSED 로 전환되어 `_holding()`에서 사라짐, When 다음 `run_once()`, Then `last_attempt_at`/`attempt_count`에서 해당 슬롯 항목이 제거된다(S-4). -5. Given market_close 트리거, When 쿨다운 로직 평가, Then market_close 는 쿨다운 적용 대상이 아니다(하루 1회 가드 유지). -6. Given 가격 회복 등으로 트리거 조건이 해소된 슬롯, When `check_prices()`, Then `_safe_sell` 미호출 + `attempt_count` 미증가(R-1). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — safety-sell tick 에 쿨다운 체크 단계 (REQ-054 처리) -- Modules: - - `trex_core/application/safety_sell.py` — `__init__`(`_last_attempt`/`_attempt_count`), `check_forced_close`/`check_prices`(쿨다운 check/update), `run_once`(holding 이탈 슬롯 정리), 경고 로그 -- Tests: - - `tests/application/test_safety_sell_worker.py`(또는 신규) — 쿨다운 skip/만료 재시도/경고 로그/정리/market_close 제외/트리거 해소(paper reject_symbols 시뮬레이션) - -## Notes - -- dcy-010 OQ-3 Answered: 단일 per-slot 쿨다운(트리거별 분리 안 함). -- ExecutionService→SafetySellService 거부 사유 피드백 채널은 Out of Scope — `_holding()` 재등장으로 암묵적 거부 감지(A-2). -- 쿨다운 상태는 재시작 시 초기화(허용, R-2) — DB 영속 Out of Scope. -- NFR-1(인메모리)/NFR-2(무락)/NFR-4(paper reject 시뮬 테스트)는 본 REQ AC 로 흡수. - -## Change Log - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- dcy-010(confirmed) → REQ-052 초안 생성. - -#### Intent - -- safety-sell 영속 거부 per-slot 쿨다운 + 경고(FR-1~4)를 Integration REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md (REQ-054) -- Modules: safety_sell.py -- Tests: test_safety_sell_worker.py(쿨다운) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC(1~6)·Impacted Area·Change Log 완비. dcy-010(confirmed) FR-1~4/OQ 와 모순 없음. ExecutionService 피드백 채널·DB 영속은 Out of Scope(비핵심). - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-batch 로 delivery 편성(batch-lite). - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-014 release-candidate). - -#### Intent - -- BAT-014 verification 의 AC↔evidence(쿨다운 6 단위: skip/만료 재시도/경고/prune/market_close 제외/트리거 해소)로 AC-1~6 충족. 단위 278 passed, ruff clean. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-014 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-014 배포 편성. 실 운영 거부 시나리오는 차기 거래일 관찰. diff --git a/docs/srs/Integration/req-058_universe-slot-reconcile-lifecycle.md b/docs/srs/Integration/req-058_universe-slot-reconcile-lifecycle.md deleted file mode 100644 index 91353a6..0000000 --- a/docs/srs/Integration/req-058_universe-slot-reconcile-lifecycle.md +++ /dev/null @@ -1,135 +0,0 @@ -# req-058: universe 기반 슬롯 lifecycle reconcile (생성/soft-retire/tombstone) - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 슬롯 집합을 portfolio-hub universe 의 projection 으로 유지하기 위해, universe fetch 결과로 슬롯을 생성/은퇴하는 reconcile 경로를 정의한다. 일일 preallocation(dcy-014)을 폐기하고 universe 동기화로 대체한다. (dcy-020 FR-1/OQ-2/NFR-3) - -## Requirement -- 스냅샷 refresh(`get_portfolios` 성공 직후)에 슬롯 reconcile 을 수행한다: (1) universe 신규 자산 → 영구 슬롯 즉시 생성(비활성·멱등, `UNIQUE(instance_key, portfolio_id)`), 이 때 `PortfolioItem.symbol` 을 슬롯의 `symbol` 컬럼에 함께 저장한다; (2) universe 제거 + 슬롯 비활성(holding/pending 아님) → **soft-retire**(`retired_at` 마킹, 행 보존, hard delete 금지); (3) universe 제거 + 보유 중/pending → **tombstone**(은퇴 보류, safety-sell/watchdog 유지, 신규 매수 후보 제외, 닫힌 뒤 은퇴). reconcile 쓰기는 인스턴스 스코프 advisory lock 안에서 단일 writer 로 수행한다. cadence 는 기존 부팅 + 일 1회 + 운영자 on-demand. portfolio-hub 불가/degraded 또는 universe N→0/급격 대량 감소면 생성·은퇴를 보류하고 경고한다. `symbol IS NULL` 인 기존 슬롯은 다음 reconcile 시 backfill 한다. - -## Acceptance Criteria -1. Given universe 에 신규 자산, When reconcile, Then 그 자산의 영구 슬롯이 비활성으로 생성되고 재실행해도 중복 생성되지 않는다(멱등). -2. Given universe 에서 빠진 비활성 슬롯, When reconcile, Then soft-retire(`retired_at` set, 행 보존)되고, 같은 자산이 재등장하면 동일 슬롯이 재활성화된다. -3. Given universe 에서 빠졌으나 holding/pending 인 슬롯, When reconcile, Then 은퇴되지 않고 tombstone(신규 매수 제외·safety-sell 유지)되며, 매도 체결 후에야 은퇴된다. -4. Given portfolio-hub degraded 또는 universe 가 N→0, When reconcile, Then 슬롯 생성·은퇴가 보류되고 마지막 집합이 유지되며 경고가 남는다. -5. Given reconcile 실행, When 슬롯 생성, Then `daily_slot.symbol` 이 `PortfolioItem.symbol` 값으로 채워진다. Given 기존 슬롯의 `symbol` 이 NULL, When reconcile, Then 해당 슬롯의 `symbol` 이 backfill 된다. - -## Impacted Area -- Docs: docs/runtime-flows.md(reconcile lifecycle) -- Modules: trex_core/application/snapshot_scheduler.py, trex_core/domain/slot/repository.py, trex_core/adapters/persistence/postgresql.py, trex_core/application/preallocation_scheduler.py(폐기) -- Tests: tests/application/test_snapshot_scheduler.py(reconcile), tests/application/test_preallocation_scheduler.py(폐기/대체) - -## Notes -- 기존 REQ-005/REQ-048(일일 preallocation·instance config) change-req 동반(preallocation 폐기·재해석). trading_date KST 계산(REQ-005)은 일일 회계 경계용으로 부분 존속. -- 출처: dcy-020 FR-1, OQ-2, NFR-3. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-1/OQ-2/NFR-3 로부터 최초 생성(Proposed). universe reconcile(생성/soft-retire/tombstone) + degrade·대량소멸 보류 + 일일 preallocation 폐기. - -#### Intent -- 슬롯 집합을 universe projection 으로 유지하는 lifecycle 을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 생성 멱등/비활성 soft-retire 재활성/tombstone/degrade·대량소멸 보류 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md -- Modules: snapshot_scheduler.py, repository.py, postgresql.py, preallocation_scheduler.py(폐기) -- Tests: test_snapshot_scheduler.py, test_preallocation_scheduler.py - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. REQ-005/048 change-req(preallocation 폐기) 동반. - -#### Validation Plan -- reconcile 생성/은퇴/tombstone/degrade 단위·통합 테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260611-01 -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary -- `reconcile_universe` 가 슬롯 생성 시 `symbol` 을 저장하지 않던 구현 누락 수정. `PortfolioItem.symbol` 이 슬롯 INSERT 에 포함되도록 시그니처를 `set[str]` → `dict[str, str]` (portfolio_id → symbol) 로 변경. 기존 `symbol IS NULL` 슬롯을 다음 reconcile 시 자동 backfill 하는 로직 추가. - -#### Intent -- 슬롯 생성 시 symbol 이 누락되어 집행 루프에서 심볼을 조회할 수 없는 상태가 발생. `reconcile_universe` 인터페이스가 portfolio_id 집합만 전달하고 symbol 을 버리는 구조적 누락이었음. - -#### Acceptance Criteria Delta -- Added: - - AC-5: 슬롯 생성 시 `symbol` 컬럼이 `PortfolioItem.symbol` 로 채워진다. 기존 `symbol IS NULL` 슬롯은 다음 reconcile 시 backfill 된다. -- Updated: - - Requirement 본문에 "(이 때 `PortfolioItem.symbol` 을 슬롯의 `symbol` 컬럼에 함께 저장한다)" 및 backfill 조건 추가. -- Removed: 없음 - -#### Impacted Area Delta -- Modules: - - `trex_core/application/snapshot_scheduler.py` — `_reconcile_slots`: `set` → `dict` comprehension - - `trex_core/domain/slot/repository.py` — `reconcile_universe` 시그니처 - - `trex_core/domain/slot/in_memory_repository.py` — 생성 시 symbol 전달 + backfill - - `trex_core/adapters/persistence/postgresql.py` — INSERT에 symbol 추가 + backfill UPDATE -- Tests: 29개 테스트 파일 `reconcile_universe` 호출부 `set` → `dict` 일괄 갱신 (420 passed) -- Docs: 없음 (runtime-flows.md 는 reconcile lifecycle 자체 변경 없음) - -#### Delivery Trace Delta -- Batches: BAT-028(구현 근거), BAT-030(e2e 검증 근거) — 기존 evidence 유효, 신규 AC-5 는 커밋 24a6017 으로 검증 -- Releases: rel-006 배포 후 운영 중 발견(슬롯 symbol NULL 확인) — 핫픽스 아닌 즉시 수정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (AC 1~4 는 기존 evidence 그대로 유효) -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: 없음. 420개 테스트 통과 + DB 확인(23개 슬롯 전부 symbol 채움)으로 AC-5 검증 완료. - -#### Validation Plan -- `reconcile_universe` 호출 후 생성된 슬롯의 `symbol` 이 NULL 이 아닌지 단위 테스트 확인. -- 기존 `symbol IS NULL` 슬롯 보유 DB 에서 데몬 재시작 후 전 슬롯 symbol 채움 확인 (test 인스턴스 23/23 confirmed). - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-028(+BAT-030 default 단일 경로·e2e) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Integration/req-062_quantity-hub-authority-and-divergence.md b/docs/srs/Integration/req-062_quantity-hub-authority-and-divergence.md deleted file mode 100644 index 5a8880b..0000000 --- a/docs/srs/Integration/req-062_quantity-hub-authority-and-divergence.md +++ /dev/null @@ -1,133 +0,0 @@ -# req-062: 수량 hub 정본+로컬 미러 + holding↔broker/hub 불일치 해소 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 보유 수량의 정본을 portfolio-hub 로 두고 슬롯은 미러만 유지하며, 코어 로컬 holding 과 브로커/hub 가 비정상적으로 어긋날 때의 해소 정책을 정의한다. (dcy-020 FR-4/OQ-5/NFR-5) - -## Requirement -- 보유 **수량(asset_quantity)의 정본은 portfolio-hub** 이며, 슬롯은 수량을 정본 소유하지 않고 로컬 best-effort 미러(option D write-back/추정)로 둔다. 실제 청산 시 매도 수량은 **브로커 잔고(`get_balance().hldg_qty`)를 정본**으로 조회한다. 불일치 감지는 `reconcile_positions`(REQ-031)를 확장해 **브로커 잔고 + hub 수량 + 코어 슬롯 holding** 3자를 비교하며, 브로커를 ground truth 로 한다. 해소: A(코어 holding/브로커 0) → 슬롯 `error` + 운영자 resolve; B(코어 flat/브로커 보유, 최위험) → WARN/alert + 격리 + 운영자 결정(자동 청산·자동 편입 금지); C(수량만 불일치) → 미러 갱신 + warning, 상태 변경 없음; D(hub 만 지연) → 기존 write-back. **조용한 자동 교정 금지·표면화**, 실주문은 운영자 게이트(issue #15). 감지 reconcile 은 일일 refresh 와 분리해 더 자주 수행 가능(브로커 잔고+슬롯 상태만 필요). - -## Acceptance Criteria -1. Given 매도 청산, When 매도 수량 결정, Then 로컬 미러가 stale 해도 브로커 잔고 기준으로 정확히 청산된다. **부분 매도(dcy-021)**: 집행 수량 = `min(intent qty, 브로커 hldg_qty)`; intent 가 잔고 이상이면 전량 매도(→inactive). -2. Given 코어 holding 인데 브로커 0, When reconcile, Then 슬롯이 `error` 로 격리되고 safety-sell 대상에서 빠지며 운영자 resolve 가 요구된다. -3. Given 코어 flat 인데 브로커 보유, When reconcile, Then WARN/alert + 격리되고 자동 청산·자동 편입 없이 운영자 결정으로 넘어간다. -4. Given 둘 다 보유, 수량만 불일치, When reconcile, Then 미러만 갱신되고 상태 전이는 없으며 hub 지속 불일치는 warning 으로 노출된다. -5. Given 사이징 주문(증액/부분 매도)이 일부만 체결(dcy-021), When 집행, Then 실제 체결분만 ledger 기록·슬롯 반영하고 미체결 잔량은 core 가 추적/재시도하지 않는다(재수렴은 전략). - -## Impacted Area -- Docs: docs/runtime-flows.md(불일치 해소·write-back) -- Modules: trex_core/application/reconciliation.py, trex_core/application/ledger_outbox.py, trex_core/domain/slot/models.py(qty 미러 의미) -- Tests: tests/application/test_reconciliation.py, tests/application/test_ledger_option_d.py - -## Notes -- 기존 REQ-017/019/029/030/031 와 연계(option D write-back 재사용). 슬롯 qty 정본 소유 제거는 REQ-002/006 change-req 와 정합. -- 출처: dcy-020 FR-4, OQ-5, NFR-5. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-4/OQ-5/NFR-5 로부터 최초 생성(Proposed). 수량 hub 정본+미러·청산=브로커·불일치 해소(A/B/C/D, 표면화·운영자 게이트). - -#### Intent -- 수량 정본 분리와 holding↔broker/hub 불일치 해소 정책을 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: 청산=브로커/A error/B 격리·운영자/C 미러갱신 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md -- Modules: application/reconciliation.py, ledger_outbox.py, domain/slot/models.py -- Tests: test_reconciliation.py, test_ledger_option_d.py - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (REQ-031/017/019 연계·확장) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. REQ-002/006(qty 미러) change-req 정합. - -#### Validation Plan -- 청산 브로커 기준 + A/B/C 불일치 해소 단위·통합 테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-029(M2a 불일치 reconcile)+BAT-030(M2b 매도 수량 브로커 정본) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. - -### CHG-20260610-03 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-021(포지션 사이징) 반영: 부분 매도 집행 수량 = **`min(intent qty, 브로커 hldg_qty)`**(과매도 방지, 잔고 이상이면 전량→inactive). 사이징 주문 부분 체결 시 **실제 체결분만** ledger/슬롯 반영, 미체결 잔량 미추적(전략 재수렴). 기존 "매도 수량=브로커 정본"의 부분 매도 확장. - -#### Intent -- 보유 수량 부분 감액을 브로커 정본 하에 과매도 없이 집행하고, 부분 체결은 실제 거래만 원장에 남긴다(dcy-021 OQ-3, dcy-013 부분 체결 처리 정합). - -#### Acceptance Criteria Delta -- Added: AC-5(사이징 부분 체결 → 실제 체결만 반영·미추적) -- Updated: AC-1 에 부분 매도 집행 수량 = min(intent, 브로커 잔고)·이상이면 전량 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, docs/interface-contract.md (REQ-067 정합) -- Modules: trex_core/application/execution.py(부분 매도 dispatch qty), reconciliation.py(불변) -- Tests: tests/application/test_execution*.py(부분 매도 min·부분 체결) - -#### Delivery Trace Delta -- Batches: dcy-021 신규 배치(예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (부분 매도 집행 수량 정책 신규) -- Existing Implementation Invalidated: no (전량 매도=브로커 정본 기존 동작 보존, 부분은 확장) -- Status Recommendation: revert-to-approved (부분 매도 집행 미구현 — dcy-021 배치에서 구현·검증) -- Follow-up Action: dcy-021 배치 구현 후 Implemented 복귀. - -#### Validation Plan -- 부분 매도 min(intent,잔고)·잔고 이상 전량·부분 체결 실제만 ledger 단위 + 회귀. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음. diff --git a/docs/srs/Integration/req-069_order-timeout-execution.md b/docs/srs/Integration/req-069_order-timeout-execution.md deleted file mode 100644 index c6bd310..0000000 --- a/docs/srs/Integration/req-069_order-timeout-execution.md +++ /dev/null @@ -1,95 +0,0 @@ -# req-069: 주문 timeout 집행 — timeout 초과 시 cancel_order 송신·롤백·부분 보존 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- 집행 루프가 dispatched-pending 슬롯의 `timeout_sec` 를 추적해 초과 시 `broker.cancel_order` 를 송신(best-effort)하고, 취소 확인 후 롤백하며 이미 체결된 부분 수량을 보존한다. 취소-체결 race 는 기존 poll 경로로 자연 수렴한다. (dcy-022 FR-2, FR-4) - -## Requirement -- `_poll` 루프에서 dispatched-pending 슬롯이 `timeout_sec` 를 가지고 경과 시간이 초과하면 `broker.cancel_order(broker_order_id, ...)` 를 best-effort 로 송신한다(송신 실패는 WARNING 로그, 슬롯 상태 불변). 취소 요청 송신 직후 슬롯을 즉시 전이하지 않는다 — 다음 poll 틱에서 `is_terminal_reject`(전량 취소)·`is_partial_cancel`(부분 체결 후 취소) 경로로 수렴한다(기존 롤백/부분 보존 경로 재사용, REQ-013 CHG-03). `timeout_sec` 없는 슬롯은 기존 stale-alert 만(`_check_stale_pending`) 실행(Alert-only, REQ-014 회귀 없음). - -## Acceptance Criteria -1. Given dispatched-pending 슬롯에 `timeout_sec=10` 설정, When 경과 시간 10초 초과, Then 집행 루프가 `broker.cancel_order` 를 송신하고 `timeout_cancelled` 카운터가 증가한다. -2. Given cancel_order 응답 True(브로커 수락), When 다음 poll, Then 슬롯은 `is_terminal_reject` 경로로 rollback 된다(pending_buy→inactive 또는 pending_sell→holding). -3. Given timeout 취소 전 일부 체결(is_partial_cancel), When 다음 poll, Then 기존 `_on_partial_cancel` 경로 — 체결분 원장 기록·잔량 holding 복귀(부분 보존, dcy-013 선례). -4. Given cancel_order 가 False 반환(이미 체결 — 취소-체결 race), When 다음 poll, Then `is_filled` 경로로 정상 체결 처리(임의 롤백 없음). (FR-4 race 안전) -5. Given `timeout_sec` 없는 dispatched-pending 슬롯, When 경과 시간 초과, Then cancel_order 미송신 — 기존 stale_pending WARN 만(Alert-only, REQ-014 회귀 없음). -6. Given cancel_order 송신 실패(예외), When 처리, Then WARNING 로그 + `timeout_cancelled` 미증가, 슬롯 상태 불변(비차단, NFR-2). - -## Impacted Area -- Docs: docs/runtime-flows.md(timeout 취소 흐름 추가) -- Modules: trex_core/application/execution.py(`_poll` timeout cancel, ExecutionResult +timeout_cancelled) -- Tests: tests/application/test_execution.py(timeout→cancel), test_execution_cancel.py(timeout 부분 보존) - -## Notes -- `updated_at` 를 dispatch 시각 proxy 로 사용(mark_dispatched 가 updated_at 갱신 — `_check_stale_pending` 과 동일 방법). -- best-effort 원칙: 송신 실패 시 다음 틱 재시도(threshold 넘으면 재송신). 중복 cancel_order 는 브로커가 멱등 처리(기존 KIS/paper 어댑터 계약). -- 출처: dcy-022 FR-2, FR-4, NFR-2. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-022 FR-2/FR-4 로부터 최초 생성(Proposed). timeout 취소·race 처리·부분 보존. - -#### Intent -- dispatched-pending 주문 timeout 집행 + 취소-체결 race 안전 처리를 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: AC-1~6 (timeout cancel, 전량/부분 취소 수렴, race 안전, 하위호환, 비차단 6개 기준) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Modules: execution.py(_poll), ExecutionResult -- Tests: test_execution, test_execution_cancel - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 BAT-032. - -#### Validation Plan -- timeout → cancel_order 송신 + 롤백 수렴 단위. race(cancel→filled 수렴) 단위. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) - -#### Delivery Trace Delta -- Batches: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032. - -#### Validation Plan -- 승인 후 batch planning 에서 AC 검증 기준으로 사용. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Integration/req-070_slippage-limit-price-execution.md b/docs/srs/Integration/req-070_slippage-limit-price-execution.md deleted file mode 100644 index 5587afc..0000000 --- a/docs/srs/Integration/req-070_slippage-limit-price-execution.md +++ /dev/null @@ -1,143 +0,0 @@ -# req-070: slippage 보호 지정가 집행 — entry_price 기준 실효 지정가 산출 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: project maintainers - -## Intent -- `slippage_pct` 가 지정된 주문을 dispatch 할 때 `entry_price*(1±pct)` 실효 지정가로 제출해 과도한 불리 체결을 막는다. `entry_price` 없으면 무시(순수 시장가는 슬리피지 기준 없음). (dcy-022 FR-3) - -## Requirement -- `_dispatch` 가 슬롯의 `slippage_pct` 를 읽어 실효 지정가를 산출한 뒤 **KRX 호가단위(REQ-083)로 스냅**한다. 방향은 **상한 준수**: 매수 = `floor_to_tick(limit_price * (1 + slippage_pct/100))` (내림 — slippage 상한 미초과), 매도 = `ceil_to_tick(limit_price * (1 - slippage_pct/100))` (올림 — 매도 하한 미초과). band<1tick 엣지에서 미체결을 막기 위해 하한/상한을 보장한다: 매수 실효가 = `max(floor_to_tick(...), ceil_to_tick(limit_price))`, 매도 실효가 = `min(ceil_to_tick(...), floor_to_tick(limit_price))`. `limit_price`(= `entry_price`)가 None 이면 `slippage_pct` 를 무시하고 시장가 주문 제출(기존 동작 보존, 스냅 없음). `slippage_pct` 없는 슬롯은 기존 `limit_price` 직접 사용(회귀 없음). 주식 전용 — ETF/ETN 비주식 호가체계는 범위 밖(dcy-030 OQ-1). - -## Acceptance Criteria -1. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=buy, When dispatch, Then OrderRequest.limit_price = `floor_to_tick(10000*1.01)` = `floor_to_tick(10100)` = 10100 (상한 준수 내림). -2. Given 슬롯에 `limit_price=10000`, `slippage_pct=1.0`, side=sell, When dispatch, Then OrderRequest.limit_price = `ceil_to_tick(10000*0.99)` = `ceil_to_tick(9900)` = 9900 (하한 준수 올림). -3. Given `limit_price=None`(시장가), `slippage_pct=1.0`, When dispatch, Then OrderRequest.limit_price = None(시장가 — slippage·스냅 무시). -4. Given `slippage_pct=None`, `limit_price=10000`, When dispatch, Then OrderRequest.limit_price = 10000(기존 동작, 회귀 없음). -5. Given `slippage_pct=0.0`, When dispatch, Then 실효 지정가 = `floor_to_tick(limit_price)`(0% 슬리피지 = 기준가의 유효 호가). -6. Given 기존 매수/매도 슬롯(slippage_pct 없음), When dispatch, Then 기존 동작과 동일(전체 회귀 없음). -7. Given `limit_price=147700`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(148438.5)` = 148400 (issue #28 케이스, 유효 호가·상한 미초과). -8. Given `limit_price=54500`, `slippage_pct=0.5`, side=buy, When dispatch, Then `floor_to_tick(54772.5)` = 54700 (issue #28 케이스). -9. Given band<1tick (예: `limit_price=147700`, `slippage_pct=0.01`, buy), When dispatch, Then 매수 실효가 = `max(floor_to_tick(147714.77), ceil_to_tick(147700))` ≥ entry 유효 호가 (미체결 방지), 그리고 slippage 상한 미초과. -10. Given 임의 매수/매도 + slippage, When dispatch, Then 결과는 유효 KRX 호가단위 배수이고 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)`. - -## Impacted Area -- Docs: docs/runtime-flows.md(slippage dispatch + 호가단위 스냅 흐름), docs/project-structure.md(신규 tick 유틸 — REQ-083) -- Modules: trex_core/application/execution.py(`_dispatch` slippage 계산 + 호가단위 스냅), REQ-083 tick 유틸 소비 -- Tests: tests/application/test_execution.py(slippage dispatch + 스냅 방향·경계·band0인 매수 한계가가 호가단위를 위반해 KIS `주식주문호가단위 오류`로 전량 거부됨(issue #28). 호가단위 스냅으로 거부를 제거하되, `slippage_pct`를 리스크 상한으로 취급해 스냅이 상한을 넘기지 않도록 매수=내림/매도=올림으로 정정한다. - -#### Acceptance Criteria Delta -- Updated: - - AC-1: 매수 = `floor_to_tick(limit*(1+pct/100))` (상한 준수 내림). 예: `limit=10000,pct=1.0` → `floor_to_tick(10100)=10100`. - - AC-2: 매도 = `ceil_to_tick(limit*(1-pct/100))` (하한 준수 올림). 예: `limit=10000,pct=1.0` → `ceil_to_tick(9900)=9900`. -- Added: - - AC-7: `limit=147700,pct=0.5,buy` → `floor_to_tick(148438.5)=148400` (issue #28 케이스). - - AC-8: `limit=54500,pct=0.5,buy` → `floor_to_tick(54772.5)=54700` (issue #28 케이스). - - AC-9 (band<1tick 하한 보장): 매수 실효가 = `max(floor_to_tick(limit*(1+pct/100)), ceil_to_tick(limit))`; 매도 실효가 = `min(ceil_to_tick(limit*(1-pct/100)), floor_to_tick(limit))`. slippage<1tick이어도 최소 entry 유효 호가 보장. - - AC-10: 스냅 결과는 항상 유효 호가단위 배수이고, 매수 ≤ `limit*(1+pct/100)` / 매도 ≥ `limit*(1-pct/100)` (상한·하한 미초과). -- Removed: - - (없음 — AC-3·4·5·6 시장가/무slippage/0% 회귀 기준 유지) - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md(주문 집행 슬리피지 dispatch에 호가단위 스냅 단계 추가), docs/project-structure.md(신규 tick 유틸 — REQ-083) -- Modules: - - trex_core/application/execution.py(`_dispatch` 스냅 적용), REQ-083 tick 유틸 소비 -- Tests: - - tests/application/test_execution.py(슬리피지 스냅 방향·경계·band`이 쿼리 파라미터로 포함된다. -3. Given portfolio-hub가 네트워크 오류 또는 비-2xx 응답을 반환할 때, When `get_realized_pnl` 호출, Then `PortfolioHubUnavailableError`가 raise 된다. -4. Given `stub_read.py` stub, When `get_realized_pnl` 호출, Then 네트워크 없이 미리 설정한 고정 `RealizedPnlResult`를 반환한다. -5. Given import sweep, When `trex_core/domain`을 검사, Then 신규 포트/어댑터 관련 IO/네트워크 의존이 domain에 없다(NFR: 도메인 순수성 불변). -6. Given `data_completeness_warning: true` 응답, When 파싱, Then `RealizedPnlResult.data_completeness_warning == True`로 반영된다. - -## Impacted Area -- Docs: 없음(리포트 표면 갱신은 REQ-110/111) -- Modules: - - `trex_core/application/portfolio.py` — `PortfolioHubReadPort`에 `get_realized_pnl` 메서드 추가, `RealizedPnlResult`/`RealizedPnlItem` 데이터클래스 신규 정의 - - `trex_core/adapters/portfolio/hub_read.py` — `get_realized_pnl` HTTP 구현 추가 - - `trex_core/adapters/portfolio/stub_read.py` — stub 메서드 추가 -- Tests: - - `tests/adapters/portfolio/test_hub_read.py` — 신규 AC-1~3, AC-6 케이스 추가 - - `tests/adapters/portfolio/test_stub_read.py` — AC-4 stub 케이스 추가 - - `tests/domain/test_domain_import_sweep.py` — AC-5 회귀(기존 sweep 재실행) - -## Notes -- portfolio-hub `GET /v1/portfolios/{id}/pnl/realized` 계약(REQ-040/041): from_dt/to_dt는 **매도 기준** date-filter. cost basis는 기간 밖 매수도 WAC에 포함(멀티데이 정의). 응답 envelope는 portfolio-hub `app/api/v1_portfolios.py:712`·`app/domain/pnl.py`·schemas 기준 — 구현 시 실 계약을 `hub_read.py`의 기존 `get_cost_basis` 패턴(graceful degrade 방식)에 맞춰 구현한다. -- OQ-2(기간 파라미터 기본값 정의), OQ-3(수수료 출처), OQ-4(N콜 vs 집계) 미결 → 이 REQ는 어댑터 계층만 정의. 기간 인자는 caller(REQ-110의 CLI/report 레이어)가 결정해 전달. 어댑터는 전달된 파라미터를 그대로 쿼리에 쓴다. -- `total_realized_pnl`은 `items`의 `realized_pnl` 합산으로 어댑터 내에서 계산(portfolio-hub 응답에 합산 필드가 있으면 재사용, 없으면 클라이언트 합산). -- 기존 `get_cost_basis`(REQ-105 FR-7)는 sync CLI용 포트로 독립 유지 — 이 REQ와 중복 아님. - -## Change Log - -### CHG-20260626-02 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- BAT-052 구현·검증 완료(PR #33, 머지 커밋 81d06d9). confirm-batch-verification + confirm-req-implemented. Approved → Implemented. - -#### Intent -- dcy-035 FR-1 delivery 완료 확인 및 상태 전환. - -#### Acceptance Criteria Delta -- Updated: - - none (모든 AC-1~6 검증 완료, 상태 전환만) -- Added: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: 없음 -- Modules: 변경 없음 (구현 완료 상태 유지) -- Tests: 717 passed / 47 skipped / 0 fail (BAT-052 verification) - -#### Delivery Trace Delta -- Batches: - - BAT-052 (verified, PR #33, 머지 커밋 81d06d9, 2026-06-26) -- Releases: - - none (M3 배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: none - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- dcy-035 FR-1로부터 최초 생성(Proposed). portfolio-hub WAC 실현 PnL read 포트 + HTTP 어댑터 + stub. - -#### Intent -- 운영 리포트(REQ-110)가 WAC 실현손익을 표시하기 위한 하위 의존 어댑터 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~6 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - portfolio.py 포트 확장, hub_read.py/stub_read.py 어댑터 신규 메서드 -- Tests: - - test_hub_read.py, test_stub_read.py, test_domain_import_sweep.py - -#### Delivery Trace Delta -- Batches: - - none (미배치) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/Integration/req-112_portfolio-hub-broker-credential-read-adapter.md b/docs/srs/Integration/req-112_portfolio-hub-broker-credential-read-adapter.md deleted file mode 100644 index 5ecfe5f..0000000 --- a/docs/srs/Integration/req-112_portfolio-hub-broker-credential-read-adapter.md +++ /dev/null @@ -1,102 +0,0 @@ -# req-112: portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: trex-core-dev - -## Intent -- 현재 `PortfolioHubReadAdapter.whoami()`는 `GET /v1/user_info`에서 `daemon_id`만 읽는다. dcy-036 C1에 따라 같은 엔드포인트가 daemon-scoped 단일 `broker` 객체(`account_ref`, `app_key`, `app_secret`, `credential_version`)와 `trading_enabled` 필드를 함께 반환하도록 확장된 portfolio-hub 계약(REQ-049, dcy-010)을 소비해야 한다. -- 이 REQ는 트랜스포트 레이어 — 포트 인터페이스 정의, HTTP 어댑터 구현, 테스트용 stub — 만 다루며, KISConfig 구성 로직(REQ-113)과 로드 게이트(REQ-114)의 기반을 제공한다. - -## Requirement -- `trex_core/application/portfolio.py`에 `BrokerCredentialPayload` 데이터클래스를 추가한다: `account_ref: str`, `app_key: str`, `app_secret: str`, `credential_version: int | None = None`. -- `PortfolioHubReadPort` 프로토콜에 `fetch_broker_credential() -> BrokerCredentialPayload | None` 메서드를 추가한다. - - 반환 값 None: portfolio-hub 응답 `broker: null` (해당 daemon에 인증정보 없음). - - `trading_enabled` 필드도 함께 읽어 반환해야 하므로, 포트 메서드는 `TradingContext` 데이터클래스를 반환한다: `broker: BrokerCredentialPayload | None`, `trading_enabled: bool`. -- `PortfolioHubReadAdapter`의 기존 `whoami()` 메서드를 내부적으로 `GET /v1/user_info`를 공유하는 내부 헬퍼(`_fetch_user_info()`)로 리팩토링하고, `fetch_broker_credential()` 구현이 이를 재사용하도록 한다. 외부 동작은 변경하지 않는다(`whoami()` 시그니처·예외 동작 불변, 회귀 차단 NFR-1). -- `fetch_broker_credential()`은 다음 규칙을 따른다: - - `broker: null` 응답 → `TradingContext(broker=None, trading_enabled=trading_enabled)` 반환. - - `credential_payload: null`(복호화 실패) → `TradingContext(broker=None, trading_enabled=trading_enabled)` 반환(정보 부재로 처리). - - 네트워크 오류 / 비-2xx / JSON 파싱 실패 → `PortfolioHubUnavailableError` raise. - - HTTP 403(`daemon_id` 없는 토큰) → `PortfolioHubUnavailableError` raise. -- `trex_core/adapters/portfolio/stub_read.py`의 stub에도 `fetch_broker_credential()` 결정론 stub을 추가한다. -- `app_secret` 필드는 어댑터 내에서 로그에 직접 출력하지 않는다(NFR-2 보안 — 마스킹). - -## Acceptance Criteria -1. Given daemon_id에 매칭된 broker 인증정보가 있고 `trading_enabled: true`인 `/v1/user_info` 응답, When `fetch_broker_credential()` 호출, Then `TradingContext(broker=BrokerCredentialPayload(account_ref=..., app_key=..., app_secret=...), trading_enabled=True)` 반환. -2. Given `broker: null` 응답(인증정보 없음), When `fetch_broker_credential()` 호출, Then `TradingContext(broker=None, trading_enabled=<응답값>)` 반환 (예외 없음). -3. Given `credential_payload: null`(복호화 실패) 응답, When `fetch_broker_credential()` 호출, Then `TradingContext(broker=None, trading_enabled=<응답값>)` 반환 (예외 없음, 인증정보 부재로 처리). -4. Given 네트워크 오류 또는 비-2xx(403/500 등) 응답, When `fetch_broker_credential()` 호출, Then `PortfolioHubUnavailableError` raise. -5. Given `fetch_broker_credential()` 구현 이후, When `whoami()` 호출, Then 기존 `whoami()` 계약(daemon_id str 반환, PortfolioHubUnavailableError on failure)이 변경되지 않는다 (회귀 없음). -6. Given stub_read.py stub, When `fetch_broker_credential()` 호출, Then 미리 설정한 고정 `TradingContext`를 네트워크 없이 반환한다. -7. Given `fetch_broker_credential()` 구현, When 에러/성공 로그 출력 확인, Then `app_secret` 값이 평문으로 로그에 출력되지 않는다. -8. Given import sweep, When `trex_core/domain`을 검사, Then 신규 포트/어댑터 관련 IO/네트워크 의존이 domain에 없다 (도메인 순수성 불변). - -## Impacted Area -- Docs: 없음 (baseline 문서 갱신은 dcy-036 scope 밖) -- Modules: - - `trex_core/application/portfolio.py` — `BrokerCredentialPayload`, `TradingContext` 데이터클래스 신규 정의; `PortfolioHubReadPort`에 `fetch_broker_credential()` 메서드 추가 - - `trex_core/adapters/portfolio/hub_read.py` — `_fetch_user_info()` 내부 헬퍼 추출, `fetch_broker_credential()` 구현 추가; `whoami()` 동작 보존 - - `trex_core/adapters/portfolio/stub_read.py` — `fetch_broker_credential()` stub 추가 -- Tests: - - `tests/adapters/portfolio/test_hub_read.py` — AC-1~5, AC-7 케이스 추가 - - `tests/adapters/portfolio/test_stub_read.py` — AC-6 stub 케이스 추가 - - `tests/domain/test_domain_import_sweep.py` — AC-8 회귀(기존 sweep 재실행) - -## Notes -- 소비 계약: portfolio-hub `req-049_user-info-daemon-scoped-broker-delivery.md` (Implemented, dcy-010 FR-2). 응답 스키마: `broker: { account_ref, credential_payload: { app_key, app_secret } | null, credential_version, ... } | null` + `trading_enabled: bool`. -- `TradingContext`를 별도 데이터클래스로 도입하는 이유: 포트 메서드가 broker 인증정보와 `trading_enabled`를 한 번의 호출로 함께 반환해야 불필요한 중복 HTTP 호출을 피할 수 있기 때문(원자적 읽기). -- OQ-4(그날-시작 로드 실패 알람 trex-notifier 연동): Deferred — 알람 발신은 REQ-114 Notes에서 다룬다. -- 선행 의존: portfolio-hub dcy-010 / REQ-049 배포 완료 여부에 따라 실 통합 테스트 가능 시점이 달라진다. CI 단계에서는 stub 기반 단위 테스트로 AC-1~8 검증. -- Source: dcy-036 C1, FR-1, dcy-010 OQ-2 Answered. - -## Change Log - -### CHG-20260628-01 -- Date: 2026-06-28 -- Author: trex-core-dev - -#### Change Summary -- 신규 생성 (Proposed) — dcy-036 C1 산출물. - -#### Intent -- dcy-036 FR-1(그날-시작 portfolio-hub `/v1/user_info`에서 KIS 인증정보 fetch)의 트랜스포트 계층을 REQ로 정규화한다. KISConfig 구성(REQ-113) 및 로드 게이트(REQ-114)의 선행 의존. - -#### Acceptance Criteria Delta -- Added: - - AC-1~8 전체 (신규 초안) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - `portfolio.py` 포트 확장 (BrokerCredentialPayload, TradingContext, fetch_broker_credential) - - `hub_read.py` 어댑터 확장 (_fetch_user_info 헬퍼, fetch_broker_credential 구현) - - `stub_read.py` stub 확장 -- Tests: - - `test_hub_read.py`, `test_stub_read.py`, `test_domain_import_sweep.py` - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 대기) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 승인 게이트 후 REQ-112~115 일괄 batch 배정 검토. - -#### Validation Plan -- pytest로 AC-1~8 각 케이스 확인. app_secret 마스킹(AC-7)은 로그 캡처 검증. domain sweep(AC-8)은 기존 test_domain_import_sweep.py 재실행. - -### CHG-20260628-02 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req: `Proposed` → `Approved`. dcy-036 FR-1/C1 추적. TradingContext 원자 read(broker+trading_enabled 1회 호출) 설계 타당 — /v1/user_info 단일 응답에 정합. whoami 회귀보존(AC-5)·도메인 순수성(AC-8)·app_secret 마스킹(AC-7) 적절. blocker 없음. 선행 의존 portfolio-hub REQ-049(머지완료). - -### CHG-20260628-03 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req-implemented: `Approved` → `Implemented`. 근거: BAT-053 PR #34 머지(merge commit 01655ab). fetch 포트(`fetch_broker_credential()`, `BrokerCredentialPayload`, `TradingContext`) + HTTP 어댑터 + stub AC-1~8 전 케이스 pytest 검증 완료. QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀. diff --git a/docs/srs/Integration/req-114_daystart-load-gate-fail-closed-entry.md b/docs/srs/Integration/req-114_daystart-load-gate-fail-closed-entry.md deleted file mode 100644 index c71c745..0000000 --- a/docs/srs/Integration/req-114_daystart-load-gate-fail-closed-entry.md +++ /dev/null @@ -1,99 +0,0 @@ -# req-114: 그날-시작 로드 게이트 — fail-closed 신규 진입 차단 + 알람 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: trex-core-dev - -## Intent -- dcy-036 C2/FR-2에 따라, 그날-시작 체크포인트(단기: 07:30 KST 스냅샷, 중장기: dcy-027 운영 프로파일 주기)에서 portfolio-hub가 불가하거나 broker 인증정보 부재(`broker: null`) 또는 `trading_enabled: false`이면 그날 **신규 진입을 개시하지 않는다(fail-closed)**. 사유를 로깅하고 운영자 알람을 발신한다. -- "그날 고정" 원칙: 로드 성공 후 그날 인증정보는 캐시로 유지되며, 장중 portfolio-hub 복구 시에도 그날 재fetch하지 않는다(결정론 — REQ-115 담당). - -## Requirement -- **그날-시작 체크포인트 후크**: `SnapshotScheduler.run_once()`(단기 07:30 KST 스냅샷 트리거)의 `credential_fetch_enabled = true` 경로에서, 스냅샷 전 `fetch_broker_credential()` (REQ-112)를 호출해 `TradingContext`를 획득한다. 중장기 인스턴스는 dcy-027 운영 프로파일 리밸런싱 주기의 동등한 후크 지점에서 동일 로직을 수행한다. -- **fail-closed 조건**: 다음 중 하나라도 해당하면 그날 `entry_allowed = False` 상태를 설정한다: - 1. `PortfolioHubUnavailableError` (네트워크 오류 / 비-2xx / 403) - 2. `TradingContext.broker is None` (인증정보 부재) - 3. `TradingContext.trading_enabled is False` -- **진입 차단**: `entry_allowed = False` 상태에서 BuyIntent(신규 진입 의도)는 admission 단계에서 거부된다. 이미 진행 중인 주문(DISPATCHED 슬롯)과 청산(SellIntent) 경로는 차단하지 않는다. -- **사유 로깅**: fail-closed 진입 시 ERROR 레벨로 사유(portfolio-hub 불가 / broker null / trading_enabled false)와 인스턴스 정보를 로깅한다. app_secret은 로그에 포함하지 않는다. -- **알람 발신**: fail-closed 진입 시 CRITICAL 알람을 `AlarmEmitter`를 통해 발신한다(`trex.alerts` exchange, REQ-099/100). 알람 코드: `DAY_START_LOAD_FAILED`. 알람 페이로드에는 사유 코드와 인스턴스 키를 포함한다. -- **성공 로깅**: 로드 성공 시 INFO 레벨로 fetch 소스, account_ref(app_secret 제외), trading_enabled를 기록한다. - -## Acceptance Criteria -1. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `PortfolioHubUnavailableError` 발생, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람(`DAY_START_LOAD_FAILED`) 발신. -2. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.broker is None`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신. -3. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.trading_enabled = False`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신. -4. Given `entry_allowed = False` 상태, When BuyIntent 수신, Then BuyIntent가 admission 단계에서 거부된다 (신규 진입 차단). -5. Given `entry_allowed = False` 상태, When SellIntent 수신, Then SellIntent는 차단되지 않고 정상 처리된다 (청산 경로 영향 없음). -6. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트 성공(`broker` 있음 + `trading_enabled = True`), When 체크포인트 실행, Then `entry_allowed = True` 설정 + INFO 로그(account_ref 포함, app_secret 제외). -7. Given `credential_fetch_enabled = false`, When 그날-시작 체크포인트 실행, Then `fetch_broker_credential()` 호출 없이 기존 스냅샷 로직만 수행된다 (회귀 없음, opt-in 보존). -8. Given 알람 발신 성공, When `trex.alerts` exchange 메시지 확인, Then `DAY_START_LOAD_FAILED` 코드와 instance_key를 포함한 알람 envelope가 존재한다. -9. Given fail-closed 후 `entry_allowed = False` 상태에서 portfolio-hub가 복구됨, When 그날 중 재fetch 없이 운행, Then `entry_allowed` 상태는 그날 고정(재fetch로 자동 해제 안 됨) — 다음 그날-시작 체크포인트까지 유지. - -## Impacted Area -- Docs: 없음 (baseline 문서 갱신은 별도 scope) -- Modules: - - `trex_core/application/snapshot_scheduler.py` — `run_once()` 또는 그날-시작 후크 지점에 `fetch_broker_credential()` 호출 + `entry_allowed` 상태 설정 + 알람 발신 추가 - - `trex_core/application/execution.py` (또는 IntentService) — BuyIntent admission에 `entry_allowed` 게이트 추가 - - `trex_core/application/alarm_emitter.py` — `DAY_START_LOAD_FAILED` 알람 코드 추가(REQ-100 패턴 재사용) -- Tests: - - `tests/application/test_snapshot_scheduler.py` — AC-1~3, AC-6~9 케이스 추가 (stub fetch_broker_credential + AlarmEmitter mock) - - `tests/application/test_execution.py` (또는 IntentService 테스트) — AC-4~5 케이스 추가 - -## Notes -- OQ-4(그날-시작 로드 실패 알람 trex-notifier 연동): **Deferred** — 이 REQ는 `trex.alerts` exchange 발신(REQ-099/100 계약)까지만 정의한다. trex-notifier(dcy-033) 연동은 별도 REQ/Discovery로 추적한다. 알람 등급은 CRITICAL(거래 개시 불가). -- `entry_allowed` 상태 저장소: in-process 상태 변수(process-local)로 충분하다. 재시작 시 그날-시작 체크포인트가 재실행되므로 내구성 저장소 불필요. -- 중장기 인스턴스의 "그날-시작 체크포인트" 시점은 dcy-027 운영 프로파일 리밸런싱 주기의 구체적 후크 위치에 의존한다. 구현 단계에서 확인 후 조정. -- AC-9(그날 고정 상태 불변성): 장중 재fetch 없음이 REQ-115의 보완 요건이기도 하다. 이 REQ는 "진입 차단 상태의 그날 고정"만 명시하고, "장중 장애 후 인증정보 캐시 지속 운행"은 REQ-115가 담당. -- Source: dcy-036 C2, FR-2, OQ-1 Answered (단기 07:30 / 중장기 프로파일 주기), OQ-2 Answered (그날 고정). - -## Change Log - -### CHG-20260628-01 -- Date: 2026-06-28 -- Author: trex-core-dev - -#### Change Summary -- 신규 생성 (Proposed) — dcy-036 C2/FR-2 산출물. - -#### Intent -- dcy-036 FR-2(그날-시작 로드 시점 fail-closed 진입 차단 + 알람)를 Integration REQ로 정규화한다. - -#### Acceptance Criteria Delta -- Added: - - AC-1~9 전체 (신규 초안) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - `snapshot_scheduler.py` 그날-시작 후크 + entry_allowed 상태 + 알람 - - `execution.py` / IntentService BuyIntent admission 게이트 - - `alarm_emitter.py` 코드 추가 -- Tests: - - `test_snapshot_scheduler.py`, `test_execution.py` - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 대기) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-112~115 일괄 batch 배정 검토. - -#### Validation Plan -- pytest로 AC-1~9 각 케이스 확인. stub `fetch_broker_credential`과 AlarmEmitter mock으로 I/O 격리. AC-4/5(차단 범위 경계: 매수 차단 / 매도 통과)가 가장 중요한 케이스. - -### CHG-20260628-02 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req: `Proposed` → `Approved`. dcy-036 FR-2/OQ-1/OQ-2 추적. **진입 fail-closed(BuyIntent 차단) + 청산·DISPATCHED 통과(AC-4/5)** 비대칭이 사장님 fail-safe 결정 정합 — 자본 갇힘 방지. CRITICAL 알람 기존 trex.alerts(REQ-099/100) 재사용·그날 고정(AC-9) 적절. OQ-4(trex-notifier 연동) Deferred 정당. blocker 없음. - -### CHG-20260628-03 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req-implemented: `Approved` → `Implemented`. 근거: BAT-053 PR #34 머지(merge commit 01655ab). fail-closed 게이트 AC-1~9 pytest 검증 완료 — BuyIntent 차단/SellIntent 통과 비대칭, CRITICAL 알람(`DAY_START_LOAD_FAILED`), opt-in off 비활성(AC-7), 그날 고정(AC-9) 확인. QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀. diff --git a/docs/srs/Integration/req-117_order-request-venue-field.md b/docs/srs/Integration/req-117_order-request-venue-field.md deleted file mode 100644 index 822c86f..0000000 --- a/docs/srs/Integration/req-117_order-request-venue-field.md +++ /dev/null @@ -1,119 +0,0 @@ -# req-117: OrderRequest venue additive 필드 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: trex-core-dev - -## Intent - -- KIS NXT(대체거래소) 주문 지원을 위해 `OrderRequest` DTO에 `venue: str = "KRX"` 필드를 additive 추가한다. -- 기본값 `"KRX"`로 미지정 시 기존 orb/tsl 경로와 wire 의미론이 byte-동일하게 유지되어 하위 호환을 보장한다. -- 포트-어댑터 경계에서 venue 의미론을 확립하고, 상위 intent→venue 전달(tvr 팬-인 batch)이 이 계층 설계를 건드리지 않아도 되도록 확장 경로를 열어 둔다. - -## Requirement - -- `trex_core/application/broker_port.py`의 `OrderRequest` dataclass에 `venue: str = "KRX"` 필드를 추가한다. -- 필드는 frozen dataclass의 마지막 옵셔널 위치에 추가하여 기존 위치 인수 호출을 깨지 않는다. -- venue 값 의미론: `"KRX"` = 정규 KRX 거래소, `"NXT"` = 대체거래소(연장세션 포함), `"SOR"` = 최선집행(현재 비권고 — OQ-1 잔여 미확인). -- 이 필드는 포트 계층 DTO로서 venue 명시를 강제하지 않는다(기본값 `"KRX"` 허용). KIS 어댑터(REQ-118)가 이 값을 `EXCG_ID_DVSN_CD`로 변환한다. -- paper broker stub은 `venue` 필드를 수신하되 무시해도 무방하다(venue는 KIS-specific 개념, paper는 교환소 개념 없음). 단, `OrderRequest` 수신 시 venue 필드를 읽어 로깅/audit에 포함할 수 있다. - -## Acceptance Criteria - -1. Given `OrderRequest(slot_id="s1", side="buy", symbol="005930", qty=1)`로 venue 미지정 시, When dataclass를 생성하면, Then `req.venue == "KRX"`이다. -2. Given `OrderRequest(..., venue="NXT")`로 NXT 지정 시, When dataclass를 생성하면, Then `req.venue == "NXT"`이다. -3. Given 기존 orb/tsl 코드가 `OrderRequest` 생성 시 `venue`를 지정하지 않는 경우, When 단위 테스트를 실행하면, Then 기존 테스트가 모두 통과한다(회귀 없음). -4. Given `OrderRequest` frozen dataclass 불변식, When `venue` 필드가 추가된 후에도, Then 기존 위치 인수(`slot_id`, `side`, `symbol`, `qty`) 순서가 변경되지 않고 기존 코드가 재컴파일 없이 동작한다. -5. Given paper broker stub이 `submit_order(req: OrderRequest)` 를 수신할 때, When `req.venue`가 `"NXT"`이든 `"KRX"`이든, Then paper stub은 주문을 정상 수락한다(venue 값에 무관하게 기존 동작 유지). - -## Impacted Area - -- Docs: `docs/interface-contract.md` — OrderRequest DTO 스키마 섹션 venue 필드 추가 필요. -- Modules: - - `trex_core/application/broker_port.py` — `OrderRequest.venue` 필드 추가(additive). - - `trex_core/adapters/broker/paper.py` — venue 수신 허용(무시 또는 로깅), 기존 동작 유지. - - 호출 측(execution, safety_sell 등)은 venue 미지정으로 자동 KRX 기본값 적용 — 변경 불필요. -- Tests: - - `tests/` — 기존 `OrderRequest` 생성 테스트 회귀 확인. - - 신규: venue 기본값 및 NXT 지정 단위 테스트 추가. - -## Notes - -- **wire 계약 영향 없음**: `OrderRequest`는 AMQP wire 위를 직접 흐르지 않는다. intent 메시지 계약(wire)에서 venue를 전달하는 것은 tvr 팬-인 batch(후속)의 범위이며, 본 REQ는 브로커 어댑터 계층 DTO까지만 다룬다. -- `"SOR"` 값은 정의만 하고 현재 KIS 운영 권고 대상 아님(OQ-1 잔여: SOR 체결거래소 식별·EXCG 미전송 기본값 미확인). SOR 동작은 REQ-118 AC에서 별도 처리하지 않는다. -- frozen dataclass 확장 시 기존 `field(default_factory=...)` 패턴이 없으므로 기본값 있는 단순 필드로 충분하다. -- 출처 Discovery: trex-tvr-v1 `dcy-002_20260704_execution-cadence-slot-fetch.md` FR-6, confirmed 2026-07-06. -- 실측 근거: 2026-07-06 실전 계좌 NXT 주문 rt_cd=0 접수·취소 rt_cd=0 확인. - -## Change Log - -### CHG-20260707-01 -- Date: 2026-07-07 -- Author: 팀장(Claude) — confirm-req-implemented - -#### Change Summary -- Approved → **Implemented**: bat-056 구현 완료. 독립 QC **opus** PASS-WITH-NOTES(blocker 0) — 794 passed, 기존 주문 페이로드 불변(EXCG=KRX 추가만, 키 집합 잠금), 전 호출부 정합. PR #38 사장님 결재·머지(079fc04, 2026-07-07) = confirm-batch-verification 완료. -- **배포 게이트(NXT 라이브 사용 전)**: 실전 소액 스모크(KRX+NXT 각 1건)로 poll_fill 구 TR의 NXT 체결 포함 여부 실측(AC-4 OI-1 해소). 미포함 시 신 TR(TTTC0081R) 전환 별도 batch. - -#### Delivery Trace Delta -- Batches: bat-056_20260706_kis-broker-venue (merged, PR #38) - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Implemented - -### CHG-20260706-02 -- Date: 2026-07-06 -- Author: 팀장(Claude) — confirm-req - -#### Change Summary -- Proposed → Approved: 팀장 검토·승인. dcy-002(tvr, confirmed 2026-07-06) NXT venue 트랙. 실측 근거(NXT 주문/취소 rt_cd=0, 2026-07-06) 확보 상태. 고위험(라이브 주문 경로) — worktree+PR+QC(opus) 플로. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Approved - -### CHG-20260706-01 - -- Date: 2026-07-06 -- Author: trex-core-dev - -#### Change Summary - -- REQ-117 신규 생성(Proposed). trex-tvr-v1 dcy-002 FR-6 기반 OrderRequest venue additive 필드. - -#### Intent - -- NXT 거래소구분 지원을 위한 포트 계층 DTO 확장. additive 설계로 기존 orb/tsl 무영향. - -#### Acceptance Criteria Delta - -- Added: AC-1~5 신규 작성. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta - -- Docs: docs/interface-contract.md venue 필드 섹션 추가 예정. -- Modules: broker_port.py OrderRequest 확장, paper broker 수신 허용. -- Tests: 기존 회귀 + venue 기본값/NXT 신규 단위 테스트. - -#### Delivery Trace Delta - -- Batches: 미할당(suggest-batch-reqs 대기). -- Releases: 미할당. - -#### Revalidation Impact - -- Reverification Needed: no (신규 Proposed, 기존 구현 없음) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 suggest-batch-reqs → REQ-118과 동일 batch로 묶을 것. - -#### Validation Plan - -- pytest 전체 통과(기존 회귀 없음) + venue 필드 단위 테스트 신규 통과. diff --git a/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md b/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md deleted file mode 100644 index 89c9bcb..0000000 --- a/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md +++ /dev/null @@ -1,211 +0,0 @@ -# req-118: KIS 브로커 어댑터 venue 파라미터화 (EXCG_ID_DVSN_CD) - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: trex-core-dev - -## Intent - -- KIS 브로커 어댑터(`adapters/broker/kis.py`)에서 주문 거래소구분(`EXCG_ID_DVSN_CD`)을 `OrderRequest.venue`로부터 파라미터화하여 NXT(대체거래소) 주문·취소를 지원한다. -- 동시에 `submit_order`에서 기존 EXCG 미전송 관행을 `"KRX"` 명시 전송으로 변경하여 서버 기본값 의존을 제거한다(동작 고정, 실측상 서버 기본값=KRX로 동일하나 명시화가 더 견고함). -- `poll_fill`은 신 TR(`TTTC0081R`/`VTTC0081R`)과 `EXCG_ID_DVSN_CD=ALL`을 - 사용하여 KRX·NXT 체결 조회를 보장한다. -- `get_current_price`의 시장구분(`FID_COND_MRKT_DIV_CODE`) `"J"` 하드코딩을 파라미터로 전환한다(기본 `"J"`, `"UN"` 옵션). -- 이 모든 변경은 venue 미지정(기본 KRX) 경로에서 orb/tsl 기존 동작과 byte-동일하게 유지되는 additive 설계다. - -## Requirement - -1. `submit_order`: `body`에 `"EXCG_ID_DVSN_CD": req.venue` 항상 명시 전송한다. `req.venue` 기본값은 `"KRX"`이므로 venue 미지정 호출에서도 `EXCG_ID_DVSN_CD=KRX`가 전송된다. -2. `cancel_order`: 취소 요청 `body`에 `"EXCG_ID_DVSN_CD": <원주문 venue>` 를 전송한다. 원주문 venue는 cancel_order 시그니처에 `venue: str = "KRX"` 파라미터를 추가하거나, 호출자가 원주문 venue를 보존·전달하는 방식으로 구현한다(설계 방식은 구현 batch에 위임 — 의미론 확정: 취소는 원주문 venue와 동일한 EXCG를 사용해야 한다). -3. `poll_fill`: 일반 주문과 결과불명 주문 조회 모두 실전 `TTTC0081R`, 모의 - `VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달하여 KRX·NXT 체결을 - 하나의 조회로 확인한다. -4. `get_current_price`: 시그니처에 `market: str = "J"` 파라미터를 추가하고, `FID_COND_MRKT_DIV_CODE`를 해당 값으로 전달한다. 기본 `"J"` 유지로 기존 safety-sell 경로 무영향. 호출자가 `"UN"` 지정 가능. -5. orb/tsl 무영향: venue 미지정 경로에서 submit_order 페이로드는 기존 대비 `EXCG_ID_DVSN_CD="KRX"` 필드 추가만 다르며, 주문 수락 동작이 동일함을 회귀 테스트로 확인한다. - -## Acceptance Criteria - -1. Given `OrderRequest(..., venue="KRX")`(또는 venue 미지정 기본값)로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "KRX"`가 포함된다. -2. Given `OrderRequest(..., venue="NXT")`로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다. -3. Given NXT 주문의 broker_order_id와 `venue="NXT"`로 `cancel_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다. -4. Given 일반 주문번호 또는 결과불명 주문 메타데이터를 조회할 때, When - `inquire-daily-ccld` 요청을 생성하면, Then 실전은 `TTTC0081R`, 모의는 - `VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달한다. -5. Given `get_current_price(symbol, market="J")` 기본 호출(또는 market 미지정) 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=J`가 전달된다(기존 동작 동일). -6. Given `get_current_price(symbol, market="UN")` 호출 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=UN`이 전달된다. -7. Given 기존 orb/tsl가 venue 미지정으로 `submit_order`를 호출하는 경로, When 단위 테스트를 실행하면, Then 기존 submit_order 관련 테스트 전체가 통과한다(회귀 없음). 페이로드 차이는 `EXCG_ID_DVSN_CD="KRX"` 추가뿐임을 fixture 검사로 확인한다. -8. Given paper broker stub이 `cancel_order` 시그니처 변경(venue 파라미터 추가)을 반영한 후, When paper broker로 cancel_order 호출 시, Then 기존 취소 동작이 유지된다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — KIS 브로커 어댑터 EXCG 전송 명세 추가. - - `docs/runtime-flows.md` — 주문 집행 흐름 중 venue 전달 경로 명시. -- Modules: - - `trex_core/adapters/broker/kis.py` — `submit_order`, `cancel_order`, `poll_fill`, `get_current_price` 변경. - - `trex_core/application/broker_port.py` — `BrokerPort.cancel_order` 시그니처 venue 파라미터 추가(또는 호출자 보존 방식에 따라 설계 batch에서 결정). - - `trex_core/adapters/broker/paper.py` — cancel_order 시그니처 정합 유지. -- Tests: - - `tests/adapters/broker/` — submit_order EXCG 필드 포함 fixture, cancel_order venue 전달 검증, get_current_price market 파라미터 검증. - - `tests/` — 전체 회귀(기존 venue 미지정 경로 pass 확인). - -## Notes - -- **고위험 경로**: `submit_order`·`cancel_order`·`poll_fill`은 라이브 주문 직결 경로다. worktree+PR+QC(opus) 플로 필수. -- **cancel_order 설계 위임**: 취소 시 venue 전달 방식(cancel_order 시그니처 추가 vs. execution context 보존)은 구현 batch design에서 확정한다. 본 REQ는 의미론만 확정한다 — "취소 요청은 원주문 venue와 동일한 EXCG_ID_DVSN_CD를 사용해야 한다." -- **poll_fill TR 계약**: KIS 공식 `주식일별주문체결조회[v1_국내주식-005]` - 기준 3개월 이내 실전은 `TTTC0081R`, 모의는 `VTTC0081R`이며 거래소 - 구분 `ALL`은 전체 거래소 조회다. 구형 `TTTC8001R`/`VTTC8001R`은 - 사전고지 없이 중단될 수 있으므로 사용하지 않는다. -- **모의(VTS) 범위**: 모의투자는 KRX만 제공되지만 동일 신형 - `VTTC0081R` 요청 형식과 `ALL` 파라미터를 유지해 실전·가상 계약을 - 분기시키지 않는다. -- **토큰 캐시**: 실측(2026-07-06)에서 토큰 재요청 1분 rate-limit 확인. 현행 `kis.py` 토큰 캐시 방식과 정합 — 신 TR 전환 시에도 토큰 캐시 재사용 경로를 유지해야 한다. -- **SOR**: `"SOR"` 값 전달은 설계 경로에서 차단하지 않되, 현재 권고하지 않는다(체결거래소 식별·정정취소 EXCG 일치 보장이 미확인). -- 출처 Discovery: trex-tvr-v1 `dcy-002_20260704_execution-cadence-slot-fetch.md` FR-6, confirmed 2026-07-06. -- 실측 근거: 2026-07-06 실전 계좌 NXT 주문(TTTC0012U + EXCG=NXT) rt_cd=0, 취소(TTTC0013U + EXCG=NXT) rt_cd=0·잔여 0건 확인. -- 선행 REQ: REQ-117(OrderRequest.venue 필드) 구현 완료 후 본 REQ 구현 가능. - -## Change Log - -### CHG-20260729-02 -- Date: 2026-07-29 -- Author: Codex — confirm-req-implemented - -#### Change Summary -- Approved → Implemented: BAT-063이 release-candidate가 되었고 변경으로 무효화된 - AC-4를 포함해 REQ-118 전체 수용 기준이 다시 검증됐다. - -#### Delivery Trace Delta -- Batches: BAT-063 release-candidate -- Verification: - `docs/batches/bat-063_20260729_kis-daily-fill-all/verification.md` - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented - -#### Validation Plan -- KIS broker 집중검사 59 passed/1 skipped, Core 전체 922 passed/51 skipped, - Ruff PASS. -- 공용 가상 주문 인증 1~7단계 PASS, 미체결·포지션·중복 주문 0, 원장 일치. -- 전략 신호·가격·수량 정책 변경이 없어 수익률 BT는 비대상이다. -- 저장소 FROZEN 규칙 파일과 변경 범위의 Quick Fix/TODO/FIXME/HACK 잔여 없음. - -### CHG-20260729-01 -- Date: 2026-07-29 -- Author: Codex — 형님 승인 `남은 작업 진행하자` - -#### Change Summary -- KIS 공식 신형 일별주문체결조회 계약을 Core가 직접 소유하도록 - `TTTC0081R`/`VTTC0081R`과 `EXCG_ID_DVSN_CD=ALL`을 `poll_fill` 및 - 결과불명 주문 조회에 적용한다. - -#### Intent -- NXT 체결 조회를 Docker sidecar의 거래 ID 변환에 의존하지 않고 Core KIS - transport에서 완결해 주문·체결·취소 거래시장 정본을 Core 하나로 단일화한다. - -#### Acceptance Criteria Delta -- Updated: AC-4를 공식 명세 확인 단계에서 신형 거래 ID와 전체 거래소 조회를 - 직접 검증하는 실행 기준으로 변경한다. -- Added: 일반 주문과 결과불명 주문 조회가 같은 신형 계약을 사용하는 회귀검사. -- Removed: 구형 거래 ID 유지와 sidecar 변환 의존. - -#### Impacted Area Delta -- Modules: `trex_core/adapters/broker/kis.py` -- Tests: `tests/adapters/broker/test_kis.py`, - `tests/adapters/broker/test_venue.py` -- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`, - `docs/batches/bat-063_20260729_kis-daily-fill-all/` - -#### Delivery Trace Delta -- Batches: BAT-063 신규 -- Releases: 미할당 - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: yes — 구형 거래 ID와 NXT 포함 여부 미확인 - 상태는 최신 KIS 공식 계약 및 sidecar 제거 목표와 맞지 않는다. -- Status Recommendation: Implemented → Approved, BAT-063 검증 후 Implemented 복귀 - -#### Validation Plan -- 일반·결과불명 `poll_fill` 요청이 실전 `TTTC0081R`, 모의 - `VTTC0081R`, `EXCG_ID_DVSN_CD=ALL`을 사용하는 단위검사를 통과한다. -- 기존 KRX/NXT 주문·취소·결과불명·재시작 회귀와 Core 전체 테스트를 통과한다. -- 가상거래소 `logic`부터 `ledger_reconcile`까지 1~7단계 인증을 통과한다. -- FROZEN 영향: 저장소 내 FROZEN 목록이 없으며 공개 포트 시그니처는 변경하지 않는다. -- BT 영향: 전략 신호·가격·수량·자본 정책은 변경하지 않으므로 수익률 BT 대상이 - 아니다. 주문 수명주기 가상 인증으로 회귀를 검증한다. - -### CHG-20260707-01 -- Date: 2026-07-07 -- Author: 팀장(Claude) — confirm-req-implemented - -#### Change Summary -- Approved → **Implemented**: bat-056 구현 완료. 독립 QC **opus** PASS-WITH-NOTES(blocker 0) — 794 passed, 기존 주문 페이로드 불변(EXCG=KRX 추가만, 키 집합 잠금), 전 호출부 정합. PR #38 사장님 결재·머지(079fc04, 2026-07-07) = confirm-batch-verification 완료. -- **배포 게이트(NXT 라이브 사용 전)**: 실전 소액 스모크(KRX+NXT 각 1건)로 poll_fill 구 TR의 NXT 체결 포함 여부 실측(AC-4 OI-1 해소). 미포함 시 신 TR(TTTC0081R) 전환 별도 batch. - -#### Delivery Trace Delta -- Batches: bat-056_20260706_kis-broker-venue (merged, PR #38) - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Implemented - -### CHG-20260706-02 -- Date: 2026-07-06 -- Author: 팀장(Claude) — confirm-req - -#### Change Summary -- Proposed → Approved: 팀장 검토·승인. dcy-002(tvr, confirmed 2026-07-06) NXT venue 트랙. 실측 근거(NXT 주문/취소 rt_cd=0, 2026-07-06) 확보 상태. 고위험(라이브 주문 경로) — worktree+PR+QC(opus) 플로. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Approved - -### CHG-20260706-01 - -- Date: 2026-07-06 -- Author: trex-core-dev - -#### Change Summary - -- REQ-118 신규 생성(Proposed). trex-tvr-v1 dcy-002 FR-6 기반 KIS 브로커 어댑터 venue 파라미터화. - -#### Intent - -- NXT 거래소구분 지원을 위한 KIS 어댑터 EXCG_ID_DVSN_CD 전송 구현. 기존 orb/tsl 하위 호환 유지. - -#### Acceptance Criteria Delta - -- Added: AC-1~8 신규 작성. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md venue 경로 명세 추가 예정. -- Modules: kis.py 4개 메서드 변경, broker_port.py cancel_order 시그니처 정합, paper.py 정합. -- Tests: 신규 EXCG 필드 검증 + 전체 회귀. - -#### Delivery Trace Delta - -- Batches: 미할당(suggest-batch-reqs 대기). -- Releases: 미할당. - -#### Revalidation Impact - -- Reverification Needed: no (신규 Proposed, 기존 구현 없음) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: REQ-117과 동일 batch 묶기 권고. QC opus 지정(고위험 주문 경로). - -#### Validation Plan - -- pytest 전체 통과(기존 회귀 없음) + 신규 AC-1~8 단위 테스트 통과. -- 배포 전 실전 소액 스모크(KRX + NXT 각 1건) 운영 협의. diff --git a/docs/srs/Integration/req-119_fetched-credential-broker-provider-cli-parity.md b/docs/srs/Integration/req-119_fetched-credential-broker-provider-cli-parity.md deleted file mode 100644 index 604fe29..0000000 --- a/docs/srs/Integration/req-119_fetched-credential-broker-provider-cli-parity.md +++ /dev/null @@ -1,127 +0,0 @@ -# REQ-119: fetched credential 공통 broker provider와 operator CLI truth 정합 - -- Status: Implemented -- Type: Integration -- Priority: High -- Owner: trex-core-dev - -## Intent -- `credential_fetch_enabled=true` 인스턴스에서 daemon과 operator `sync`/`verify`가 서로 다른 credential source로 브로커를 생성하면, 같은 인스턴스의 broker truth를 신뢰할 수 없다. -- daemon과 모든 operator broker-read 경로가 동일한 portfolio-hub fetched credential authority를 해석하도록 하되, 자격증명·계좌 식별 비밀은 노출하지 않는다. - -## Requirement -- 시스템은 fetched credential 모드에서 daemon, `sync`, `verify` 및 같은 broker-read provider를 사용하는 operator 경로가 공통 broker provider를 통해 동일한 portfolio-hub credential source를 해석하도록 해야 한다. -- fetch 실패 시 명시적으로 활성화된 break-glass 정책이 없는 한 env/config credential로 조용히 fallback하지 않아야 하며, `credential_fetch_enabled=false` 인스턴스의 기존 env-only 동작은 유지해야 한다. -- provider가 source/account identity 정합 증거를 노출해야 할 경우 비밀이 아닌 stable label 또는 마스킹된 fingerprint만 사용해야 한다. - -## Acceptance Criteria -1. Given `credential_fetch_enabled=true`와 mocked portfolio-hub credential 응답, When daemon-equivalent broker 구성과 `sync --dry-run` 및 `verify`의 broker-read 구성을 실행하면, Then 모두 같은 fetched source identity(안전한 label/fingerprint)와 같은 broker holding set을 사용한다. -2. Given fetched credential 모드에서 portfolio-hub fetch가 실패하고 break-glass가 비활성화됨, When operator broker-read 경로를 실행하면, Then env/config credential fallback이나 broker qty 기반 판단 없이 안전한 오류로 중단한다. -3. Given fetched credential 모드에서 명시적인 break-glass 정책이 활성화됨, When fetch가 실패하면, Then fallback의 활성화 근거와 source mode는 비밀 없이 추적 가능하고, 명시적 정책이 없을 때와 구별된다. -4. Given `credential_fetch_enabled=false` env-only instance, When daemon과 기존 operator broker-read 경로를 실행하면, Then fetched credential 요청 없이 기존 동작 및 CLI 호환성이 유지된다. -5. Given CLI 출력·구조화 로그·테스트 fixture, When source 정합 검증을 수행하면, Then app key, app secret, access token, raw credential payload 및 raw account number가 포함되지 않는다. - -## Impacted Area -- Docs: `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/operations.md`, `docs/srs/Integration/req-112_portfolio-hub-broker-credential-read-adapter.md`, `docs/srs/Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md` -- Modules: `trex_core/runtime/daemon.py`, `trex_core/runtime/cli.py`, `trex_core/application/snapshot_scheduler.py` 또는 공통 broker provider/factory, portfolio-hub credential read adapter -- Tests: daemon/provider/CLI integration regression; fetched source parity, fetch failure/no-silent-fallback, opt-in off env-only compatibility, secret-redaction assertions - -## Notes -- Source Discovery: `docs/discovery/dcy-037_20260714_operator-broker-truth-and-managed-slot-divergence-hotfix.md` (FR-1, NFR-1~4; GitHub #39). -- Dependencies: REQ-112 implemented credential read port; REQ-113 approved source priority/break-glass policy; REQ-115 approved day-start credential resilience. -- OQ-3의 production live sync target 선정·실행은 release gate이며 본 REQ 범위가 아니다. - -## Change Log - -### CHG-20260714-01 -- Date: 2026-07-14 -- Author: delivery-runner - -#### Change Summary -- dcy-037 #39의 fetched credential daemon/operator broker truth mismatch를 독립 Proposed REQ로 초안화했다. - -#### Intent -- fetched credential authority를 공통 provider 경계로 정렬하여 dry-run/verify 결과가 daemon broker truth와 동일한 source를 사용하게 한다. - -#### Acceptance Criteria Delta -- Added: - - fetched provider source parity, no-silent-fallback, break-glass 구별, env-only 회귀 및 secret-safe 관찰성 기준. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - runtime/interface/operations baseline 및 REQ-112/113 참조 정합 대상. -- Modules: - - daemon/CLI 및 공통 broker 구성 경계. -- Tests: - - mocked fetched credential 기반 parity와 회귀 테스트. - -#### Delivery Trace Delta -- Batches: - - none (draft only; dcy-037 #39/#40/#41 단일 emergency batch의 향후 입력). -- Releases: - - none (production release/live sync 승인 대상 아님). - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 전 공통 provider 경계와 REQ-113/115 의존 관계를 검토한다. - -#### Validation Plan -- isolated fixture에서 daemon-equivalent, `sync --dry-run`, `verify`의 source identity와 holding set을 비교하고, fetch failure·break-glass·env-only 분기를 회귀 검증한다. - -### CHG-20260714-02 -- Date: 2026-07-14 -- Author: Justin (TREX lead) - -#### Change Summary -- Proposed에서 Approved로 승인했다. - -#### Intent -- dcy-037에서 확정된 #39 broker truth 정합 범위를 독립 delivery input으로 전환한다. - -#### Acceptance Criteria Delta -- Added: - - 없음. -- Updated: - - 없음. -- Removed: - - 없음. - -#### Impacted Area Delta -- Docs: - - 없음. -- Modules: - - 없음. -- Tests: - - 없음. - -#### Delivery Trace Delta -- Batches: - - dcy-037의 #39/#40/#41 단일 emergency batch (아직 미생성). -- Releases: - - none; `sync --live`는 별도 release approval gate 유지. - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs에서 REQ-119~122를 단일 emergency batch로 편성한다. - -#### Validation Plan -- batch verification과 독립 QC에서 parity·fallback·redaction AC를 실제 fixture와 CLI 회귀로 재검증한다. - -### CHG-20260715-03 -- Date: 2026-07-15 -- Author: TREX lead - -#### Change Summary -- Approved → Implemented (`confirm-req-implemented`). - -#### Delivery Trace -- BAT-057 verification confirmed; PR #42 merged to `main` as `94e148940adb008db0bbd215052a1e984f7c566b`. -- Independent QC: PASS-WITH-NOTES, acceptance blocker 0. Production live sync/deploy remains a separate release gate. diff --git a/docs/srs/Interface/req-002_slot-pure-domain-port.md b/docs/srs/Interface/req-002_slot-pure-domain-port.md deleted file mode 100644 index a878ace..0000000 --- a/docs/srs/Interface/req-002_slot-pure-domain-port.md +++ /dev/null @@ -1,371 +0,0 @@ -# req-002: slot 순수 도메인 이식 및 공개 API 확정 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 검증된 slot 상태머신/서비스 계약을 인프라에 오염되지 않은 storage-agnostic 코어로 trex-core 에 이식해, trex-core 의 표준 도메인 작성 패턴(순수 도메인 + Protocol 저장소 경계)을 확립한다. - -## Requirement -- trex-orbit 의 순수 6개 모듈(`state`/`models`/`errors`/`repository`/`service`/`in_memory_repository`)과 `__init__.py` 를 `trex_core.domain.slot` 네임스페이스로 이식하고, 모듈 간 import 경로를 `trex_core.domain.slot.*` 로 통일하며, 공개 API 를 인프라 심볼 없이 노출한다. - -## Acceptance Criteria -1. Given 이식 완료, When `from trex_core.domain.slot import SlotState, Slot, SlotCapacity, SlotTransitionRecord, SlotRepository, SlotService, SlotDomainError, SlotNotFoundError, SlotStateMismatchError, SlotTransitionError, is_allowed_transition, SLOT_ADVISORY_LOCK_ID, ALLOWED_TRANSITIONS` 를 실행, Then 모두 import 에 성공한다. -2. Given 이식된 도메인 코드, When 정적 import 스윕, Then stdlib 와 `trex_core.domain.slot` 내부 참조 외 외부 의존(psycopg/네트워크/CLI/config) import 가 0이다. (NFR-1) -3. Given 공개 API, When `__init__.py` 의 `__all__` 을 확인, Then `PostgreSQLSlotRepository`·`check_slot_domain_schema_drift` 등 인프라 심볼이 노출되지 않는다. -4. Given `SlotService`, When 6 상태/9 base 전이(`closed→pending_buy` 포함, dcy-019)/advisory-lock 직렬화/`closed→idle` 재진입 +1/`→pending_buy` 시 slot `entry_count` +1(dcy-019)/`closed→pending_buy` clean-slate 점유 초기화/lock 보유 중 외부 I/O 금지 동작을 확인, Then 보존된다. (NFR-2 — 검증은 REQ-003) -5. (CHG-20260610-02, dcy-023/issue #22) Given allocated_capital 제거 후, Then `Slot` 모델과 `EXPECTED_SLOT_FIELDS` 에 `allocated_capital` 필드가 없고, `SlotService` 의 설정/전이 경로(buy 진입·전이 시 set/clear)에서 해당 파라미터가 제거된다. -6. (CHG-20260612-01, issue #25) Given `SlotRepository.list_slots(instance_key, status_filter=None)`, When `status_filter` 가 status 값 집합(예: `{"inactive"}`, `{"pending_buy","pending_sell"}`)이면, Then 해당 status 슬롯만 반환하고 `None`(미지정)이면 기존대로 전체를 반환한다(in-memory 구현 포함). 기존 무인자 호출자는 동작이 불변이다. - -## Impacted Area -- Docs: docs/interface-contract.md, docs/data-model.md (계약 기준 — 큰 변경 없을 수 있음) -- Modules: trex_core/domain/slot/{__init__,state,models,errors,repository,service,in_memory_repository}.py, (CHG-20260610-02) models.py `Slot`/`EXPECTED_SLOT_FIELDS` + service.py allocated_capital 제거, (CHG-20260612-01) repository.py `SlotRepository.list_slots` 시그니처 `status_filter` 옵션 + in_memory_repository.py 구현 -- Tests: REQ-003 에서 이식 단위테스트 + 계약 테스트로 검증 - -## Notes -- NFR-1(순수 경계)·NFR-2(불변식 보존)를 본 REQ 의 acceptance 로 흡수. NFR-3(컬럼 계약)은 REQ-003 계약 테스트가 강제한다. -- 이식 시 유일한 코드 수정은 `__init__.py` docstring 정리 수준이며 로직 변경은 없다(dcy-001 A-2). -- 네임스페이스 `trex_core.domain.slot` 은 dcy-001 OQ-1 결정. REQ-001(부트스트랩) 선행. -- 출처: dcy-001 FR-2. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-001 FR-2 로부터 최초 생성(Proposed). NFR-1·NFR-2 를 acceptance 로 흡수. - -#### Intent -- slot 순수 도메인 이식 + 공개 API 확정 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 공개 API import, 외부 의존 0, 인프라 심볼 비노출, 도메인 불변식 보존 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/data-model.md -- Modules: - - trex_core/domain/slot/* (6 모듈 + __init__) -- Tests: - - REQ-003 에 위임 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-001 과 동일 batch 가능) - -#### Validation Plan -- 공개 API import 성공 + import 스윕(외부 의존 0) + REQ-003 테스트 통과로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-001(confirmed, handoff 승인)과 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-001 release-candidate). - -#### Intent -- BAT-001 verification.md 의 evidence(공개 API import / import 스윕 외부 의존 0 / `__all__` 인프라 심볼 0 / 도메인 불변식 테스트)로 AC1~AC4 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-001 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-001 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012/issue #10) 슬롯 도메인 필드 `entry_price` → `limit_price`(side 무관 "현재 pending 주문 지정가") 내부 rename + 생애주기 정의: pending 진입 시 set, pending 이탈(→HOLDING/→CLOSED) 시 None clear. `transit_slot`/`_apply_transition_fields` 파라미터·`EXPECTED_SLOT_FIELDS` 반영. BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 매수 지정가가 매도 주문에 누수되는 구조적 원인을 도메인 모델에서 제거(clear-on-exit). 가격 필드 의미를 side 무관으로 통일. - -#### Acceptance Criteria Delta - -- Updated: - - Slot.`entry_price` → `limit_price`; →HOLDING/→CLOSED 전이 시 limit_price clear, →PENDING_* 전이 시 set. - -#### Impacted Area Delta - -- Modules: `trex_core/domain/slot/models.py`(Slot/EXPECTED_SLOT_FIELDS), `trex_core/domain/slot/service.py`(transit_slot/_apply_transition_fields clear-on-exit) -- Tests: tests/domain/slot/test_service.py(holding clear), tests/domain/slot/test_contract.py(필드셋) - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: yes (entry_price 필드명/유지 의미) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: 전이별 set/clear 단위 + 컬럼 계약 가드(REQ-003) 정합. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-019/issue #18) 슬롯 도메인에 (1) slot-scoped `Slot.entry_count`(기본 0) 필드 + `EXPECTED_SLOT_FIELDS` 추가, (2) 상태 머신 허용 전이에 `closed→pending_buy`(closed 슬롯 직접 재할당) 추가(8→9 base 전이), (3) `_apply_transition_fields` 에 closed→pending_buy clean-slate 점유 초기화 + 모든 `→pending_buy`(idle→/closed→) 시 `entry_count` +1 로직 반영. 기존 `daily_symbol_entries`(symbol-scoped, AC-3)는 무관·미변경. BAT-025 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 슬롯 자원을 고르게 재사용(최저 진입 우선)하고 idle 소진 시 closed 재할당을 허용하기 위해, 슬롯 단위 누적 진입 횟수를 도메인 정본에 영속하고 closed→pending_buy 직접 전이를 도메인 계약에 추가. - -#### Acceptance Criteria Delta - -- Updated: - - AC-4: "6 상태/8 전이" → "6 상태/9 base 전이(`closed→pending_buy` 포함)". 불변식 보존 대상에 `→pending_buy 시 entry_count +1`, `closed→pending_buy clean-slate 점유 초기화` 추가. -- Added: - - `Slot.entry_count`(int, 기본 0) 필드가 `EXPECTED_SLOT_FIELDS` 계약에 포함된다. - -#### Impacted Area Delta - -- Modules: `trex_core/domain/slot/models.py`(Slot.entry_count/EXPECTED_SLOT_FIELDS), `trex_core/domain/slot/state.py`(closed→pending_buy 전이), `trex_core/domain/slot/service.py`(clean-slate + entry_count +1) -- Tests: tests/domain/slot/test_state.py(전이 카운트 14→15), test_contract.py(필드셋), test_slot_entry_count.py(신규) - -#### Delivery Trace Delta - -- Batches: BAT-025 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-025 에서 완료) -- Existing Implementation Invalidated: no (additive — 기존 idle-only 동작은 closed 후보 부재 시 보존, 회귀 green) -- Status Recommendation: Implemented (change-req → BAT-025 재검증) -- Follow-up Action: confirm-req-implemented(BAT-025). - -#### Validation Plan - -- BAT-025: 전이 카운트/계약 가드 + entry_count 증분·보존 + closed→pending_buy clean-slate 단위 테스트 green; `daily_symbol_entries`(AC-3) 회귀 green. - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020 자산 중심 슬롯 모델) 본 REQ 의 슬롯 도메인(6상태·`closed→idle` 복구·entry_count·익명 슬롯)이 dcy-020 으로 **재정의/폐기 예정**. 신규 REQ-057(상태머신 inactive↔pending_buy↔holding↔pending_sell·holding 로컬·인스턴스 스코프 lock)·REQ-056(영구 슬롯 필드·entry_count 제거)·REQ-062(qty hub 정본+미러)가 대체. **현재 코드는 불변이므로 Implemented 유지(keep-current)**, 실제 폐기/재정의는 dcy-020 delivery 배치(상태머신/스키마 BAT)에서 확정한다. - -#### Intent - -- 자산 1:1 영구 슬롯 모델 전환의 추적성을 본 REQ 에 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(본 주기 변경 없음 — dcy-020 delivery 배치에서 AC 재정의 예정) - -#### Impacted Area Delta - -- Docs: dcy-020, REQ-056/057/062 -- Modules: (예정) domain/slot/state.py·models.py·service.py -- Tests: (예정) REQ-057 테스트로 대체 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-020 상태머신/스키마 배치 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점) -- Existing Implementation Invalidated: no (코드 불변 — dcy-020 delivery 시 무효화·재정의) -- Status Recommendation: keep-current -- Follow-up Action: dcy-020 delivery 배치에서 Deprecated/재정의 + 그때 status 전환. - -#### Validation Plan - -- dcy-020 상태머신 배치의 verification 으로 대체. - -### CHG-20260610-02 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-023/issue #22) 죽은 필드 `allocated_capital` 을 슬롯 도메인에서 제거 — `Slot` 모델·`EXPECTED_SLOT_FIELDS` 계약·`SlotService` 의 설정/전이(set/clear) 경로에서 삭제. 소비처(의사결정/회계/사이징) 0건 확인(dcy-023 §3). AC-5 추가, Implemented→Approved(재구현 대기). - -#### Intent - -- 실제 소비되는 필드만 슬롯 계약에 남겨 유지 비용과 "capital 이 사이징한다"는 오해를 제거. dcy-021 사이징 계약(전략이 qty 계산, core 미관여) 확정의 귀결. - -#### Acceptance Criteria Delta - -- Added: - - AC-5: Slot 모델·EXPECTED_SLOT_FIELDS·SlotService 경로에서 allocated_capital 부재. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Modules: trex_core/domain/slot/models.py(Slot/EXPECTED_SLOT_FIELDS), trex_core/domain/slot/service.py(설정/전이 경로 파라미터 제거) -- Tests: tests/domain/slot/ 계약 테스트(필드셋) + allocated_capital 참조 테스트 정리 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-023 batch -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (dcy-023 batch 에서 수행) -- Existing Implementation Invalidated: yes (필드 제거 — 기존 필드셋 계약 evidence 무효) -- Status Recommendation: Approved (Implemented→Approved 되돌림) -- Follow-up Action: dcy-023 batch 구현·검증 후 confirm-req-implemented. - -#### Validation Plan - -- dcy-023 batch: 필드셋 계약 테스트(allocated_capital 부재) + 전체 suite 회귀 green. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음. - -### CHG-20260612-01 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #25) `SlotRepository.list_slots` 에 optional `status_filter` 파라미터를 추가해 status 필터링을 저장소 경계로 내린다. 시그니처 `list_slots(instance_key, status_filter: Optional[set[str]] = None)` — `None` 은 기존(전체), 값 집합이면 해당 status 만 반환. in-memory 구현도 동일 의미로 갱신. slots RPC 핸들러(REQ-073)의 per-request full scan + Python 필터를 제거하기 위한 포트 확장. 신규 AC-6 추가 구현·검증 필요 → **Implemented → Approved 되돌림**. - -#### Intent - -- `trex.slots.req` 가 매 요청마다 전 슬롯을 fetch 한 뒤 Python 에서 거르던 구조(issue #25)를 DB 레벨 필터로 바꾸기 위해, 도메인 포트(`list_slots`)와 in-memory 더블을 먼저 확장한다. 기존 무인자 호출자(reconcile/safety-sell 등)는 default `None` 으로 동작 불변. - -#### Acceptance Criteria Delta - -- Added: - - AC-6: `list_slots(instance_key, status_filter)` — status 값 집합 필터 + `None` 전체, in-memory 포함, 기존 호출자 불변. -- Updated/Removed: 없음 - -#### Impacted Area Delta - -- Docs: 없음 -- Modules: `trex_core/domain/slot/repository.py`(Protocol 시그니처), `trex_core/domain/slot/in_memory_repository.py`(구현) -- Tests: in-memory `list_slots(status_filter)` 단위 — status 집합 필터 + None 전체 + 기존 무인자 회귀 - -#### Delivery Trace Delta - -- Batches: BAT-040 (예정, issue #25) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-040 에서 AC-6 + 기존 AC 회귀) -- Existing Implementation Invalidated: no (추가 파라미터는 default None 으로 기존 동작 보존). 단 신규 AC-6 미구현으로 Implemented 유지 불가 → Approved. -- Status Recommendation: Implemented → **Approved** -- Follow-up Action: confirm-req → BAT-040 구현·검증 → confirm-req-implemented - -#### Validation Plan - -- BAT-040: in-memory `list_slots` status 집합 필터 + None 전체 단위 테스트, 기존 무인자 호출 회귀, 전체 suite + ruff clean. - -### CHG-20260612-IMPL -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-040(release-candidate) verification — AC-6 `list_slots(status_filter)` in-memory 5종(None 전체/단일 status/pending 그룹/무매치/instance 스코프) PASSED, 무인자 호출자 default None 동작 보존(전체 439 passed), ruff clean. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-007_intent-submission-api.md b/docs/srs/Interface/req-007_intent-submission-api.md deleted file mode 100644 index 26b978f..0000000 --- a/docs/srs/Interface/req-007_intent-submission-api.md +++ /dev/null @@ -1,443 +0,0 @@ -# req-007: intent 제출 HTTP API + 멱등 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 매수·매도 전략 데몬이 HTTP 로 intent 를 제출하면 코어가 단독으로 admission/검증 후 슬롯을 전이하도록 한다(전략은 제안, 코어가 집행). intent_id 멱등으로 재전송 안전을 보장한다. - -## Requirement -- `POST /v1/intents/buy` 와 `POST /v1/intents/sell` 를 제공한다. 코어는 admission/검증 후 슬롯 전이(idle→pending_buy / holding→pending_sell)를 advisory lock 하 단일 writer 로 수행하고, 동기로 admit/reject 를 응답한다. 수락된 intent 는 같은 트랜잭션에서 `intent_log`(intent_id, payload, decision, ts) 에 durable 기록하며 `intent_id` 로 dedup 한다. `GET /v1/intents/{intent_id}` 로 결정을 재조회한다. - -## Acceptance Criteria -1. Given 할당 가능 슬롯(idle ∪ closed, dcy-019), When `POST /v1/intents/buy`, Then `find_allocatable_slot`(entry_count→updated_at→slot_id 정렬)로 최저 진입 후보를 잡아 `slot.status→pending_buy`(idle→ 또는 closed→ 직접 재할당) 전이하고 `{admitted:true, slot_id}` 를 동기 응답한다. idle·closed 모두 소진 시 `no_idle_slot` 거부. -2. Given holding 슬롯, When `POST /v1/intents/sell`, Then 검증 통과 시 holding→pending_sell 로 전이하고 admit 결과를 동기 응답한다(거부 시 사유 포함). -3. Given 이미 처리된 `intent_id`, When 동일 intent 재전송, Then 슬롯이 중복 전이되지 않고 최초 결정과 동일한 응답을 돌려준다(멱등). (NFR-3) -4. Given 동일 슬롯에 대한 동시 intent, When 병렬 처리, Then advisory lock 으로 직렬화되어 단일 writer 불변식이 유지된다(매수·매도 전략은 슬롯을 직접 쓰지 않음). (NFR-2) -5. Given 수락된 intent, When `GET /v1/intents/{intent_id}`, Then 기록된 decision 을 반환한다. -6. (CHG-20260610-02, dcy-023/issue #22) Given buy intent payload, Then `allocated_capital` 은 계약 필드가 아니다 — `BuyIntent` 에 필드가 없고 admission 경로에서 전달되지 않으며, 외부 발신자가 payload 에 키를 포함해도 unknown key 로 무시된다(즉시 제거, dcy-023 OQ-1). -7. (CHG-20260615-01, issue #27) Given **rejected** sell intent 응답(`intent_type:"sell"`, `status:"rejected"`), Then additive `retryable: bool` 필드가 포함된다 — core 가 소유한 reason→retryable 매핑으로 채워지고 `reason` 은 유지된다. 매핑: `slot_state_changed`/`slot_not_holding:pending_buy`/`duplicate_in_flight` → `true`; `slot_not_found`/`slot_not_holding:inactive`/`slot_not_holding:pending_sell`/`slot_not_holding:error`/`bad_request:*` → `false`; 미인식 reason → `true`(안전 기본 — 멈춘 손절매가 hammering 보다 위험). admitted/processing/buy 응답·기존 필드는 불변(완전 additive·하위호환). - -## Impacted Area -- Docs: docs/interface-contract.md(HTTP API 계약), docs/runtime-flows.md(요청 흐름) — REQ-009 에서 정합 -- Modules: trex_core/ 하위 API 어댑터(FastAPI 라우터/스키마) + 애플리케이션 서비스; 도메인 패키지는 무의존 유지(NFR-1); (CHG-20260610-02) intents.py BuyIntent·admission + messaging_handlers.py payload 파싱 allocated_capital 제거 -- Tests: API 통합테스트(정상/거부/멱등 재전송/동시성) - -## Notes -- 동기 커밋 + intent_id 멱등 + intent_log(dcy-002 OQ-1). 완전 비동기 outbox 는 브로커 Discovery 로 Deferred. -- `intent_log` 테이블은 REQ-006 에서 생성. REQ-005(런타임)·REQ-006(영속화) 선행. -- NFR-2(단일 writer)·NFR-3(멱등) 흡수. -- 출처: dcy-002 FR-2, FR-4. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-002 FR-2·FR-4 로부터 최초 생성(Proposed). NFR-2·NFR-3 흡수. - -#### Intent -- intent 제출 API + 멱등 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - buy/sell 전이, 멱등 재전송, 동시성 단일 writer, intent 재조회 5개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md -- Modules: - - API 어댑터(FastAPI) + 애플리케이션 서비스 -- Tests: - - API 통합테스트(정상/거부/멱등/동시성) - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- API 통합테스트(멱등 재전송 + 동시성 직렬화 포함)로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-002(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-002 release-candidate). - -#### Intent -- BAT-002 verification.md 의 acceptance mapping + evidence(pytest 74 pass / PG 통합 / 데몬 E2E)로 모든 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-002 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-002 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-011) buy intent admission 계약 변경: `portfolio_id` 필수화 + admission 시 universe(`SnapshotStore`) 에서 `symbol` resolve + universe 부재 시 `unknown_portfolio` 거부. 기존 symbol-필수/portfolio_id-optional 폐기(hard cutover, 하위호환 없음). BAT-015 에서 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 정식 식별자(asset_id=portfolio_id) 기반 원장 귀속·safety 보호 범위 정합(issue #6). `_admit_buy` 에 portfolio_id_required/unknown_portfolio reject + symbol resolve(`resolve_symbol` 주입) 추가. - -#### Acceptance Criteria Delta - -- Added: - - buy 는 portfolio_id 필수(누락 시 `portfolio_id_required`), symbol 은 서버 resolve(`SnapshotStore`), universe 부재 시 `unknown_portfolio` 거부. -- Updated: - - 기존 admission AC 의 symbol 입력 전제 → portfolio_id 기반으로 정정. - -#### Impacted Area Delta - -- Modules: `trex_core/application/intents.py`(BuyIntent portfolio_id 필수+symbol resolve, IntentService.resolve_symbol), `messaging_handlers.py`(buy payload), `runtime/daemon.py`(resolver 배선) -- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md` -- Tests: intent/messaging buy 테스트 portfolio_id 기반 전환 - -#### Delivery Trace Delta - -- Batches: BAT-015 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-015 에서 완료) -- Existing Implementation Invalidated: yes (symbol-필수 admission 경로) -- Status Recommendation: Implemented (change-req → BAT-015 재구현·재검증) -- Follow-up Action: confirm-req-implemented(BAT-015) 로 Implemented 동기화. - -#### Validation Plan - -- BAT-015 verification: portfolio_id_required/unknown_portfolio/symbol resolve 단위 + 전체 회귀(314 passed). - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012) sell intent 계약에 optional 매도 가격 필드 추가: `SellIntent.limit_price`(+ sell payload `limit_price`) → `_admit_sell` 이 슬롯 `limit_price` 로 운반. 지정 시 지정가 매도, 미지정 시 시장가(자율 매도 기본 경로). buy 의 `entry_price` 는 동일 슬롯 `limit_price` 로 매핑(동작 동일). BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 매도 가격 하이브리드 정책(가격 유무 분기, dcy-012 OQ-1/OQ-2)을 intent 제출 계약에 반영. 기존 재량 매도 호출(가격 미지정)은 시장가로 안전 동작(NFR-3). - -#### Acceptance Criteria Delta - -- Added: - - sell intent/payload optional `limit_price`(미지정 → 시장가, 지정 → 지정가). admission 이 슬롯 limit_price 로 운반. - -#### Impacted Area Delta - -- Modules: `trex_core/application/intents.py`(SellIntent/_admit_sell, _admit_buy limit_price 매핑), `trex_core/application/messaging_handlers.py`(sell payload limit_price) -- Docs: docs/interface-contract.md -- Tests: tests/application/test_sell_pricing.py - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: no (optional 추가, 기존 sell 호출 시장가로 호환) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: 가격 전달 매도 → 지정가 / 미전달 → 시장가 단위 + 핸들러 payload. - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, REL-004 사전 run 검증에서 발견된 결함 수정) 데몬 buy 심볼 resolver 버그 정정: `Daemon._resolve_symbol` 이 `SnapshotStore.get()` 반환값(`SnapshotAsset`)에서 `asset.symbol` 을 접근했으나 symbol 은 `asset.item.symbol` 에 있어 `AttributeError` → 데몬 buy admission 이 매번 크래시(intent RPC 타임아웃). `asset.item.symbol` 로 수정. 단위 테스트가 stub resolver 를 주입해 실 데몬 배선을 한 번도 실행하지 않아 369 passed 에도 누락됐다 — 실 데몬 기동 + CLI e2e(REL-004 smoke)에서 검출. Implemented 유지. - -#### Intent - -- dcy-011/REQ-007 의 buy 심볼 서버 resolve 가 실 런타임(daemon + SnapshotStore)에서 동작하도록 배선 결함 수정 + 회귀 가드 추가. - -#### Acceptance Criteria Delta - -- (변경 없음 — AC 동일; 배선 결함 수정. 실 데몬 resolver 가 SnapshotAsset.item.symbol 을 읽음) - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/daemon.py`(`_resolve_symbol` → `asset.item.symbol`) -- Tests: tests/runtime/test_daemon_resolve_symbol.py(실 Daemon._resolve_symbol × 실 SnapshotStore 회귀) - -#### Delivery Trace Delta - -- Batches: BAT-015 -- Releases: REL-004 (사전 run 검증에서 검출·수정) - -#### Revalidation Impact - -- Reverification Needed: yes (REL-004 live smoke + 회귀 테스트로 완료) -- Existing Implementation Invalidated: yes (데몬 buy admission 경로 — 실 런타임에서 전면 실패였음) -- Status Recommendation: Implemented (배선 수정 + 회귀 가드) -- Follow-up Action: REL-004 confirm-release 전 live smoke 증거에 반영. - -#### Validation Plan - -- 실 데몬 기동(paper + test PG/RabbitMQ) + `trex-core enter pf-005930 --yes` → admitted/체결/ledger completed e2e + test_daemon_resolve_symbol 회귀. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-019/issue #18) buy admission(`_admit_buy`) 의 슬롯 선택을 `find_idle_slot` → `find_allocatable_slot`(idle ∪ closed, entry_count→updated_at→slot_id 정렬)로 일반화하고, 후보 status 를 따르도록 transit from_state 를 하드코딩 `IDLE` → `slot.status` 로 변경(closed 후보면 `closed→pending_buy` 직접 재할당). 마지막 슬롯 경합 재시도 루프·멱등(intent_log dedup)·단일 writer 불변식 보존. BAT-025 검증 완료 → Implemented 유지. - -#### Intent - -- idle 소진 시에도 closed 슬롯을 재할당해 슬롯 자원을 효율적으로 회전시키고, 슬롯 사용을 진입 횟수 기준으로 고르게 분산. - -#### Acceptance Criteria Delta - -- Updated: - - AC-1: 후보 = idle ∪ closed, `find_allocatable_slot` 선택 + `slot.status→pending_buy` 전이(closed 직접 재할당 포함), 양쪽 소진 시 `no_idle_slot` 거부로 일반화. - -#### Impacted Area Delta - -- Modules: `trex_core/application/intents.py`(`_admit_buy`: find_allocatable_slot + from_state=slot.status) -- Tests: tests/application/test_buy_admission_reallocation.py(신규: closed 재할당 admit/idle 우선/양쪽 소진 reject), tests/application/test_intent_concurrency.py(경합 회귀 보존) - -#### Delivery Trace Delta - -- Batches: BAT-025 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-025 에서 완료) -- Existing Implementation Invalidated: no (closed 후보 부재 시 기존 idle-only 동작과 동일 — 회귀 green) -- Status Recommendation: Implemented (change-req → BAT-025 재검증) -- Follow-up Action: confirm-req-implemented(BAT-025). - -#### Validation Plan - -- BAT-025: closed 재할당 admit / idle 우선 / 양쪽 소진 reject 단위 + 동시성 경합 회귀 green. - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020) buy/sell admission 이 자산 중심으로 **재정의 예정** — REQ-059(FCFS·M캡·무치환·over-cap)·REQ-061(intent 자산 키 통일, `sell-by-slot_id` 폐기, already_active/not_holding)가 대체. `find_allocatable_slot`/idle·closed 배정 폐기. 외부 전략 계약 변경(NFR-4) 동반. 현재 코드 불변이라 Implemented 유지(keep-current), dcy-020 admission/intent 배치에서 확정(외부 계약 cutover 게이트). - -#### Intent - -- 신호 기반 자산 admission 전환 추적성 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(dcy-020 delivery 배치에서 admission/intent AC 재정의) - -#### Impacted Area Delta - -- Docs: dcy-020, REQ-059/061 -- Modules: (예정) application/intents.py·메시지 핸들러/스키마 -- Tests: (예정) intent/admission 테스트 재작성 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-020 admission/intent 배치(외부 계약 cutover) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점) -- Existing Implementation Invalidated: no (코드 불변) -- Status Recommendation: keep-current -- Follow-up Action: dcy-020 admission/intent 배치에서 재정의 + status 전환. 외부 전략 모듈 동시 전환(NFR-4). - -#### Validation Plan - -- dcy-020 admission/intent 배치 verification 으로 대체. - -### CHG-20260610-02 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-023/issue #22) buy intent payload 계약에서 `allocated_capital` 즉시 제거 — `BuyIntent` 데이터클래스 필드·admission 전달(`_admit_buy`→SlotService)·messaging handler payload 파싱(`body.get("allocated_capital")`) 삭제. 외부 발신자(trex-orb-v1)가 키를 계속 송신해도 unknown key 로 무시되어 무해(dcy-023 OQ-1 운영자 결정: 즉시 제거, 유예 없음). AC-6 추가, Implemented→Approved(재구현 대기). - -#### Intent - -- 사이징·회계 소비자가 없는 운반 전용 필드를 intent 계약에서 제거. dcy-021 명령형 qty 사이징 계약과 정합. - -#### Acceptance Criteria Delta - -- Added: - - AC-6: BuyIntent 에 allocated_capital 부재 + 수신 payload 의 해당 키 무시. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Modules: trex_core/application/intents.py(BuyIntent/admission), trex_core/application/messaging_handlers.py(payload 파싱) -- Tests: intent admission·handler 파싱 테스트에서 allocated_capital 참조 정리 + 키 포함 payload 무시 테스트 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-023 batch -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (dcy-023 batch 에서 수행) -- Existing Implementation Invalidated: yes (BuyIntent 필드·파싱 제거) -- Status Recommendation: Approved (Implemented→Approved 되돌림) -- Follow-up Action: dcy-023 batch 구현·검증 후 confirm-req-implemented. - -#### Validation Plan - -- dcy-023 batch: allocated_capital 포함 payload 가 정상 admit 되며 슬롯·로그에 미반영(무시) + 전체 suite green. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- (change-req, issue #27) rejected SellIntent 응답에 additive `retryable: bool` 추가. core 소유 reason→retryable 매핑으로 채우고 `reason` 유지. #26 sell-idempotency 논의(거부 sticky)의 후속 — 데몬이 reason 문자열 하드코딩 없이 권위 플래그로 retry/stop 판단(손절매 안전 경로 decoupling). - -#### Intent -- 거부 retryability를 core 도메인 지식으로 캡슐화해, 새 reason 추가 시 데몬 분류가 조용히 drift하는 안전 실패 모드 제거(issue #27). - -#### Acceptance Criteria Delta -- Added: AC-7 (rejected sell 응답 additive `retryable` + core 소유 매핑 + 미인식 기본 true + 완전 additive) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/interface-contract.md(rejected sell 응답 `retryable` 필드) -- Modules: trex_core/application/messaging_handlers.py(intent 응답 빌더 — sell+rejected 시 retryable; core 소유 `_SELL_REJECT_RETRYABLE` 매핑) -- Tests: tests/application/(retryable 플래그 매핑 케이스 + additive 하위호환) - -#### Delivery Trace Delta -- Batches: BAT-043(편성 예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (신규 AC-7만) -- Existing Implementation Invalidated: no (완전 additive — 기존 응답 필드/admit/buy 불변) -- Status Recommendation: revert-to-approved (Implemented → Approved — 신규 additive AC 구현·검증 대기) -- Follow-up Action: BAT-043 batch-lite 구현·검증 후 confirm-req-implemented - -#### Validation Plan -- messaging_handlers 테스트: 각 reject reason → 기대 retryable 매핑, 미인식 reason → true, admitted/processing/buy 응답에 retryable 부재, 기존 reason 필드 유지. - -### CHG-20260615-IMPL -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-043(release-candidate) verification — rejected sell 응답 additive `retryable`(core 소유 `_SELL_REJECT_RETRYABLE` 매핑 + `_sell_reject_retryable` helper, 미인식 true/`bad_request*` false). AC-7 evidence: `tests/application/test_sell_retryable_flag.py`(매핑 7 reason+미인식+None+bad_request, 응답 inactive/unknown_slot/bad_request→false, admitted/buy 부재). 487 passed, ruff clean. 기존 AC-1~6 불변. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-008_slot-readonly-query-api.md b/docs/srs/Interface/req-008_slot-readonly-query-api.md deleted file mode 100644 index 44d5826..0000000 --- a/docs/srs/Interface/req-008_slot-readonly-query-api.md +++ /dev/null @@ -1,152 +0,0 @@ -# req-008: 슬롯 read-only 조회 HTTP API - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 매도 전략이 보유 슬롯을 평가하고 운영자가 가용성을 모니터링할 수 있도록, 코어가 슬롯 스냅샷과 capacity 를 read-only 로 제공한다. - -## Requirement -- `GET /v1/slots?status=holding`(status/trading_date 필터)로 슬롯 스냅샷을 read-only 로 반환하고, `GET /v1/capacity` 로 idle/holding/pending 카운트 + scarcity_ratio 를 반환한다. 이 엔드포인트는 슬롯을 쓰지 않는다(read-only). - -## Acceptance Criteria -1. Given holding 슬롯이 있는 상태, When `GET /v1/slots?status=holding`, Then 해당 슬롯 스냅샷 목록을 반환한다(쓰기 부작용 없음). -2. Given status/trading_date 필터, When 조회, Then 필터에 맞는 슬롯만 반환한다. -3. Given 슬롯 분포, When `GET /v1/capacity`, Then idle/holding/pending 카운트와 scarcity_ratio 를 반환한다. - -## Impacted Area -- Docs: docs/interface-contract.md(조회 API 계약) — REQ-009 에서 정합 -- Modules: trex_core/ 하위 API 어댑터(읽기 라우터); 도메인 `compute_slot_capacity`/repo 조회 재사용 -- Tests: 조회 API 통합테스트 - -## Notes -- 엔드포인트 집합은 dcy-002 OQ-3 결정. REQ-005(런타임)·REQ-006(영속화) 선행. -- read-only 라 단일 writer 불변식과 무관(쓰기 경로 아님). -- 출처: dcy-002 FR-3. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-002 FR-3 로부터 최초 생성(Proposed). - -#### Intent -- 슬롯 read-only 조회 API 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - holding 스냅샷, 필터, capacity 3개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md -- Modules: - - API 어댑터(읽기 라우터) -- Tests: - - 조회 API 통합테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- 조회 API 통합테스트(스냅샷/필터/capacity)로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-002(confirmed, handoff 승인)와 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-002 release-candidate). - -#### Intent -- BAT-002 verification.md 의 acceptance mapping + evidence(pytest 74 pass / PG 통합 / 데몬 E2E)로 모든 AC 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-002 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-002 verification evidence 재참조로 충분. diff --git a/docs/srs/Interface/req-025_http-api-complete-removal.md b/docs/srs/Interface/req-025_http-api-complete-removal.md deleted file mode 100644 index 2d0e2ef..0000000 --- a/docs/srs/Interface/req-025_http-api-complete-removal.md +++ /dev/null @@ -1,157 +0,0 @@ -# req-025: HTTP `/v1` API 완전 제거 (healthz 포함, HTTP 표면 0) - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- RabbitMQ 단일 transport 결정(dcy-006 OQ-1)에 따라 trex-core 의 HTTP `/v1` API 표면을 0 으로 만든다. 전략-facing 엔드포인트뿐 아니라 `GET /v1/healthz` 같은 앱 레벨 헬스 표면까지 제거하고(OQ-7), liveness 는 프로세스 감독(systemd/supervisor/docker restart)이 담당한다. 이중 transport 병행을 두지 않는다. - -## Requirement -- 아래 HTTP 엔드포인트와 그 어댑터를 trex-core 코드와 baseline 에서 제거한다. - - `POST /v1/intents/buy`, `POST /v1/intents/sell` - - `GET /v1/intents/{intent_id}` - - `GET /v1/slots`, `GET /v1/capacity` - - `GET /v1/healthz` - - dcy-005 가 HTTP 로 가정한 스냅샷 read 엔드포인트(아직 미구현인 경우 가정 자체 갱신) -- `trex_core/adapters/api/` HTTP 어댑터 패키지를 제거하고, FastAPI/uvicorn/Starlette 직접 의존을 코어 런타임에서 제거한다(테스트 전용 보조 import 도 마찬가지). `pyproject.toml` 의 `fastapi`/`uvicorn` 의존성을 정리한다. -- `TREX_API_HOST` / `TREX_API_PORT` env 와 그 로딩 코드를 폐기한다(`DaemonConfig`/`.env.example` 정리). -- 데몬 기동 후 HTTP 리스너가 어떤 포트에도 바인딩되어 있지 않아야 한다(HTTP 표면 0). 앱 레벨 헬스/관측성은 로그·메트릭으로 대체한다. -- dcy-002 의 HTTP intent API 가정과 dcy-005 의 HTTP pull 가정(FR-1/A-3)을 cross-reference 로 갱신한다. - -## Acceptance Criteria -1. Given 새 트리, When `find trex_core -path '*/adapters/api/*'` 또는 `grep -r "fastapi\|uvicorn" trex_core/`, Then 결과가 비어 있다(HTTP 어댑터 잔존 0). -2. Given trex-core daemon, When 기동 후 포트 스캔, Then 어떤 TCP 포트에도 HTTP 리스너가 열려 있지 않다(HTTP 표면 0, dcy-006 S-6). -3. Given `DaemonConfig.from_env()`, When 환경 변수 로딩, Then `TREX_API_HOST`/`TREX_API_PORT` 를 더 이상 참조하지 않고, 누락되어도 기동에 영향이 없다. -4. Given `pyproject.toml`, When 의존성 목록을 점검, Then `fastapi`/`uvicorn`/`starlette` 가 코어 runtime 의존성에서 제거되어 있다. -5. Given dcy-002·dcy-005 의 HTTP 가정 본문, When 본 변경 완료 시점에 cross-reference 갱신을 확인, Then 두 Discovery 의 HTTP 가정이 "transport 는 RabbitMQ(dcy-006)" 로 일관되게 안내된다. -6. Given liveness 확인 시나리오, When 프로세스 감독(systemd/supervisor/docker restart)이 프로세스 죽음을 감지, Then 재시작이 broker 가용성과 무관하게 동작한다(broker 장애가 재시작 루프 유발 금지). - -## Impacted Area -- Docs: docs/interface-contract.md(HTTP `/v1` 섹션 제거), docs/runtime-flows.md(HTTP 흐름 제거), docs/project-structure.md(adapters/api 제거), docs/discovery/dcy-002·dcy-005 cross-reference 갱신 — REQ-026 에서 정합. -- Modules: - - 제거 `trex_core/adapters/api/` 전체(`app.py`, `schemas.py`) - - 변경 `trex_core/runtime/daemon.py`(uvicorn import/실행 제거; REQ-022 의 sync+thread 런타임으로 교체) - - 변경 `trex_core/runtime/config.py`(`TREX_API_HOST`/`TREX_API_PORT` 폐기) - - 변경 `pyproject.toml`(fastapi/uvicorn 제거; httpx 는 ledger 어댑터 사용처가 있으므로 유지) - - 변경 `.env.example`(API_HOST/PORT 제거, `TREX_RABBITMQ_URL` 추가는 REQ-022 책임) -- Tests: - - 제거 `tests/adapters/api/` 전체(또는 messaging 등가 테스트로 이관) - - 추가 daemon HTTP-표면-0 회귀 가드(포트 스캔 또는 import 가드) - -## Notes -- 출처: dcy-006 FR-5, OQ-1, OQ-7. -- 본 REQ 의 검증은 transport 가 RabbitMQ 로 대체 가능한 상태(REQ-021/REQ-022/REQ-023 구현)에 의존한다. 동일 batch 또는 직후 batch 에서 함께 deliver 해야 통신 단절 위험(R-5)을 피할 수 있다. -- httpx 는 본 REQ 가 제거 대상에서 제외한다(portfolio-hub ledger 어댑터가 사용). -- 외부 운영 가시성(메트릭/로그) 표준화는 본 REQ 범위 밖이며 후속 운영 트랙으로 추적한다. - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 FR-5/OQ-1/OQ-7 로부터 최초 생성(Proposed). HTTP `/v1` API + healthz + adapters/api + API_HOST/PORT env + fastapi/uvicorn 의존 전부 제거 backlog 정규화. - -#### Intent -- 이중 transport 병행 없이 HTTP 표면 0 을 달성하고 dcy-002·dcy-005 의 HTTP 가정을 갱신. - -#### Acceptance Criteria Delta -- Added: - - HTTP 어댑터 잔존 0, HTTP 리스너 0, env 폐기, 의존성 제거, cross-reference 갱신, 프로세스 감독 liveness (총 6개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md, docs/runtime-flows.md, docs/project-structure.md, dcy-002/dcy-005 cross-reference -- Modules: - - adapters/api 제거, runtime/daemon.py, runtime/config.py, pyproject.toml, .env.example -- Tests: - - adapters/api 테스트 제거 또는 이관, daemon HTTP-표면-0 회귀 가드 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: yes (BAT-002 HTTP API 관련 구현 일부가 RabbitMQ 로 대체) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-021~023 과 동일 batch 편성(통신 단절 R-5 회피). - -#### Validation Plan -- 코드 grep + 포트 스캔 + env 누락 기동 + pyproject 의존성 검사 + cross-reference 리뷰 + 프로세스 감독 시나리오로 검증. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- HTTP `/v1` API 완전 제거(healthz 포함, HTTP 표면 0)와 fastapi/uvicorn 의존 정리를 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: yes (BAT-002 HTTP API 관련 구현 일부 RabbitMQ 로 대체) -- Status Recommendation: Approved -- Follow-up Action: REQ-021/022/023 과 반드시 동일 batch 로 deliver(big-bang 마이그레이션 R-5 회피) - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- HTTP `/v1` API + healthz + adapters/api + fastapi/uvicorn 의존 + TREX_API_HOST/PORT 폐기를 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: yes (BAT-002 HTTP API 관련 구현/테스트 일부 RabbitMQ 로 대체됨; tests/adapters/api/ 제거) -- Status Recommendation: Implemented -- Follow-up Action: release 단계에서 전략 모듈 cut-over 순서 합의. - -#### Validation Plan -- AC-1: tests/runtime/test_daemon_no_http_surface.py::test_adapters_api_tree_does_not_exist + test_no_fastapi_or_uvicorn_in_trex_core_source. -- AC-2: tests/runtime/test_daemon_no_http_surface.py::test_daemon_module_has_no_fastapi_or_uvicorn_import. -- AC-3: tests/runtime/test_daemon_no_http_surface.py::test_config_env_no_longer_references_api_host_or_port. -- AC-4: tests/runtime/test_daemon_no_http_surface.py::test_pyproject_dependencies_have_no_fastapi_or_uvicorn. -- AC-5: docs/discovery/dcy-002_…md 헤더 supersede 메모 + dcy-005 cross-reference 보유. -- AC-6: Daemon.run 의 signal handler + 포트 바인딩 0 코드 review. diff --git a/docs/srs/Interface/req-028_snapshot-read-message-contract-and-envelope.md b/docs/srs/Interface/req-028_snapshot-read-message-contract-and-envelope.md deleted file mode 100644 index 682a9d3..0000000 --- a/docs/srs/Interface/req-028_snapshot-read-message-contract-and-envelope.md +++ /dev/null @@ -1,297 +0,0 @@ -# req-028: 종목 스냅샷 read 메시지 계약 + envelope + degraded 신호 - -- Status: Deprecated -- Deprecated-By: REQ-073, REQ-074 (BAT-035, dcy-025, 2026-06-11) -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 전략 모듈이 dcy-006 snapshot RPC 로 종목 스냅샷을 받는 요청/응답 계약(transport-무관 logical)을 정의한다. 빈 응답과 가용성 degrade 의 시맨틱을 모호하지 않게 한다. - -## Requirement -- 스냅샷 read 는 dcy-006 의 `trex.snapshot.req` request-reply RPC 위에서 동작한다. 요청 payload 는 선택적 `strategy_key`·`view` 를 가진다. -- 응답은 **envelope** 다: `assets` 는 항상 배열(비면 `[]`, null 금지)이고, meta 로 `view`(에코), `available_slots`, `degraded`, `reason` 을 포함한다. -- 각 자산 항목은 symbol, asset_id, cash_balance, asset_quantity, active_strategy_id, 전략파라미터(opaque)를 담고 가격은 제외한다. -- (CHG-20260608-01) envelope 은 universe `assets[]` 와 **별도로** read-only `slots[]` 배열을 운반한다 — 데몬 슬롯 테이블의 per-slot 상태(`slot_id`·`status`(idle/holding/pending_buy/pending_sell)·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id`). 슬롯이 없어도 `[]`(null 금지). strategy 소비자는 무시 가능(additive). 운영자 CLI(`trex-core slots`)는 이 `slots[]` 를 표시한다. -- portfolio-hub 가 불가해 권위 cash/position 을 유지할 수 없으면(write `*_after` 갱신 불가 또는 일일 fetch 실패) 응답에 `degraded=true` + machine-readable reason code(예: `portfolio_hub_unavailable`/`snapshot_fetch_failed`/`cash_position_stale`) + sanitize 된 message 를 실어 전략의 신규 매매 보류를 신호한다(빈 리스트/silent stale 금지). - -## Acceptance Criteria -1. Given 정상 상태, When 스냅샷을 요청, Then `assets`(배열) + meta(`view`·`available_slots`·`degraded`·`reason`) envelope 를 반환하고, 자산이 없어도 `assets` 는 `[]`(null 아님)다. -2. Given portfolio-hub 불가(권위 cash/position 유지 불가), When 요청, Then `degraded=true` + reason code 가 포함되고 raw 예외/자격증명 등 민감정보는 노출되지 않는다. -3. Given 응답 항목, Then 필드는 symbol/asset_id/cash_balance/asset_quantity/active_strategy_id/strategy_params(opaque) 이고 가격 필드는 없다. -4. (CHG-20260608-01) Given 스냅샷 요청, Then envelope 에 read-only `slots[]` 배열을 포함한다 — 각 항목은 `slot_id`·`status`·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id` 를 담고, 슬롯이 없어도 `[]`(null 아님)다. `assets[]` 와 독립이며 strategy 소비자는 무시 가능(additive, 하위호환). -5. (CHG-20260610-01, dcy-023/issue #22) Given 스냅샷 응답, Then `slots[]` 항목에 `allocated_capital` 키가 없다 — 구현이 계약 외로 노출하던 죽은 필드를 제거해 AC-4 의 문서화된 필드 집합과 구현을 정렬한다. - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md, docs/discovery/dcy-016_20260608_operator-cli-rpc-commands.md, docs/interface-contract.md -- Modules: trex_core/adapters/messaging (snapshot RPC 핸들러), trex_core/application/portfolio.py (`build_snapshot_envelope` — `slots[]`, (CHG-20260610-01) allocated_capital 노출 제거), 응답 스키마(transport-무관), trex_core/runtime/cli.py (REQ-032 companion) -- Tests: envelope 스키마 검증(빈 응답 포함), degraded 주입 테스트, `slots[]` 스키마(빈 배열·status enum) + `trex-core slots` 실제 슬롯 상태 표시 - -## Notes -- transport 는 dcy-006(REQ-021/022) 의 snapshot RPC 를 사용하며 본 REQ 는 logical 계약만 정의한다. -- NFR-3(가용성 degrade, OQ-5) + OQ-6(빈 표현)을 본 REQ 로 흡수/반영한다. -- 정확한 reason code enum/필드 스키마는 구현 단계에서 확정한다(dcy-005 OQ-5/OQ-6 결정 범위 내). - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-1/FR-3 + OQ-5/OQ-6 분해로 최초 생성(Proposed). NFR-3 흡수. - -#### Intent -- 스냅샷 read 의 요청/응답 envelope 계약 + degrade 신호 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~3 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005, interface-contract -- Modules: - - adapters/messaging snapshot RPC 핸들러, 응답 스키마 -- Tests: - - envelope 스키마, degraded 주입 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req - -#### Validation Plan -- envelope 스키마 검증 + portfolio-hub 불가 주입으로 degraded AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. 구현 전 재검증 불필요. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- snapshot RPC envelope 응답 + degraded 신호 + sanitization 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_snapshot_handler.py::test_envelope_shape_returned + test_snapshot_selection.py::test_empty_envelope_assets_is_list_not_null. -- AC-2: tests/application/test_snapshot_handler.py::test_degraded_signal_does_not_leak_credentials + 실 daemon 운영 검증. -- AC-3: tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field. - -### CHG-20260608-01 -- Date: 2026-06-08 -- Author: project maintainers - -#### Change Summary -- dcy-016 후속 정정. `trex-core slots`(REQ-032)가 universe `assets`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭 — snapshot envelope 이 슬롯 상태 목록을 운반하지 않아 발생(운영자 관측: 10개 슬롯인데 universe portfolio 1개만, status 없음). envelope 에 **read-only per-slot 상태 목록 `slots[]` 를 additive 로 추가**한다(dcy-017 의 per-slot `watchdog` 동봉 선례와 동일 방식). BAT-007 에서 HTTP 슬롯 read(REQ-008/025) 제거 후 슬롯 상태 read 표면이 부재했던 점을 함께 해소. - -#### Intent -- 운영자가 데몬의 **실제 슬롯 상태(idle/holding/pending_buy/pending_sell)** 를 snapshot RPC 로 관측할 수 있게 한다. strategy-facing `assets[]`(universe) 계약은 그대로 유지하고, 슬롯 상태는 별도 `slots[]` 로 분리 노출한다. - -#### Acceptance Criteria Delta -- Added: - - AC-4: Given 스냅샷 요청, Then envelope 에 read-only `slots[]` 배열을 포함한다 — 각 항목은 `slot_id`·`status`·`symbol`·`qty`·`limit_price`·`avg_price`·`portfolio_id` 를 담고, 슬롯이 없어도 `[]`(null 아님)다. `assets[]`(universe) 와 독립이며 strategy 소비자는 무시 가능(additive, 하위호환). -- Updated: - - none (AC-1~3 의 `assets[]`/degraded/no-price 계약 불변) -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/interface-contract.md(snapshot 응답에 `slots[]` 필드 추가), dcy-005/dcy-016 -- Modules: - - trex_core/application/portfolio.py(`build_snapshot_envelope` 에 `slots[]`), trex_core/application/messaging_handlers.py(envelope), trex_core/runtime/cli.py(REQ-032 companion — `slots` 가 `slots[]` 를 읽도록, `universe` 는 `assets[]` 유지) -- Tests: - - envelope `slots[]` 스키마(빈 배열 포함), `trex-core slots` 가 universe 가 아닌 실제 슬롯 상태(status 포함)를 표시 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (REQ-028 최초 구현), BAT-020 (dcy-016 — `slots` CLI 가 갭 노출). 정정 구현은 신규 BAT-022 (예정). -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: yes (신규 AC-4) -- Existing Implementation Invalidated: no (AC-1~3 assets/degraded/no-price 근거 유지) -- Status Recommendation: Implemented → Approved (신규 AC-4 미구현) -- Follow-up Action: REQ-032 companion change-req + draft-batch(BAT-022) → 구현/검증 - -#### Validation Plan -- AC-4: envelope `slots[]` 스키마 테스트(빈 배열·status enum 포함) + `trex-core slots` 통합 검증(universe 가 아닌 실제 슬롯 상태 N개를 status 와 함께 표시). - -### CHG-20260608-02 -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-022 release-candidate verification evidence 기반(AC-4 envelope `slots[]`). - -#### Intent -- envelope `slots[]`(operator-facing per-slot 상태) 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-022 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no (BAT-022 에서 완료) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계 별도 진행 - -#### Validation Plan -- AC-4: tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field + test_snapshot_handler.py::test_envelope_shape_returned(envelope `slots` list). 운영 재현: 신규 코드 데몬 + 실 PostgreSQL/RabbitMQ 에서 `trex-core slots` → slots=10·status 표시·DB `(2026-06-08,idle,10)` 일치(BAT-022 verification). pytest 인프라 게이트는 Human Approval Memo 수용 잔여. - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-023/issue #22) snapshot envelope `slots[]` 에서 `allocated_capital` 노출 제거 — `build_snapshot_envelope`(trex_core/application/portfolio.py) 가 계약(AC-4 필드 집합) 외로 노출하던 죽은 필드를 삭제해 문서화된 계약과 구현을 정렬. AC-5 추가, Implemented→Approved(재구현 대기). - -#### Intent - -- Slot 도메인에서 allocated_capital 이 제거(REQ-002 CHG-20260610-02)됨에 따라 read 표면에서도 동일 필드를 제거. strategy/operator 소비자가 죽은 필드를 참조할 여지 차단. - -#### Acceptance Criteria Delta - -- Added: - - AC-5: slots[] 항목에 allocated_capital 키 부재. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Modules: trex_core/application/portfolio.py(build_snapshot_envelope slots[] 매핑) -- Tests: snapshot envelope slots[] 스키마 테스트에서 allocated_capital 부재 확인 + 기존 fixture 정리 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-023 batch -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (dcy-023 batch 에서 수행) -- Existing Implementation Invalidated: yes (slots[] 노출 필드 변경) -- Status Recommendation: Approved (Implemented→Approved 되돌림) -- Follow-up Action: dcy-023 batch 구현·검증 후 confirm-req-implemented. CLI slots detail 표시 제거는 REQ-032 CHG-20260610-07 이 소유. - -#### Validation Plan - -- dcy-023 batch: envelope slots[] 스키마 테스트(allocated_capital 부재) + 전체 suite green. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-025) 본 REQ 가 소유한 snapshot read 계약(`trex.snapshot.req` 요청 view/strategy_key, 응답 envelope `assets[]`/`available_slots`/`watchdog[]`/degraded)이 **폐기 예정** — `trex.slots.req`(REQ-073) + 슬롯 단일 행 스키마(REQ-074)로 대체된다(즉시 교체, dcy-025 OQ-1). degraded/reason/message health 신호 의미는 REQ-073 으로 승계. 현재 코드는 불변이므로 Implemented 유지(keep-current, REQ-005 CHG-20260610-01 선례), dcy-025 delivery 배치에서 표면 제거와 함께 **Deprecated** 전환. - -#### Intent - -- snapshot→slots 표면 교체의 추적성 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(dcy-025 delivery 배치에서 본 REQ 의 envelope AC 폐기) - -#### Impacted Area Delta - -- Docs: dcy-025, REQ-073/074/075 -- Modules: (예정) messaging_handlers.py snapshot 핸들러·portfolio.py envelope 빌더 제거/대체 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-025 delivery 배치 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점 — 코드 불변) -- Existing Implementation Invalidated: no (배치 구현 시점에 무효화·Deprecated 전환) -- Status Recommendation: keep-current -- Follow-up Action: dcy-025 배치에서 표면 제거 + 본 REQ Deprecated 전환. - -#### Validation Plan - -- dcy-025 배치 verification 으로 대체(snapshot 표면 부재 + slots 표면 계약 검증). diff --git a/docs/srs/Interface/req-029_asset-selection-slot-overlay-view-and-strategy-key.md b/docs/srs/Interface/req-029_asset-selection-slot-overlay-view-and-strategy-key.md deleted file mode 100644 index f468a8b..0000000 --- a/docs/srs/Interface/req-029_asset-selection-slot-overlay-view-and-strategy-key.md +++ /dev/null @@ -1,215 +0,0 @@ -# req-029: 자산 선별(slot overlay) + view 모드 + strategy_key 필터 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 스냅샷 응답에 담을 자산 집합을 core 의 권위 슬롯 상태로 선별한다. 보유(holding)는 포함하되 일시·불안전 상태는 마스킹하고, 전략 타입별 view 와 전략별 필터를 제공한다. - -## Requirement -- 응답 base 집합은 요청 `view`(`all` 기본 | `buyable` | `held`)로 선택한다: `all` = universe 전체(portfolio-hub 보유 + holding 슬롯), `buyable` = universe − 슬롯 점유, `held` = holding 슬롯 자산만. -- 마스킹(pending·warning·pause)은 전 모드에 균일 적용한다. pending 은 모든 모드에서 마스킹되므로 `held` 는 holding 만 포함한다. 포함/마스킹 판정은 core 권위 슬롯 상태 + 정합 결과 + pause 상태를 portfolio-hub 보유 여부보다 우선한다(slot overlay, 불변식 A). -- 선택적 `strategy_key`(요청 시) 는 자산의 단일 `active_strategy_id` 와 대소문자 구분 정확 일치로 직교 필터링한다. `strategy_key` 는 사람이 읽는 안정 유일 slug(예: `target-value-v1`)다. -- 최종 합성: `assets = base(view) − mask(pending·warning·pause) − (strategy_key 필터, 주어진 경우)`. `buyable` 은 `available_slots=0` 이면 `[]` 다. - -## Acceptance Criteria -1. Given 같은 종목이 holding 슬롯·pending 슬롯에 각각 있을 때, When `view=all`, Then holding 자산은 포함되고 pending 자산은 마스킹된다. -2. Given `available_slots=0`, When `view=buyable`, Then `[]` 를 반환한다. -3. Given `view=held`, Then holding 슬롯 자산만 반환하고 pending 은 제외한다. -4. Given `strategy_key` 제공, Then `active_strategy_id` 가 일치하는 자산만 반환하고, 생략 시 (마스킹 후) 전체 자산을 반환한다. -5. Given warning 또는 pause 자산, When 임의 `view`, Then 해당 자산은 마스킹된다. - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- Modules: trex_core/application 스냅샷 선별/slot overlay 로직(slot repository 재사용), strategy_key 필터 -- Tests: view 모드별·마스킹·strategy_key 필터 합성 테스트 - -## Notes -- core 권위 슬롯 상태(불변식 A)가 portfolio-hub 보유 여부보다 우선한다. -- OQ-4(in-flight 합성)·OQ-9(strategy_key)·OQ-11/OQ-12(view 모드) 반영. -- holding 자산의 보유 수량 표기를 core 슬롯 vs 스냅샷 중 무엇 기준으로 할지(holding→ledger flush 갭)는 설계 시 확정(dcy-005 OQ-4 잔여). -- (orb-v1 소비 참고, CHG-04) 첫 전략 orb-v1 은 `strategy_key` 없이 `view=buyable` 로 조회한다. 즉 전략 설정과 무관하게 universe 전체를 대상으로 하고, slot 점유(holding·pending)·warning(정합 불일치)·pause(사용자) 만 제외하면 된다 — 이는 `view=buyable` + `strategy_key` 생략의 기존 동작과 정확히 일치한다. pending(in-flight) 제외는 중복 주문 방지, warning 제외는 틀린 baseline 매매 차단(REQ-031) 목적으로 orb-v1 의도에 부합한다(사용자 확인). 따라서 단일 전략 단계에서는 `/v1/portfolios` PortfolioItem 의 전략 필드(active_strategy_id/strategy_params) 부재가 orb-v1 동작에 무해하다. (2번째 전략부터는 strategy_key 라우팅이 필요해져 전략 필드 부재 해소가 재대두된다.) - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-2/FR-8 + OQ-4/9/11/12 분해로 최초 생성(Proposed). - -#### Intent -- 응답 자산 선별(slot overlay·view·strategy_key) 규칙 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~5 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005 -- Modules: - - 스냅샷 선별/slot overlay, strategy_key 필터 -- Tests: - - view/마스킹/필터 테스트 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req - -#### Validation Plan -- 슬롯 상태 주입 후 view/마스킹/strategy_key 합성 테스트로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. 구현 전 재검증 불필요. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- `build_snapshot_envelope` 의 base(view) 산출 + masking OR 합성 + strategy_key 직교 필터 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_snapshot_selection.py::test_view_all_includes_holding_excludes_pending_for_same_symbol. -- AC-2: ::test_view_buyable_with_zero_available_slots_returns_empty. -- AC-3: ::test_view_held_returns_only_holding_slot_assets. -- AC-4: ::test_strategy_key_orthogonal_filter + ::test_strategy_key_missing_includes_everything_unmasked. -- AC-5: ::test_warning_overlay_masks_asset_uniformly_across_views + ::test_pause_overlay_masks_asset. - -### CHG-20260606-04 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- 첫 전략 orb-v1 의 스냅샷 소비 패턴을 Notes 참고 사항으로 추가(본문 비변경). AC/요구사항/계약 변경 없음. - -#### Intent -- orb-v1 은 전략 설정(active_strategy_id/strategy_params)과 무관하게 모든 자산을 대상으로 하며 `strategy_key` 없이 `view=buyable` 로 조회한다는 소비 계약을 명시해, `/v1/portfolios` PortfolioItem 의 전략 필드 부재가 orb-v1 에 무해함을 기록한다. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- Docs: - - 본 REQ Notes 에 orb-v1 소비 참고 사항 1건 추가 -- Modules: 없음 -- Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) — 영향 없음(기존 구현/검증 그대로 유효) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented 유지) -- Follow-up Action: orb-v1 Discovery 착수 시 본 참고 사항을 입력으로 사용. 2번째 전략 도입 시 strategy_key/strategy_params 필요 → PortfolioItem 전략 필드 부재 해소(detail strategy_set 보강 또는 별도 change-req) 재검토. - -#### Validation Plan -- 문서 전용 변경. 기존 AC-1~5 테스트 그대로 유효하며 재실행 불필요. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-018) 수량/현금 source 정합: 슬롯 오버레이(REQ-029)는 **유지**(lifecycle 중 슬롯 = 코어 권위 즉시값, 불변식 A). 스냅샷 응답에 미정산 ledger 가 있는 자산은 `cash_balance += pending delta` + `cash_estimated:true` 라벨로 **추정 현금 오버레이**(OQ-4 (i))를 노출하고, option D write-back 도착 시 권위값 수렴. BAT-023 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- "수량 live·현금 stale" 비대칭 제거 — 수량은 슬롯 오버레이(정확), 현금은 추정 오버레이(라벨)로 즉시성 확보 후 권위값 수렴. - -#### Acceptance Criteria Delta - -- Added: - - 스냅샷 자산에 추정 현금 오버레이(`cash_estimated` 플래그) — 미정산 outbox delta 기반. 슬롯 오버레이는 그대로 유지(변경 없음). - -#### Impacted Area Delta - -- Modules: `trex_core/application/messaging_handlers.py`(`_apply_cash_estimate`) -- Docs: `docs/interface-contract.md`(envelope `cash_estimated`) -- Tests: tests/application/test_ledger_option_d.py(estimate overlay) - -#### Delivery Trace Delta - -- Batches: BAT-023 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-023 에서 완료) -- Existing Implementation Invalidated: no (오버레이 유지 + 추정 additive) -- Status Recommendation: Implemented (change-req → BAT-023 재검증) -- Follow-up Action: confirm-req-implemented(BAT-023). - -#### Validation Plan - -- BAT-023: 미정산 자산 cash_estimated + 추정 cash 단위, write-back 후 수렴. diff --git a/docs/srs/Interface/req-032_runtime-pause-resume-control.md b/docs/srs/Interface/req-032_runtime-pause-resume-control.md deleted file mode 100644 index b84bf28..0000000 --- a/docs/srs/Interface/req-032_runtime-pause-resume-control.md +++ /dev/null @@ -1,464 +0,0 @@ -# req-032: 런타임 pause/resume 제어 - -- Status: Implemented -- Type: Interface -- Priority: Medium -- Owner: project maintainers - -## Intent -- 운영자가 특정 자산을 런타임에 매매에서 제외(pause)하거나 다시 포함(resume)할 수 있게 한다. 프로그램 이상·이벤트 시 안전 통제를 위한 수동 overlay 다. - -## Requirement -- pause/resume 단위는 `portfolio_id`(= asset_id)다. -- 제어는 **CLI** 와 **dcy-006 RPC 제어 메시지** 둘 다로 가능하며, 둘은 동일한 pause 상태를 변경한다. config 리로드 방식은 채택하지 않는다. -- pause 상태는 **Postgres 에 영속**(portfolio_id 키)하며 daemon 재시작 후에도, 일일 스냅샷 refresh 후에도 유지된다(refresh 는 universe/포지션만 갱신, pause overlay 불변). -- 적용 범위는 **전역** — pause 된 자산은 모든 view/전략 요청에서 마스킹된다. -- pause 는 request-time mask 이므로 resume 시 다음 요청부터 즉시 응답에 복귀한다. -- (CHG-20260609-01, issue #17) 운영자 CLI `enter` 는 시간 기반 자동청산을 위해 **`--forced-close-at ` (절대시간)** 옵션을 제공한다. CLI 는 이를 buy intent payload 의 기존 `forced_close_at` 필드에 매핑하며, 도메인·forced-close 루프(REQ-037)는 불변이다. 상대시간 옵션은 채택하지 않는다. - -## Acceptance Criteria -1. Given 운영자가 `portfolio_id` 를 pause, When 임의 `view`/`strategy_key` 로 스냅샷을 요청, Then 그 자산은 응답에서 마스킹된다. -2. Given pause 상태에서 daemon 재시작 또는 일일 refresh 발생, When 이후 요청, Then pause 가 유지된다. -3. Given 운영자가 resume, When 다음 요청, Then 그 자산이 응답에 다시 포함된다. -4. Given CLI 로 pause 후 RPC 제어로 조회(또는 그 반대), Then 동일한 pause 상태가 보인다. -5. (CHG-20260609-01) Given 운영자가 `trex-core enter ... --forced-close-at `, When buy intent 가 제출·admit, Then payload 에 파싱된 `forced_close_at` 절대시각이 실리고 admitted 슬롯의 `forced_close_at` 이 그 값으로 설정된다. -6. (CHG-20260609-01) Given `--forced-close-at` 미지정, Then payload 에 `forced_close_at` 을 포함하지 않고 기존 enter 동작이 불변이다. Given 잘못된 timestamp 형식, Then 제출 전 검증 오류로 거부된다. -7. (CHG-20260610-06, issue #23) Given 운영자가 `trex-core enter ` 에 `--qty` 를 지정하지 않으면, Then CLI 가 intent 제출 전에 즉시 오류를 반환하고 RPC 는 호출되지 않는다. -8. (CHG-20260610-07, dcy-023/issue #22) Given 운영자 CLI, Then `enter` 는 `--capital` 옵션을 받지 않고(지정 시 argparse unknown argument 거부), buy intent payload 에 `allocated_capital` 을 싣지 않으며, `slots` detail 표시에서 `allocated_capital` 행이 제거된다. - -## Impacted Area -- Docs: docs/discovery/dcy-005_20260604_portfolio-hub-asset-snapshot-api.md -- Modules: pause 상태 store(Postgres), CLI 제어, dcy-006 RPC 제어 메시지 핸들러, 응답 산출 시 pause 마스킹, (CHG-20260609-01) `trex_core/runtime/cli.py` `_cmd_enter` `--forced-close-at` 옵션 → buy intent `forced_close_at` 매핑, (CHG-20260610-06) `trex_core/runtime/cli.py` `_cmd_enter` `--qty` 미지정 시 제출 전 오류 처리, (CHG-20260610-07) `trex_core/runtime/cli.py` `--capital` argparse 제거 + payload `allocated_capital` 제거 + `_print_slot_detail` 행 제거 -- Tests: pause→호출→resume→호출 시퀀스, 재시작/refresh 후 영속, (CHG-20260609-01) enter `--forced-close-at` payload 매핑·형식 검증, (CHG-20260610-06) enter `--qty` 누락 오류 + RPC 미호출, (CHG-20260610-07) `--capital` argparse 거부 + payload allocated_capital 부재 - -## Notes -- pause(수동·영속) vs warning(자동·일시, REQ-031)은 독립 overlay 이며 OR 합성(둘 중 하나라도면 마스킹). -- OQ-10 반영. - -## Change Log - -### CHG-20260606-01 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- dcy-005 FR-9 + OQ-10 분해로 최초 생성(Proposed). - -#### Intent -- 런타임 pause/resume 제어(단위·제어 표면·영속·범위) 정의. - -#### Acceptance Criteria Delta -- Added: - - AC-1~4 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - dcy-005 -- Modules: - - pause store, CLI, RPC 제어, 마스킹 -- Tests: - - pause/resume 시퀀스, 영속 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req - -#### Validation Plan -- pause→호출→resume→호출 + 재시작 영속 테스트로 AC 검증. - -### CHG-20260606-02 -- Date: 2026-06-06 -- Author: project maintainers - -#### Change Summary -- 승인 게이트 통과 → Proposed → Approved. - -#### Acceptance Criteria Delta -- Added: none -- Updated: none -- Removed: none - -#### Impacted Area Delta -- 변경 없음 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch - -#### Validation Plan -- 승인 기록. 구현 전 재검증 불필요. - -### CHG-20260606-03 -- Date: 2026-06-06 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-008 release-candidate verification evidence 기반. - -#### Intent -- pause/resume CLI + RPC 제어 + Postgres 영속 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-008 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계는 별도 진행 - -#### Validation Plan -- AC-1: tests/application/test_pause_handler.py::test_pause_then_list + test_snapshot_selection.py::test_pause_overlay_masks_asset. -- AC-2: tests/adapters/portfolio/test_postgresql_pause_store.py::test_durability_simulated_via_fresh_store_instance. -- AC-3: tests/application/test_pause_handler.py::test_resume_clears_pause. -- AC-4: tests/application/test_pause_handler.py::test_cli_pause_visible_to_rpc_list_via_shared_store + 실 daemon RPC 운영 검증. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-016) 운영자 CLI 확장: `universe`/`slots`/`enter`/`exit` 서브커맨드 추가(전부 데몬 RabbitMQ RPC). 인스턴스 라우팅(`--config` canonical/`--instance` 단축/default), enter/exit 안전장치(확인 프롬프트 기본 ON + `--yes`(paper)/`--yes --live`(live 이중 ack)/`--dry-run`), 전 명령 `--json`. pause/resume/paused 는 기존 direct-DB 유지(OQ-3). BAT-020 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 운영자가 조회·매수(enter)·매도(exit)·일시정지를 CLI 로 수행하되, 매매는 데몬 admission(단일 writer)을 거쳐 불변식·portfolio_id 검증을 보존(DB 직접 쓰기 없음, NFR-1). - -#### Acceptance Criteria Delta - -- Added: - - universe/slots(snapshot RPC)·enter(buy intent RPC)·exit(sell intent RPC) CLI + 인스턴스 라우팅 + enter/exit 안전장치 + `--json`. - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(서브커맨드/가드/라우팅/RPC 클라이언트) -- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md` -- Tests: tests/runtime/test_cli_operator.py - -#### Delivery Trace Delta - -- Batches: BAT-020 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-020 에서 완료) -- Existing Implementation Invalidated: no (pause/resume 불변, 명령 추가) -- Status Recommendation: Implemented (change-req → BAT-020 재검증) -- Follow-up Action: confirm-req-implemented(BAT-020). - -#### Validation Plan - -- BAT-020: CLI universe/slots/enter/exit RPC payload·안전장치·라우팅·--json 단위. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017) `trex-core slots` 가 snapshot 응답의 `watchdog` 필드를 읽어 `expired=true` 슬롯을 **WATCHDOG-OWNED(전략 이탈)** 로 표시(+`watchdog_owned` 카운트). dcy-016 `slots` 확장. BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 운영자가 CLI 로 워치독 소유(전략 이탈) 슬롯을 즉시 인지. - -#### Acceptance Criteria Delta - -- Updated: - - `slots` 출력에 워치독 소유 표시(snapshot watchdog 필드 기반). - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(_cmd_slots watchdog 표시) -- Tests: tests/runtime/test_cli_operator.py::test_slots_marks_watchdog_owned - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (표시 추가) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: slots watchdog-owned 표시 단위. - -### CHG-20260608-03 - -- Date: 2026-06-08 -- Author: project maintainers - -#### Change Summary - -- (change-req, dcy-016 후속) `trex-core slots` 가 universe `assets[]`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭 정정(운영 관측: 10개 슬롯인데 universe 1개·status 없음). snapshot envelope 의 신규 read-only `slots[]`(REQ-028 CHG-20260608-01, AC-4)를 읽어 **실제 슬롯 상태(`slot_id`·`status`(idle/holding/pending_buy/pending_sell)·`symbol`·`qty`·`limit_price`·`avg_price`)** 를 표시한다. `universe` 명령은 `assets[]`(universe) 유지로 둘을 명확히 분리. watchdog-owned 표시(CHG-20260608-02)는 `slots[]` 항목과 결합. - -#### Intent - -- 운영자가 `trex-core slots` 로 데몬의 실제 슬롯 상태(idle/holding/pending_*)를 관측하게 한다. `universe`(거래 대상 portfolio)와 `slots`(데몬 슬롯 할당 상태)를 표면상 명확히 구분. - -#### Acceptance Criteria Delta - -- Updated: - - `slots` 출력은 universe `assets[]` 가 아니라 envelope `slots[]`(슬롯 상태, `status` 포함)를 표시한다. `--status held/holding/...` 필터는 슬롯 상태 기준으로 적용된다. -- Added: none -- Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(`_cmd_slots` → `resp["slots"]` 읽기). 전제: REQ-028 envelope `slots[]`(CHG-20260608-01). -- Tests: `slots` 가 universe 가 아닌 실제 슬롯 상태(status 포함)를 표시, universe 와 구분. - -#### Delivery Trace Delta - -- Batches: BAT-020 (`slots` 최초 구현 — 갭 발생). 정정은 신규 BAT-022 (REQ-028 AC-4 와 함께). -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes -- Existing Implementation Invalidated: yes (`slots` 가 universe assets 를 표시하던 부분이 슬롯 상태 의도와 불일치) -- Status Recommendation: Implemented → Approved (REQ-028 `slots[]` 의존, BAT-022 재구현/재검증) -- Follow-up Action: REQ-028 AC-4 구현 후 draft-batch(BAT-022) → 구현/검증 - -#### Validation Plan - -- BAT-022: `trex-core slots` 가 envelope `slots[]` 의 실제 슬롯 상태(status enum 포함)를 표시하고 universe(`assets[]`)와 구분됨을 단위/통합 검증. - -### CHG-20260608-04 -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved → Implemented. BAT-022 release-candidate verification evidence 기반(`slots` 가 envelope `slots[]` 표시, CHG-20260608-03). - -#### Intent -- `trex-core slots` 가 실제 슬롯 상태(status)를 표시하는 정정 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-022 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no (BAT-022 에서 완료) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계 별도 진행 - -#### Validation Plan -- tests/runtime/test_cli_operator.py::test_slots_shows_real_slot_state_not_universe / ::test_slots_status_filters_by_slot_status / ::test_slots_marks_watchdog_owned. 운영 재현(BAT-022): 신규 코드 데몬 + 실 인프라 `trex-core slots` → slots=10 status·`--status holding`=0·universe(assets=1) 분리·비침습. pytest 인프라 게이트는 Human Approval Memo 수용 잔여. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #17) `trex-core enter` 에 **`--forced-close-at ` (절대시간) 옵션** 추가. 운영자가 매수 진입 명령 하나로 시간 기반 자동청산(forced_close) 시각을 지정. CLI 는 이 값을 buy intent payload 의 **기존** `forced_close_at` 필드에 매핑한다(계약: REQ-007/021 buy intent body 가 이미 `forced_close_at` optional 수용, 도메인 `Slot.forced_close_at`(REQ-066), forced-close 만기 매도 루프 REQ-037 — 전부 구현·불변). 상대시간(`--hold-minutes`)은 **미채택**(사용자 결정: 절대시간만). - -#### Intent - -- 운영자가 진입 명령 하나로 시간 기반 매매 계획(자동청산 시각 포함)을 표현하게 한다. 현재는 `--stop-loss`/`--take-profit` 만 노출돼 시간 기반 청산은 별도 수동/스케줄 `exit` 가 필요했다(issue #17 갭). 도메인 능력은 이미 존재하고 CLI 노출만 보완. - -#### Acceptance Criteria Delta - -- Added: - - AC-5: `trex-core enter ... --forced-close-at ` 가 주어지면 CLI 는 buy intent payload 에 `forced_close_at`(파싱된 절대시각)을 실어 제출하고, admitted 슬롯의 `forced_close_at` 이 그 값으로 설정된다. - - AC-6: `--forced-close-at` 미지정 시 payload 에 `forced_close_at` 을 포함하지 않으며 기존 enter 동작은 불변(하위호환). 잘못된 timestamp 형식은 제출 **전** 검증 오류로 거부한다. -- Updated: none -- Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(`_cmd_enter` payload 에 `forced_close_at` 추가 + `--forced-close-at` 인자/파싱·검증). buy intent 핸들러·도메인·forced-close 루프는 불변(이미 수용). -- Docs: `docs/interface-contract.md`(enter 옵션 표 + buy intent payload `forced_close_at` 노출), 필요 시 `docs/runtime-flows.md`. -- Tests: `tests/runtime/test_cli_operator.py`(enter `--forced-close-at` → payload `forced_close_at` 매핑 + 잘못된 형식 거부). - -#### Delivery Trace Delta - -- Batches: 신규 batch-lite(차기 BAT) — enter `--forced-close-at` 노출. -- Releases: none (차기 release 또는 통합 편입). - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 AC-5/6 검증) -- Existing Implementation Invalidated: no (pause/resume/universe/slots/enter/exit 기존 AC 불변, 옵션 additive) -- Status Recommendation: Implemented → Approved (신규 AC-5/6 미구현 — batch 재검증 후 Implemented 복귀; 기존 AC-1~4 및 CLI 명령은 구현 유지) -- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verification → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: `enter --forced-close-at ` → buy intent payload 에 `forced_close_at` 정확 매핑 단위, 잘못된 형식(예 `--forced-close-at notatime`) 제출 전 거부 단위, 미지정 시 payload 미포함(회귀). (가능 시) 실 데몬 e2e: `enter --forced-close-at <근접시각>` → admitted 슬롯 `forced_close_at` 설정 → forced-close 루프(REQ-037) 만기 매도 확인. - -### CHG-20260609-02 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- Status 변경: Approved → Implemented. BAT-024 release-candidate verification evidence 기반(CHG-20260609-01 AC-5/6). - -#### Intent - -- 운영자 `enter --forced-close-at`(절대시간) → buy intent `forced_close_at` 매핑 구현 완료 동기화. - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta - -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta - -- Batches: - - BAT-024 (release-candidate) -- Releases: - - none - -#### Revalidation Impact - -- Reverification Needed: no (BAT-024 에서 완료) -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: release 단계 별도 진행(차기 REL 또는 통합 편입) - -#### Validation Plan - -- tests/runtime/test_cli_operator.py::test_enter_with_forced_close_at_maps_to_payload (AC-5) / ::test_enter_without_forced_close_at_omits_field (AC-6 하위호환) / ::test_enter_rejects_malformed_forced_close_at_before_submit (AC-6 형식 거부). 전체 단위 353 passed, ruff clean. - -### CHG-20260610-06 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #23) `trex-core enter`에 **`--qty` 미지정 시 제출 전 즉시 오류 반환** 추가. 현재 `--qty` 생략 → `qty=None` → admitted 응답 → 집행 루프 KIS 거절 → `pending_buy→inactive` 자동 롤백으로 "접수 성공처럼 보이지만 실제 매수 안 됨" 혼동 발생. CLI 단에서 admission 전에 차단. - -#### Intent - -- 운영자가 수량 없이 `enter`를 실행하면 RPC 제출 이전에 즉시 오류를 보고 혼동 없이 재시도할 수 있도록 한다. - -#### Acceptance Criteria Delta - -- Added: - - AC-7: `trex-core enter ` 에 `--qty` 를 지정하지 않으면 CLI 가 intent 제출 전에 즉시 오류를 반환하고 RPC 는 호출되지 않는다. -- Updated: none -- Removed: none - -#### Impacted Area Delta - -- Modules: `trex_core/runtime/cli.py`(`_cmd_enter` 또는 argparse에 `--qty` 검증 추가) -- Tests: `tests/runtime/test_cli_operator.py`(enter `--qty` 누락 오류 + RPC 미호출 검증) - -#### Delivery Trace Delta - -- Batches: 신규 batch-lite(차기 BAT) — enter `--qty` 필수 검증 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch-lite 에서 AC-7 검증) -- Existing Implementation Invalidated: no (AC-1~6 기존 동작 불변, additive 검증 추가) -- Status Recommendation: Implemented → Approved (AC-7 미구현; 기존 AC-1~6 구현은 유효) -- Follow-up Action: draft-batch(batch-lite) → run-batch-implementation → verify → confirm-req-implemented - -#### Validation Plan - -- 신규 batch-lite: `enter` 에 `--qty` 없이 실행 → rc=1 + RPC calls=0 단위. `--qty 10`과 함께 실행 → 정상 submit 불변(회귀) 단위. - -### CHG-20260610-07 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-023/issue #22) 운영자 CLI 에서 `--capital` 옵션 제거 — `enter` argparse 인자 삭제(지정 시 unknown argument 거부), `_cmd_enter` summary·payload 의 `allocated_capital` 제거, `slots` detail(`_print_slot_detail`)의 `allocated_capital` 행 제거. AC-8 추가, Implemented→Approved(재구현 대기). - -#### Intent - -- `--capital` 이 사이징한다는 오해를 CLI 표면에서 원천 제거. 실제 수량은 `--qty`(AC-7 필수)만이 결정하며, capital 은 어떤 경로에서도 소비되지 않음(dcy-023 §3 전수 추적). - -#### Acceptance Criteria Delta - -- Added: - - AC-8: `--capital` argparse 거부 + payload allocated_capital 부재 + slots detail 행 제거. -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta - -- Modules: trex_core/runtime/cli.py(`--capital` 인자, `_cmd_enter` summary/payload, `_print_slot_detail`) -- Tests: tests/runtime/test_cli_operator.py — 기존 enter 테스트의 `--capital` 인자 제거 + argparse 거부 테스트 추가 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-023 batch. BAT-033(AC-7, release-candidate)의 enter 테스트와 같은 파일을 수정하므로 dcy-023 batch 에서 정합 유지. -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (dcy-023 batch 에서 수행) -- Existing Implementation Invalidated: yes (enter 인터페이스 변경 — 기존 --capital 사용 테스트/스크립트 무효) -- Status Recommendation: Approved (Implemented→Approved 되돌림) -- Follow-up Action: dcy-023 batch 구현·검증 후 confirm-req-implemented. AC-1~7 의 기존 evidence 는 유지되나 enter 표면 변경분 재검증 필요. - -#### Validation Plan - -- dcy-023 batch: `enter --capital` argparse 거부(단위) + enter payload allocated_capital 부재 + slots detail 표시 + 전체 suite green. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-034_per-slot-watchdog.md b/docs/srs/Interface/req-034_per-slot-watchdog.md deleted file mode 100644 index 784f82b..0000000 --- a/docs/srs/Interface/req-034_per-slot-watchdog.md +++ /dev/null @@ -1,236 +0,0 @@ -# req-034: per-slot 워치독 — snapshot RPC 기반 전략 모듈 활성 감지 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -전략 모듈이 슬롯을 활성 관리 중인지 데몬이 감지하기 위해, snapshot RPC 응답 시 해당 슬롯들의 `last_seen_at` 타임스탬프를 in-memory 워치독 store에 기록한다. `TREX_WATCHDOG_TIMEOUT` 이상 갱신되지 않은 슬롯은 "전략 모듈 미감시" 상태로 판정해 자율 안전 매도 체크 대상이 된다(dcy-007 FR-5). - -## Requirement - -- 시스템은 `trex.snapshot.req` 요청을 처리할 때, 응답 envelope에 포함되는 `holding` 슬롯들의 `last_seen_at`을 in-memory 워치독 store(`dict[slot_id, datetime]`)에 현재 시각(UTC 또는 KST 일관 적용)으로 갱신해야 한다. -- 시스템은 `now() − last_seen_at > TREX_WATCHDOG_TIMEOUT(초)` 조건이 성립하는 슬롯을 "워치독 만료(watchdog-expired)"로 판정해야 한다. -- 데몬 기동 후 `TREX_WATCHDOG_GRACE_PERIOD(초)` 동안은 워치독 만료 판정을 유예해야 한다(grace period). grace period 중에는 어떤 슬롯도 만료로 판정하지 않는다. -- 워치독 store는 Postgres에 영속하지 않으며 데몬 재시작 시 초기화된다. - -## Acceptance Criteria - -1. Given `trex.snapshot.req` handler가 응답을 산출할 때, When 응답 슬롯 목록에 `slot_id = S`가 포함되면, Then `watchdog_store[S]`가 현재 시각으로 갱신된다. -2. Given `TREX_WATCHDOG_TIMEOUT=120`이고 slot S의 `watchdog_store[S]`가 121초 전일 때, When 워치독 만료 여부를 평가하면, Then S는 만료로 판정된다. -3. Given `TREX_WATCHDOG_TIMEOUT=120`이고 slot S의 `watchdog_store[S]`가 60초 전일 때, When 워치독 만료 여부를 평가하면, Then S는 만료로 판정되지 않는다. -4. Given 데몬이 기동된 지 `TREX_WATCHDOG_GRACE_PERIOD=60` 초 이내일 때, When 워치독 만료 평가를 수행하면, Then 모든 슬롯의 만료 판정이 유예된다(store 비어 있는 슬롯 포함). -5. Given 데몬 재시작이 발생하면, When 기동 직후 워치독 store를 조회하면, Then store는 비어 있다. -6. Given 전략 모듈이 `TREX_WATCHDOG_TIMEOUT / 2` 미만 주기로 snapshot RPC를 보낼 때, When 정상 동작 중이면, Then 해당 슬롯의 워치독이 만료되지 않는다(NFR-4 관계식 검증). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — 워치독 갱신 흐름 추가 (REQ-042 참조) - - `docs/interface-contract.md` — snapshot RPC가 watchdog 갱신 신호임을 명시 (REQ-042 참조) -- Modules: - - `trex_core/application/messaging_handlers.py` — `make_snapshot_rpc_handler` 확장: 응답 슬롯의 `last_seen_at` 갱신 - - `trex_core/application/portfolio.py` 또는 신규 `trex_core/application/watchdog.py` — WatchdogStore (`dict[slot_id, datetime]`), grace period 시작 타임스탬프 관리 -- Tests: - - `make_snapshot_rpc_handler` 단위 테스트 — `last_seen_at` 갱신 확인 - - 워치독 만료 판정 단위 테스트 — 만료/비만료/grace period 시나리오 - -## Notes - -- 워치독 store 갱신은 snapshot RPC 응답 슬롯 기준이다. intent RPC(매수/매도)는 갱신 신호가 아니다(`update_thresholds` 액션은 제외 — REQ-040 참조). -- NFR-4 권장 관계식: `snapshot RPC 호출 주기 < TREX_WATCHDOG_TIMEOUT / 2`. 기본값 기준 전략 모듈은 60초 이내 주기로 snapshot RPC를 호출해야 한다. 관계식 위반 시 정상 동작 슬롯에서도 워치독 오탐 발생. -- 워치독 store 구현 위치(portfolio.py 확장 vs 신규 watchdog.py)는 batch planning에서 결정한다. -- `TREX_WATCHDOG_TIMEOUT`, `TREX_WATCHDOG_GRACE_PERIOD` env var 파싱은 REQ-041 참조. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-034 초안 생성 - -#### Intent - -- per-slot 워치독 메커니즘(FR-5)을 독립 구현·검증 단위로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리) -- Modules: messaging_handlers.py, watchdog store 신규/확장 -- Tests: snapshot handler + 워치독 판정 단위 테스트 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-016) 워치독 갱신 부수효과를 운영자 조회에서 억제: snapshot RPC 핸들러가 `touch_watchdog=false`/`actor="operator"` 요청에 대해 holding 슬롯 `last_seen_at` 갱신을 건너뛴다. 전략 폴링 기본 동작은 불변(REQ-034 핵심 의미 유지). BAT-020 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 운영자 CLI(`universe`/`slots`) 조회가 워치독 생존 신호로 작동해 죽은 전략의 safety-sell 을 억제하는 안전 결함(R-4)을 제거. - -#### Acceptance Criteria Delta - -- Updated: - - 워치독 `last_seen_at` 갱신은 전략 폴링(기본)에만 적용; 운영자 억제 플래그 요청은 제외. - -#### Impacted Area Delta - -- Modules: `trex_core/application/messaging_handlers.py`(snapshot 핸들러 워치독 분기) -- Tests: tests/application/test_messaging_snapshot_consumer.py::test_operator_query_suppresses_watchdog_touch - -#### Delivery Trace Delta - -- Batches: BAT-020 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-020 에서 완료) -- Existing Implementation Invalidated: no (기본 전략 폴링 동작 불변) -- Status Recommendation: Implemented (change-req → BAT-020 재검증) -- Follow-up Action: confirm-req-implemented(BAT-020). - -#### Validation Plan - -- BAT-020: operator 요청 시 watchdog.update 미호출, 기본 요청 시 호출 단위. - -### CHG-20260608-02 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-017/issue #12) 워치독 상태 가시화: `WatchdogStore.last_seen(slot_id)` read-only getter 추가, snapshot RPC 응답에 holding 슬롯 워치독 상태(`expired`/`last_seen`) read-only 동봉(best-effort). 판정 로직·timeout 불변(관측만 추가). BAT-021 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 전략 이탈로 슬롯이 안전망(워치독) 소유로 넘어간 상태를 운영자가 인지 가능하게(무성 → 가시화). - -#### Acceptance Criteria Delta - -- Added: - - snapshot 응답 워치독 상태 노출 + `last_seen` getter. 판정 동작 불변. - -#### Impacted Area Delta - -- Modules: `trex_core/application/watchdog.py`(last_seen), `trex_core/application/messaging_handlers.py`(_attach_watchdog) -- Tests: tests/application/test_messaging_snapshot_consumer.py::test_snapshot_exposes_watchdog_state_for_holding_slots - -#### Delivery Trace Delta - -- Batches: BAT-021 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-021 에서 완료) -- Existing Implementation Invalidated: no (관측만 추가, 판정 불변) -- Status Recommendation: Implemented (change-req → BAT-021 재검증) -- Follow-up Action: confirm-req-implemented(BAT-021). - -#### Validation Plan - -- BAT-021: 워치독 만료 holding 슬롯의 expired/last_seen 노출 단위. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-025) 워치독 갱신 트리거 표면을 `trex.snapshot.req` → **`trex.slots.req`**(REQ-073)로 정합한다. 갱신 시맨틱: 기본 조회(holding 슬롯 포함)가 `last_seen_at` 을 터치하고, **`spy=true` 면 무터치**(기존 `actor="operator"`/`touch_watchdog=false` ad-hoc 플래그의 일반화·대체). 응답 노출은 top-level `watchdog[]` 배열이 아니라 슬롯 행의 `watchdog:{expired,last_seen}`(REQ-074). 만료 판정·grace period·in-memory store 는 불변. - -#### Intent - -- read 표면 교체 후에도 전략 폴링=생존 신호 / 비침습 조회 구분(R-4)을 보존. - -#### Acceptance Criteria Delta - -- Updated: - - 워치독 갱신 트리거: `trex.snapshot.req` 처리 시 → `trex.slots.req` 기본 조회 시(holding 슬롯 last_seen 갱신). - - 비침습 조회: `actor="operator"`/`touch_watchdog=false` → `spy=true`(무터치). - - 노출: 응답 `watchdog[]` 배열 → 슬롯 행 `watchdog:{expired,last_seen}`. -- Added/Removed: none - -#### Impacted Area Delta - -- Modules: messaging_handlers.py(터치 분기), portfolio.py(slot_to_dict watchdog 흡수) -- Docs: docs/interface-contract.md(REQ-075) -- Tests: spy 무터치/기본 터치, 행 watchdog 필드 - -#### Delivery Trace Delta - -- Batches: 신규 batch(REQ-073/074/075 와 동일 배치, dcy-025) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 갱신 AC 검증) -- Existing Implementation Invalidated: yes (snapshot 표면 기반 터치·watchdog[] 노출 evidence 가 표면 교체로 무효화) -- Status Recommendation: Implemented → Approved (batch 재구현·재검증 후 복귀) -- Follow-up Action: draft-batch → 구현/검증 → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: 기본 조회 터치/spy 무터치 단위(REQ-073 AC-4 와 공유), 행 watchdog 필드(REQ-074 AC-1), 만료·grace 회귀. diff --git a/docs/srs/Interface/req-036_auto-sell-internal-path.md b/docs/srs/Interface/req-036_auto-sell-internal-path.md deleted file mode 100644 index a17636f..0000000 --- a/docs/srs/Interface/req-036_auto-sell-internal-path.md +++ /dev/null @@ -1,151 +0,0 @@ -# req-036: 자동 매도 내부 경로 — SlotService.sell + idempotency - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -자율 안전 매도 트리거가 RabbitMQ intent RPC를 거치지 않고 직접 슬롯 매도를 제출할 수 있도록 `SlotService.sell()` 내부 메서드를 추가한다. RabbitMQ 장애와 독립적으로 동작해야 하는 transport-independent 매도 경로다. 이미 `pending_sell` 상태인 슬롯에 대한 중복 트리거를 방지하는 idempotency도 함께 보장한다(dcy-007 FR-3, FR-4, A-1). - -## Requirement - -- `SlotService`는 `sell(slot_id, actor, reason)` 메서드를 제공해야 한다. 이 메서드는 `holding` 슬롯을 `pending_sell`로 전이시키고 기존 outbox 내구성·execution 루프 경로를 따른다. -- `sell()`은 내부 함수 직접 호출이며 RabbitMQ intent RPC를 거치지 않는다. -- 대상 슬롯이 이미 `pending_sell` 상태이면 `sell()`은 추가 전이 없이 조용히 반환하거나 멱등 응답을 반환해야 한다(중복 트리거 방지). -- 대상 슬롯이 `holding` 이 아니고 `pending_sell`도 아닌 상태이면 적절한 예외 또는 오류 응답을 반환한다. -- `sell()`을 통해 제출된 매도는 기존 `execution` worker의 `transit_slot(holding→pending_sell)` + broker dispatch + ledger 기록 경로를 재사용해야 한다. - -## Acceptance Criteria - -1. Given `holding` 상태의 슬롯 S가 있을 때, When `SlotService.sell(S, actor="safety-sell", reason="stop_loss")` 를 호출하면, Then S의 상태가 `pending_sell`로 전이된다. -2. Given `pending_sell` 상태의 슬롯 S가 있을 때, When `sell(S, ...)` 를 다시 호출하면, Then 슬롯 상태가 변경되지 않고 중복 매도 intent가 제출되지 않는다. -3. Given `idle` 상태의 슬롯 S가 있을 때, When `sell(S, ...)` 를 호출하면, Then 오류(예외 또는 rejected 응답)가 반환되고 슬롯 상태는 변경되지 않는다. -4. Given `sell()` 호출 성공 시, When execution worker가 이후 처리하면, Then 매도 주문이 broker에 제출되고 outbox 및 ledger 경로가 기존 intent RPC 경로와 동일하게 작동한다. -5. Given RabbitMQ transport가 불가용 상태일 때, When `sell()` 을 호출하면, Then transport 상태와 무관하게 `holding→pending_sell` 전이가 성공한다. - -## Impacted Area - -- Docs: 없음 (런타임 흐름 변경은 REQ-037~039에서 발생, baseline 문서 갱신은 REQ-042 참조) -- Modules: - - `trex_core/domain/slot/service.py` — `SlotService.sell(slot_id, actor, reason)` 메서드 추가 -- Tests: - - `SlotService.sell()` 단위 테스트 — `holding→pending_sell` 전이 확인 - - idempotency 테스트 — `pending_sell` 슬롯 중복 호출 시 무전이 확인 - - 잘못된 상태(`idle`) 슬롯 호출 시 오류 반환 확인 - -## Notes - -- `sell()`은 `transit_slot(slot_id, from_state=HOLDING, to_state=PENDING_SELL, ...)` 패턴 위에 구현한다. 기존 advisory lock + Postgres commit 보장이 그대로 적용된다. -- NFR-2(자동 매도 실패 시 outbox retry): broker 오류 발생 시 execution worker의 기존 retry 경로를 따른다. 별도 retry 로직 불필요. -- `actor` 값은 트리거 구분을 위해 `"safety-sell"`, `"forced-close"`, `"market-close"` 등으로 구체화하는 것을 권장한다. 정확한 값은 batch 설계에서 결정한다. -- 이 메서드는 dcy-007 FR-3의 "내부 자동 매도 trigger 경로"를 표준화한다. 이후 kill-switch Discovery(trex-core#3)에서도 재사용 가능하도록 격리한다. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-036 초안 생성 - -#### Intent - -- 자동 매도 내부 경로(FR-3, FR-4)를 독립 Interface REQ로 정규화 - -#### Acceptance Criteria Delta - -- Added: AC-1~5 전체 - -#### Impacted Area Delta - -- Modules: service.py (`sell()` 메서드 추가) -- Tests: 단위 테스트 3종 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012) `SlotService.sell` 에 optional `limit_price` 파라미터(default None) 추가. 자율 매도(safety-sell/kill-switch)는 가격 미전달 → 시장가로 즉시 체결(미체결 pending_sell 갇힘 방지), 재량 경로만 지정가 전달 가능. BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 자동 매도 내부 경로가 매수 지정가에 걸리지 않고 시장가로 체결되도록(issue #10/#9 연계), 가격 파라미터를 optional 로 추가하되 기본값 None 으로 자율 경로 체결성을 보장. - -#### Acceptance Criteria Delta - -- Updated: - - `SlotService.sell(..., limit_price=None)` — 미전달 시 시장가(슬롯 limit_price None), 전달 시 지정가. - -#### Impacted Area Delta - -- Modules: `trex_core/domain/slot/service.py`(`sell`) -- Tests: tests/application/test_sell_pricing.py::test_safety_sell_dispatches_at_market - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: no (시그니처 확장, 기존 호출 default None 동일 동작) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: svc.sell 가격 미전달 → 시장가 + closed 전이 단위. diff --git a/docs/srs/Interface/req-040_update-thresholds-intent-rpc.md b/docs/srs/Interface/req-040_update-thresholds-intent-rpc.md deleted file mode 100644 index 3b56bf9..0000000 --- a/docs/srs/Interface/req-040_update-thresholds-intent-rpc.md +++ /dev/null @@ -1,151 +0,0 @@ -# req-040: update_thresholds intent RPC 액션 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -전략 모듈이 매수 이후에도 손절·익절 임계값과 강제 만기 시각을 갱신할 수 있도록 `trex.intent.req` 채널에 `action: "update_thresholds"` 메시지 액션을 추가한다. 수신 시 해당 슬롯의 `stop_loss_price`·`take_profit_price`·`forced_close_at`를 DB에 갱신하고, 해당 슬롯의 워치독 `last_seen_at`을 현재 시각으로 리셋해 전략 모듈 활성 신호로 활용한다(dcy-007 FR-7). - -## Requirement - -- 시스템은 `trex.intent.req` 채널의 `action: "update_thresholds"` 메시지를 처리해야 한다. -- 메시지 payload는 `{action: "update_thresholds", slot_id, stop_loss_price?, take_profit_price?, forced_close_at?}` 형식이며, 세 필드는 각각 독립적으로 포함하거나 생략할 수 있다. 생략된 필드는 현재 값을 유지한다. -- 처리 성공 시 요청한 필드를 DB에 갱신하고 해당 슬롯의 워치독 `last_seen_at`을 현재 시각으로 리셋해야 한다. -- 대상 슬롯이 `holding` 상태인 경우에만 갱신을 수행한다. -- 대상 슬롯이 `holding`이 아닌 경우의 처리(무시 또는 에러 응답)는 구현 시 결정한다(OQ-6 Deferred — 선택 옵션: `{status: ignored, reason: "not_holding"}` 응답 또는 silent discard). -- `reply_to`가 있으면 갱신 결과(`{status: updated|ignored, slot_id}`)를 응답으로 반환한다. - -## Acceptance Criteria - -1. Given `holding` 상태의 슬롯 S가 있을 때, When `{action: "update_thresholds", slot_id: S, stop_loss_price: 50000}`을 `trex.intent.req`에 발행하면, Then S의 `stop_loss_price`가 `50000`으로 DB에 갱신된다. -2. Given `holding` 상태의 슬롯 S가 있을 때, When `{action: "update_thresholds", slot_id: S, take_profit_price: 80000, forced_close_at: "2026-06-07T15:10:00+09:00"}`를 발행하면, Then S의 `take_profit_price`와 `forced_close_at`가 DB에 갱신된다. -3. Given `holding` 상태의 슬롯 S가 있을 때, When `update_thresholds`를 수신하면, Then S의 워치독 `last_seen_at`이 현재 시각으로 리셋된다. -4. Given `update_thresholds` 메시지에 `stop_loss_price`만 포함되고 `take_profit_price`와 `forced_close_at`는 생략될 때, When 처리되면, Then `stop_loss_price`만 갱신되고 나머지 두 필드는 기존 값을 유지한다. -5. Given `null` 값으로 `stop_loss_price: null`을 명시할 때, When 처리되면, Then `stop_loss_price`가 `NULL`로 갱신된다(임계값 해제). -6. Given 대상 슬롯이 `holding`이 아닌 상태일 때, When `update_thresholds`를 수신하면, Then 슬롯 필드가 변경되지 않는다. `reply_to`가 있으면 `{status: ignored, reason: "not_holding"}` 응답을 반환한다(OQ-6 구현 결정). -7. Given `reply_to`가 있는 요청에서 갱신이 성공하면, When 처리가 완료되면, Then `{status: updated, slot_id}`가 `reply_to`로 반환된다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — `update_thresholds` action 계약 추가 (REQ-042 참조) -- Modules: - - `trex_core/application/messaging_handlers.py` — `make_intent_rpc_handler` 확장: `update_thresholds` action 라우팅 + DB 갱신 + `last_seen_at` 리셋 - - `trex_core/domain/slot/service.py` — `SlotService.update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)` 메서드 추가 -- Tests: - - `update_thresholds` handler 단위 테스트 — holding 슬롯 갱신 확인, last_seen_at 리셋 확인 - - 필드 부분 갱신 테스트 (일부만 포함된 payload) - - null 값 처리 테스트 - - non-holding 슬롯 무시 테스트 - -## Notes - -- OQ-6(Deferred → REQ AC): `holding`이 아닌 슬롯 요청 처리 방식. 구현 시 `{status: ignored, reason: "not_holding"}` 응답 반환을 권장한다. silent discard도 허용되며 batch에서 결정한다. -- `update_thresholds` 수신은 워치독 `last_seen_at` 리셋을 겸한다. 전략 모듈이 임계값을 갱신하면 활성 신호로 처리돼 그 시점부터 `TREX_WATCHDOG_TIMEOUT` 타이머가 재시작된다(REQ-034). -- 기존 intent RPC dedup(`intent_log.try_insert`) 적용 여부는 batch 설계에서 결정한다. `update_thresholds`는 상태 read-modify가 아닌 idempotent SET이므로 dedup 생략이 적절할 수 있다. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-007 confirmed → REQ-040 초안 생성 - -#### Intent - -- update_thresholds intent RPC 액션(FR-7)을 독립 Interface REQ로 정규화. OQ-6 Deferred 처리 방식을 AC-6에 명시. - -#### Acceptance Criteria Delta - -- Added: AC-1~7 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md (REQ-042에서 처리) -- Modules: messaging_handlers.py (handler 확장), service.py (`update_thresholds()` 추가) -- Tests: handler 단위 테스트 4종 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req) - -#### Intent - -- 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs 후 batch 배정 - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). - -#### Intent - -- BAT-011 verification.md 의 AC↔evidence(`update_thresholds` 부분 갱신·null 해제·last_seen_at 리셋·non-holding ignored·미존재 slot_not_found·updated 응답)로 AC-1~7 충족 → 구현 완료 동기화. OQ-6 은 AC-6 의 `{ignored, not_holding}` 응답으로 확정 구현. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-011 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-011 배포 편성. diff --git a/docs/srs/Interface/req-043_control-channel-action-dispatch.md b/docs/srs/Interface/req-043_control-channel-action-dispatch.md deleted file mode 100644 index 8ad2496..0000000 --- a/docs/srs/Interface/req-043_control-channel-action-dispatch.md +++ /dev/null @@ -1,148 +0,0 @@ -# req-043: trex.control.req 제어 채널 + action dispatch - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -기존 `trex.pause.req` 단일 목적 큐를 `trex.control.req` 제어 채널로 개념 확장하고, payload `action` 필드 기반 dispatch로 운영자 제어 명령을 단일 채널에서 처리한다. 기능마다 큐/consumer 스레드를 늘리지 않기 위한 단일 제어 채널 + action discriminator 패턴(dcy-008 OQ-1 Answered). 기존 pause/resume/list 동작을 보존하면서 kill-switch(REQ-045)와 resume_kill_switch(REQ-046) 라우팅 진입점을 추가한다. - -## Requirement - -- 데몬은 단일 제어 큐 `trex.control.req` 를 소비하고 메시지 payload 의 `action` 필드로 핸들러를 분기한다. -- 기존 제어 action(`pause`/`resume`/`list`, REQ-032)의 동작·응답 계약을 그대로 유지한다. -- 신규 action 라우팅을 추가한다: `kill_switch`(REQ-045), `resume_kill_switch`(REQ-046). 본 REQ는 라우팅·메시지 계약 골격만 정의하고 실제 청산/해제 동작은 각 REQ에서 정의한다. -- 알 수 없는 `action` 은 `{status: "error", reason: "unknown_action:"}` 으로 응답한다. -- 기존 `trex.pause.req` consumer 스레드를 control consumer(`trex.control.req`)로 교체한다. 데몬 워커 스레드 총수는 불변(7개 유지). -- 하위호환: `trex.pause.req` 를 사용하던 기존 클라이언트 호환 방식(별칭 큐 유지 vs `trex.control.req` 단일화)은 배포 영향이 있으므로 Notes 에 결정 필요로 남긴다. - -## Acceptance Criteria - -1. Given `trex.control.req` 에 `{action: "pause", portfolio_id}` 발행, When 처리되면, Then 기존 REQ-032 pause 동작과 동일하게 `snapshot_pause` 에 반영되고 `{status: "ok", paused[], action}` 응답을 반환한다. -2. Given `{action: "resume", portfolio_id}` / `{action: "list"}`, When 처리되면, Then 각각 기존 resume/list 동작과 동일한 응답을 반환한다. -3. Given `{action: "kill_switch", ...}`, When 처리되면, Then kill-switch 핸들러(REQ-045)로 라우팅된다. -4. Given `{action: "resume_kill_switch", ...}`, When 처리되면, Then resume 핸들러(REQ-046)로 라우팅된다. -5. Given `{action: "bogus"}`, When 처리되면, Then `{status: "error", reason: "unknown_action:bogus"}` 를 반환한다. -6. Given 데몬 기동, When 워커 스레드를 확인하면, Then pause consumer 가 control consumer 로 교체되어 총 워커 스레드 수가 7로 불변이다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — 제어 채널 계약(`trex.control.req` + action 목록) (REQ-047 처리) -- Modules: - - `trex_core/adapters/messaging/topology.py` — `CONTROL_REQ_QUEUE = "trex.control.req"` 추가(또는 `PAUSE_REQ_QUEUE` 대체) - - `trex_core/application/messaging_handlers.py` — `make_control_rpc_handler`(action dispatch) 신설 또는 `make_pause_rpc_handler` 확장 - - `trex_core/runtime/daemon.py` — pause consumer → control consumer 교체 -- Tests: - - control dispatch 단위 테스트(pause/resume/list 회귀 + kill_switch/resume_kill_switch 라우팅 + unknown action) - -## Notes - -- dcy-008 OQ-1 Answered: 단일 제어 채널 + action discriminator. 새 큐 추가 없음. -- 하위호환 결정 필요(배포): 기존 `trex.pause.req` 토픽 이름을 유지(별칭)할지, `trex.control.req` 로 단일화하고 전략/운영 클라이언트를 함께 갱신할지 — batch planning/release 단계에서 확정. 현재 CLI pause/resume 는 store 직접 호출이라 영향 없음(RPC 경로만 대상). -- kill_switch/resume_kill_switch 의 payload 상세(scope/portfolio_id)는 REQ-045/046 에서 정의. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-008(confirmed) → REQ-043 초안 생성. - -#### Intent - -- kill-switch 트리거 표면(OQ-1 Answered)을 단일 제어 채널 Interface REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md (REQ-047) -- Modules: topology.py, messaging_handlers.py, daemon.py -- Tests: control dispatch 단위 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-008(confirmed) 범위·결정(OQ-1/2/4 Answered, OQ-3 Deferred)과 모순 없음. 하위호환(큐 rename)은 batch/release 단계 비핵심 운영 결정으로 승인 blocker 아님. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-012 release-candidate). - -#### Intent - -- BAT-012 verification 의 AC↔evidence(control dispatch 9 단위: pause/resume/list 회귀 + kill_switch/resume_kill_switch 라우팅 + unknown action + 워커 스레드 불변 + e2e control RPC)로 AC-1~6 충족. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-012 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-012 배포 편성. 제어 큐 하위호환은 release rollout 결정. diff --git a/docs/srs/Interface/req-045_kill-switch-execution.md b/docs/srs/Interface/req-045_kill-switch-execution.md deleted file mode 100644 index e669b58..0000000 --- a/docs/srs/Interface/req-045_kill-switch-execution.md +++ /dev/null @@ -1,155 +0,0 @@ -# req-045: kill-switch 실행 — 범위별 holding 즉시 청산 + 이력 로깅 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -운영자가 `trex.control.req` 에 `action: "kill_switch"` 를 발행하면 대상 범위(portfolio 단위 또는 전역)의 모든 `holding` 슬롯을 즉시 청산한다. 기존 `SlotService.sell()`(REQ-036)을 재사용해 순차 매도를 제출하고, 같은 발동에서 대상 범위를 당일 제외(REQ-044/046)로 표시한다. 자율 안전 매도(dcy-007)와 독립적으로 동작하며, 발동 이력을 로깅한다(dcy-008 FR-1/4/5/6, NFR-1/2). - -## Requirement - -- `action: "kill_switch"` payload: `{action, scope: "portfolio"|"global", portfolio_id?(scope=portfolio 시 필수), actor?}`. -- 발동 시 대상 범위의 모든 `holding` 슬롯에 `SlotService.sell(actor="kill-switch", reason="emergency_stop")` 을 순차 호출한다. - - scope=portfolio: 해당 `portfolio_id` 의 holding 슬롯만. - - scope=global: 당일 전체 holding 슬롯. -- 이미 `pending_sell` 인 슬롯은 중복 sell 하지 않는다(REQ-036 idempotency). -- 발동과 동시에 대상 범위를 당일 제외로 표시한다(REQ-044 store 의 `exclude(...)` 호출). masking 동작 자체는 REQ-046. -- 발동 이력(actor, 발동 시각, scope, portfolio_id, 처리 슬롯 수)을 데몬 로그에 1건 기록한다. -- 응답: `{status: "ok", scope, portfolio_id?, liquidated: <처리 슬롯 수>}`. scope=portfolio 인데 `portfolio_id` 누락 시 `{status: "error", reason: "portfolio_id_required"}`. -- kill-switch 실행은 자율 안전 매도 루프(dcy-007)와 상태/스레드를 공유하지 않으며 서로 영향을 주지 않는다. - -## Acceptance Criteria - -1. Given holding 슬롯 N개(전역), When `{action:"kill_switch", scope:"global"}` 처리, Then N개 모두 `holding→pending_sell`(actor=kill-switch, reason=emergency_stop) 전이되고 응답 `liquidated=N`. -2. Given portfolio `pf-A` 에 holding 2개 + `pf-B` 에 holding 1개, When `{action:"kill_switch", scope:"portfolio", portfolio_id:"pf-A"}`, Then `pf-A` 2개만 청산되고 `pf-B` 는 holding 유지. -3. Given 대상 중 이미 `pending_sell` 인 슬롯, When kill_switch 처리, Then 그 슬롯에 중복 sell 호출이 없다(REQ-036 idempotency, transition_log 중복 없음). -4. Given scope=portfolio 인데 `portfolio_id` 누락, When 처리, Then `{status:"error", reason:"portfolio_id_required"}`. -5. Given kill_switch 발동, When 처리 완료, Then 대상 범위가 REQ-044 store 에 당일 제외로 기록된다(`list_excluded(today)` 에 포함). -6. Given kill_switch 발동, When 로그를 확인, Then actor/발동 시각/scope/portfolio_id/처리 슬롯 수를 담은 이력 1건이 기록된다. -7. Given 자율 안전 매도 루프(dcy-007)가 동작 중, When kill_switch 가 발동, Then 두 경로가 독립적으로 동작하며(REQ-036 idempotency 로 충돌 없음) safety-sell 루프가 중단/오류 없이 유지된다. -8. Given holding N개, When kill_switch 처리, Then 전체 sell() 제출 완료까지 지연이 N×100ms + 5s 이내이다(순차, outbox 전송 제외; A-4 N<10 가정). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md` — kill-switch 발동 흐름(트리거→순차 sell()→당일 제외 표시) (REQ-047 처리) - - `docs/interface-contract.md` — kill_switch payload/응답 계약 (REQ-047 처리) -- Modules: - - `trex_core/application/messaging_handlers.py` — kill_switch handler(REQ-043 dispatch 하위) - - `trex_core/domain/slot/service.py` 또는 신규 application 서비스 — 범위별 holding 조회 + 순차 sell() 진입점 - - REQ-044 store 연동(당일 제외 표시) -- Tests: - - kill_switch handler 단위 테스트(전역/portfolio/idempotency/portfolio_id 누락/이력 로깅/독립성) - -## Notes - -- NFR-1(지연)·NFR-2(dcy-007 독립)는 본 REQ AC-8/AC-7 로 흡수(별도 REQ 미승격 — 독립 backlog 가치 낮음, dcy-008 정합). -- pending_buy 슬롯 처리는 dcy-008 OQ-3 Deferred → 기본 구현은 HOLDING 만 대상. pending_buy 롤백은 후속(REQ 추가 또는 change-req). -- R-1(큐 부하): sell()은 PENDING_SELL 전이(DB write 1회)로 빠름. execution worker 순차 처리 유지. -- R-3(브로커 오류): sell()→PENDING_SELL 성공 시 기존 outbox retry 가 eventual consistency 보장. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-008(confirmed) → REQ-045 초안 생성. - -#### Intent - -- kill-switch 즉시 청산(FR-1/4/5/6)과 NFR-1/2 흡수를 Interface REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~8 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, interface-contract.md (REQ-047) -- Modules: messaging_handlers.py, service.py(또는 신규 서비스), REQ-044 store 연동 -- Tests: kill_switch handler 단위 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC(1~8)·Impacted Area·Change Log 완비. dcy-008(confirmed) 범위와 모순 없음. NFR-1/2 흡수, OQ-3(pending_buy) REQ Deferred 는 비핵심 후속으로 승인 blocker 아님(기본 구현 HOLDING 한정). - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-012 release-candidate). - -#### Intent - -- BAT-012 verification 의 AC↔evidence(실행 6 단위: 전역/portfolio 범위/idempotency/당일 제외 표시/resume 비가역/no-holding + e2e kill_switch global liquidated=2 → closed, emergency_stop reason)로 AC-1~8 충족. NFR-1/2 흡수 확인. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-012 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-012 배포 편성. 실 KIS 라이브 발동은 운영 실증 이월. diff --git a/docs/srs/Interface/req-046_killswitch-day-exclusion-masking-and-resume.md b/docs/srs/Interface/req-046_killswitch-day-exclusion-masking-and-resume.md deleted file mode 100644 index 4f889dc..0000000 --- a/docs/srs/Interface/req-046_killswitch-day-exclusion-masking-and-resume.md +++ /dev/null @@ -1,154 +0,0 @@ -# req-046: kill-switch 당일 제외 masking + 자동 해제 + 수동 resume - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -kill-switch 발동(REQ-045)으로 표시된 당일 제외 상태를 snapshot RPC 응답에 반영해 대상 자산을 당일 전략 실행에서 제외한다(추가 매수 차단). 제외는 day-scoped 라 다음 거래일 자동 복귀하며(REQ-044 `trading_date` 키), 운영자는 같은 거래일 내 `action: "resume_kill_switch"` 로 수동 해제할 수 있다(dcy-008 FR-2/3, NFR-3, OQ-4 Answered). - -## Requirement - -- snapshot RPC 응답 산출 시 masking 소스에 "kill-switch 당일 제외"(REQ-044 `list_excluded(today)`)를 추가한다: `mask = pending | warning | pause | killswitch_today`. - - scope=portfolio 제외는 해당 `portfolio_id` 자산만, 전역 제외는 당일 전체 자산을 마스킹한다. -- 마스킹된 자산은 buyable/held 뷰에 노출되지 않아 당일 추가 매수 의도가 거부된다. -- 당일 제외는 `trading_date` 기준 day-scoped 다. 다음 거래일 첫 조회/부팅 시 `list_excluded(다음 날)` 에 포함되지 않아 자동 복귀한다(별도 해제 동작 불필요). -- 영속성: 데몬 재시작 후에도 같은 거래일이면 masking 이 유지된다(REQ-044 영속, NFR-3). -- 수동 해제: `action: "resume_kill_switch"` payload `{action, scope, portfolio_id?, actor?}` 처리 시 대상 범위의 당일 제외를 해제(REQ-044 `clear(...)`)해 다음 조회부터 정상 매매 대상으로 복귀시킨다. - - resume 은 **당일 제외(매매 차단)만 해제**하며 이미 청산(매도)된 슬롯을 되돌리지 않는다(매도 비가역). - - 응답: `{status: "ok", scope, portfolio_id?}`. scope=portfolio 인데 `portfolio_id` 누락 시 `{status:"error", reason:"portfolio_id_required"}`. - -## Acceptance Criteria - -1. Given `pf-A` 가 당일 제외(REQ-044)로 표시됨, When snapshot RPC(view=buyable/all) 처리, Then `pf-A` 자산이 응답 assets 에서 마스킹되어 노출되지 않는다. -2. Given 전역 당일 제외 표시, When snapshot RPC 처리, Then 당일 전체 자산이 마스킹된다. -3. Given 당일 제외 표시 후, When 당일 추가 매수 intent 가 해당 자산에 들어오면, Then buyable 마스킹으로 거부/비노출된다. -4. Given `pf-A` 당일(D) 제외, When 거래일 D+1 로 snapshot RPC 처리, Then `pf-A` 가 다시 노출된다(자동 복귀, 별도 resume 불필요). -5. Given `pf-A` 제외 후 데몬 재시작(같은 거래일 D), When snapshot RPC 처리, Then 여전히 마스킹된다(영속, NFR-3). -6. Given `pf-A` 당일 제외 상태, When `{action:"resume_kill_switch", scope:"portfolio", portfolio_id:"pf-A"}` 처리, Then 다음 snapshot RPC 부터 `pf-A` 가 정상 노출되고 응답 `{status:"ok"}`. -7. Given resume_kill_switch scope=portfolio 인데 `portfolio_id` 누락, When 처리, Then `{status:"error", reason:"portfolio_id_required"}`. -8. Given resume_kill_switch 처리, When 이미 청산된 슬롯을 확인, Then 슬롯 상태(pending_sell/closed)는 되돌려지지 않는다(매도 비가역). - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — masking 소스에 kill-switch 당일 제외 + resume_kill_switch 계약 (REQ-047 처리) - - `docs/runtime-flows.md` — 당일 제외 masking / 자동 복귀 / 수동 resume 흐름 (REQ-047 처리) -- Modules: - - `trex_core/application/portfolio.py`(또는 snapshot envelope builder) — masking 소스에 `list_excluded(today)` 추가 - - `trex_core/application/messaging_handlers.py` — `resume_kill_switch` handler(REQ-043 dispatch 하위) + snapshot 핸들러에 killswitch store 주입 - - REQ-044 store 연동(`list_excluded`/`clear`) -- Tests: - - masking 단위 테스트(portfolio/전역/추가매수 거부) + day-scoped 자동 복귀(날짜 mock) + 재시작 영속 + resume 단위 - -## Notes - -- dcy-008 OQ-4 Answered: 수동 해제 지원(`resume_kill_switch`). 자동 복귀(day-scoped)와 병행. -- NFR-3(영속 후 재시작 masking 유지)는 본 REQ AC-5 로 흡수. -- 전역 제외 sentinel 해석은 REQ-044 store 계약을 따른다. -- masking 합성은 기존 pause masking 패턴(`portfolio.py`)을 재사용(A-2). - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-008(confirmed) → REQ-046 초안 생성. - -#### Intent - -- 당일 제외 masking/자동 복귀/수동 resume(FR-2/3, OQ-4, NFR-3 흡수)을 Interface REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~8 전체 - -#### Impacted Area Delta - -- Docs: interface-contract.md, runtime-flows.md (REQ-047) -- Modules: portfolio.py, messaging_handlers.py, REQ-044 store 연동 -- Tests: masking + day-scoped + 영속 + resume 단위 - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC(1~8)·Impacted Area·Change Log 완비. dcy-008(confirmed) OQ-4 결정(resume_kill_switch) 반영, NFR-3 흡수. 범위·결정과 모순 없음. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-012 release-candidate). - -#### Intent - -- BAT-012 verification 의 AC↔evidence(masking·resume 7 단위: portfolio/전역 masking + buyable 차단 + day-scoped 자동복귀(날짜) + 영속 + resume 재포함 + 청산 비가역 + backward-compat; e2e resume_kill_switch → exclusion 0)로 AC-1~8 충족. NFR-3 흡수. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-012 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-012 배포 편성. diff --git a/docs/srs/Interface/req-049_instance-key-dynamic-queue-naming.md b/docs/srs/Interface/req-049_instance-key-dynamic-queue-naming.md deleted file mode 100644 index 256d938..0000000 --- a/docs/srs/Interface/req-049_instance-key-dynamic-queue-naming.md +++ /dev/null @@ -1,147 +0,0 @@ -# req-049: instance_key 기반 동적 큐명 + default 하위호환 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent - -다중 인스턴스가 동일 RabbitMQ 에서 경쟁 소비(round-robin)로 split-brain 을 일으키지 않도록, 수신 큐명을 `instance_key` 기반으로 인스턴스별 분리한다. `topology.py` 의 고정 큐명 상수를 동적 함수로 교체하고, `declare_topology(instance_key)` + 각 consumer 가 인스턴스 전용 큐를 구독하게 한다. `instance_key="default"` 는 기존 리터럴 큐명을 그대로 사용해 하위호환을 보장한다(dcy-009 FR-2, FR-5, NFR-1/2/3). - -## Requirement - -- `topology.py` 의 고정 큐명 상수(`SNAPSHOT_REQ_QUEUE` 등)를 동적 빌더 함수로 교체/보강한다: `intent_queue(instance_key)`, `snapshot_queue(instance_key)`, `control_queue(instance_key)`. - - **하위호환 규칙**: `instance_key in (None, "default")` 이면 기존 리터럴 큐명(`trex.intent.req` / `trex.snapshot.req` / `trex.control.req`)을 반환한다. 그 외에는 `{base}.{instance_key}` 를 반환한다(FR-5: default 무회귀). -- `rabbitmq.py` `declare_topology(instance_key=...)` 가 인스턴스 전용 큐를 선언한다. `daemon.py` 의 intent/snapshot/control consumer 가 동일 `instance_key` 의 전용 큐를 `consume_rpc_forever` 로 구독한다. -- `daemon.py` 는 설정(REQ-048 TOML)에서 읽은 `instance_key` 를 큐 선언/구독에 일관되게 전달한다. -- 기존 pika `BlockingConnection` per-thread 패턴을 유지한다 — 큐 분리로 인해 추가 스레드/커넥션을 만들지 않는다(NFR-2). 워커 스레드 수 불변. - -## Acceptance Criteria - -1. Given `instance_key="inst-1"`, When `intent_queue("inst-1")`/`snapshot_queue`/`control_queue`, Then 각각 `trex.intent.req.inst-1`/`trex.snapshot.req.inst-1`/`trex.control.req.inst-1` 를 반환한다. -2. Given `instance_key="default"`(또는 None), When 동일 빌더, Then 기존 리터럴 `trex.intent.req`/`trex.snapshot.req`/`trex.control.req` 를 반환한다(하위호환). -3. Given `declare_topology("inst-1")`, When 선언, Then `*.inst-1` 인스턴스 전용 큐가 선언된다(S-1 일부). -4. Given `instance_key="default"` 데몬, When 전체 pytest 스위트 실행, Then 회귀 없음(FR-5/NFR-1, 기존 큐명 테스트 통과). -5. Given 데몬 기동, When 워커 스레드 확인, Then per-thread connection 패턴 유지 + 스레드 수 불변(NFR-2). -6. Given `instance_key="inst-1"` 와 `inst-2` 동시 운영(전용 큐 분리), When 각자 큐만 소비, Then 한 인스턴스의 snapshot/intent 소비가 다른 인스턴스 큐에 영향을 주지 않는다(NFR-3 split-brain 방지 기반). - -## Impacted Area - -- Docs: - - `docs/runtime-flows.md`, `docs/project-structure.md` — 동적 큐명/토폴로지 (REQ-051 처리) -- Modules: - - `trex_core/adapters/messaging/topology.py` — 동적 큐명 빌더(default 하위호환) - - `trex_core/adapters/messaging/rabbitmq.py` — `declare_topology(instance_key)` - - `trex_core/runtime/daemon.py` — consumer 가 instance_key 전용 큐 구독 -- Tests: - - `tests/adapters/messaging/` — 큐명 빌더(default vs named) + declare/consume 분리 + 기존 회귀 - -## Notes - -- 하위호환 핵심: default = 리터럴 큐명(특수 처리)이라 기존 테스트/배포 무영향. FR-2 의 suffix 규칙은 named instance 에 적용. -- NFR-3(split-brain 방지)는 인스턴스 전용 큐로 구조적으로 달성 — 다중 인스턴스 e2e 는 verification 에서 확인. -- exchange/binding(트래픽 라우팅)은 REQ-050. - -## Change Log - -### CHG-20260607-01 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- dcy-009(confirmed) → REQ-049 초안 생성. - -#### Intent - -- instance_key 동적 큐명 + default 하위호환(FR-2/FR-5)을 Interface REQ 로 정규화. - -#### Acceptance Criteria Delta - -- Added: AC-1~6 전체 - -#### Impacted Area Delta - -- Docs: runtime-flows.md, project-structure.md (REQ-051) -- Modules: topology.py, rabbitmq.py, daemon.py -- Tests: tests/adapters/messaging - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 로 승인. - -### CHG-20260607-02 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Proposed → Approved (confirm-req). - -#### Intent - -- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-009(confirmed) FR-2/FR-5 와 일치. default 하위호환 규칙 명확. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: none (미할당) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성. - -### CHG-20260607-03 - -- Date: 2026-06-07 -- Author: Justin Ko - -#### Change Summary - -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-013 release-candidate). - -#### Intent - -- BAT-013 verification 의 AC↔evidence(빌더 단위 named suffix/default 리터럴 + declare_topology(instance_key) + 전체 301 passed 로 default 무회귀)로 AC-1~6 충족. NFR-1/2/3 흡수. - -#### Acceptance Criteria Delta - -- (변경 없음) - -#### Impacted Area Delta - -- (변경 없음) - -#### Delivery Trace Delta - -- Batches: BAT-013 (release-candidate) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Implemented) -- Follow-up Action: draft-release 로 BAT-013 배포 편성. diff --git a/docs/srs/Interface/req-057_asset-slot-state-machine.md b/docs/srs/Interface/req-057_asset-slot-state-machine.md deleted file mode 100644 index 9b1861e..0000000 --- a/docs/srs/Interface/req-057_asset-slot-state-machine.md +++ /dev/null @@ -1,135 +0,0 @@ -# req-057: 자산 영구 슬롯 상태머신 재정의 + 인스턴스 스코프 advisory lock - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 영구 자산 슬롯에 맞춰 슬롯 상태머신을 재정의하고, holding 을 코어 로컬 정본으로 두며, 공유 DB 다중 인스턴스에서 단일 writer 불변식을 인스턴스 단위로 보존한다. (dcy-020 FR-6/FR-3/NFR-1) - -## Requirement -- 상태 집합을 `inactive → pending_buy → holding → pending_sell → inactive` (+ 모든 비-error → `error`, `error → {inactive, holding}` 운영자 resolve)로 재정의한다. 기존 `idle`·`closed` 는 영구 슬롯에서 단일 `inactive` 로 합치고 `closed→idle` 복구 전이를 제거한다. `holding`(포지션 열림)은 코어 슬롯의 로컬 정본으로 체결 시 동기 set, 매도 체결 시 동기 clear 한다(portfolio-hub `asset_quantity` 로 판단하지 않음). advisory lock 을 **인스턴스 스코프**(`pg_advisory_xact_lock(SLOT_LOCK_ID, hash(instance_key))`)로 두어 인스턴스 간 교차 직렬화를 제거한다. - -## Acceptance Criteria -1. Given 상태머신, When 허용 전이 집합을 확인, Then `{inactive→pending_buy, pending_buy→holding, pending_buy→inactive, holding→pending_buy, holding→pending_sell, pending_sell→inactive, pending_sell→holding, *→error, error→inactive, error→holding}` 와 정확히 일치한다(dcy-021 CHG-20260610-03: `holding→pending_buy` 증액 전이 추가). -2. Given 영구 슬롯, When 매도 체결로 종료, Then 슬롯은 `inactive` 로 돌아가고(별도 `closed`·`closed→idle` 없음) holding 로컬 플래그가 clear 된다. -3. Given 서로 다른 instance_key 의 두 슬롯, When 동시에 전이, Then 인스턴스별 락으로 서로를 차단하지 않는다(교차 경합 없음). -4. Given 같은 슬롯에 대한 동시 전이, When 병렬 처리, Then 인스턴스 스코프 락으로 직렬화되어 단일 writer 불변식이 유지된다. -5. Given 보유(holding) 슬롯 증액, When `holding→pending_buy` 전이, Then 허용되고 체결 시 `pending_buy→holding` 으로 복귀한다(dcy-021 REQ-066). 부분 매도 잔량은 기존 `pending_sell→holding` 전이로 holding 을 유지한다. - -## Impacted Area -- Docs: docs/runtime-flows.md(전이 흐름), docs/data-model.md(상태) -- Modules: trex_core/domain/slot/state.py, trex_core/domain/slot/service.py, trex_core/domain/slot/models.py -- Tests: tests/domain/slot/test_state.py, tests/domain/slot/test_service.py - -## Notes -- 기존 REQ-002(상태머신·서비스) 재정의 change-req 동반. dcy-001 의 6상태/closed→idle 모델 폐기. -- holding 로컬 정본 근거(offline 안전·universe 누락 생존·hub 지연·capacity 원자성)는 dcy-020 FR-3. -- 출처: dcy-020 FR-6, FR-3, NFR-1, OQ-7. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-6/FR-3/NFR-1 로부터 최초 생성(Proposed). 상태머신 재정의(idle/closed→inactive, closed→idle 제거) + holding 로컬 정본 + 인스턴스 스코프 advisory lock. - -#### Intent -- 영구 자산 슬롯 상태머신·holding 로컬·인스턴스 단위 단일 writer 를 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 전이 집합/inactive 종료/인스턴스별 락 비교차/동일 슬롯 직렬화 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, docs/data-model.md -- Modules: domain/slot/state.py, service.py, models.py -- Tests: test_state.py, test_service.py - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (신규 정의; REQ-002 재정의 change-req 로 기존 폐기) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. REQ-002 change-req 동반. - -#### Validation Plan -- 전이 허용/거부 집합 + 인스턴스 락 단위테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-027(토대)+BAT-030(literal inactive·별칭 제거) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. - -### CHG-20260610-03 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-021(포지션 사이징, issue #1) 반영: 상태 전이 집합에 **`holding→pending_buy`(보유 슬롯 증액)** 를 추가하고, `pending_sell→holding`(기존)을 부분 매도 잔량 holding 유지 경로로 명시. 전량 매도만 `pending_sell→inactive`. - -#### Intent -- 보유 자산 슬롯이 holding 을 유지한 채 수량 증액(추가 매수)·부분 감액을 표현하도록 상태머신을 additive 확장(dcy-020 이 고정한 "종료=전량 청산" 경계 해제). - -#### Acceptance Criteria Delta -- Added: AC-5(holding→pending_buy 증액 전이 허용·체결 시 holding 복귀, 부분 매도 잔량 pending_sell→holding) -- Updated: AC-1 허용 전이 집합에 holding→pending_buy 포함 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, docs/data-model.md (REQ-067 정합) -- Modules: trex_core/domain/slot/state.py(전이 추가), service.py(증액 qty/avg_price = REQ-066) -- Tests: tests/domain/slot/test_state.py(전이 집합·size), test_service.py - -#### Delivery Trace Delta -- Batches: dcy-021 신규 배치(예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (전이 집합 변경 — test_state 갱신·증액 경로 검증) -- Existing Implementation Invalidated: no (기존 전이 보존, additive) — 단 AC-1 "정확히 일치"가 신규 전이를 포함하도록 확장되어 재검증 필요 -- Status Recommendation: revert-to-approved (신규 전이 미구현 — dcy-021 배치에서 구현·검증 후 confirm-req-implemented) -- Follow-up Action: dcy-021 배치 구현 + test_state 전이 집합 갱신 후 Implemented 복귀. - -#### Validation Plan -- ALLOWED_TRANSITIONS 에 holding→pending_buy 포함·size 갱신 단위 테스트 + 증액 end-to-end(REQ-066). - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-059_active-capacity-admission.md b/docs/srs/Interface/req-059_active-capacity-admission.md deleted file mode 100644 index 7dc9c9d..0000000 --- a/docs/srs/Interface/req-059_active-capacity-admission.md +++ /dev/null @@ -1,94 +0,0 @@ -# req-059: 활성 capacity 제어 + 신호 기반 admission (FCFS·M캡·무치환·over-cap) - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 익명 슬롯 배정 대신 "동시 활성(holding) 최대 M개" 정책으로 capacity 를 제어하고, 매수/매도 신호를 해당 자산 슬롯의 활성화/비활성화로 처리한다. (dcy-020 FR-2/FR-3/OQ-1/OQ-8) - -## Requirement -- 매수 신호는 해당 자산 슬롯을 활성화(`inactive→pending_buy`)한다. 활성(holding+pending_buy) 수 < M 이면 즉시 활성화, == M 이면 **거부(`capacity_full`)** 한다. 코어는 자리를 만들려고 기존 holding 을 **자율 청산(치환)하지 않는다**(FCFS·무치환). 동시 후보 tie-break 은 `updated_at` asc → `portfolio_id`(alpha 비사용). M 을 현재 보유 수보다 작게 바꿔 over-cap 이 되면 **신규 활성화만 차단**하고 기존 보유는 자연 종료까지 유지한다(강제 청산 없음). 마지막 슬롯 경합은 인스턴스 스코프 advisory lock + 재시도로 직렬화한다. - -## Acceptance Criteria -1. Given 활성 수 < M, When 자산 매수 신호, Then 그 자산 슬롯이 `pending_buy` 로 활성화된다. -2. Given 활성 수 == M, When 새 자산 매수 신호, Then `capacity_full` 로 거부되고 기존 holding 은 청산되지 않는다. -3. Given 이미 활성(pending_buy/holding/pending_sell)인 자산, When 또 매수 신호, Then 멱등 거부(`already_active`)된다. -4. Given M 을 현재 보유 수보다 작게 변경(over-cap), When 신규 매수 신호, Then 활성 수가 M 이하로 내려갈 때까지 신규만 거부되고 기존 보유는 유지된다. -5. Given 동시 매수 신호 경합, When 병렬 처리, Then 인스턴스 스코프 락으로 직렬화되어 M 초과 활성화가 발생하지 않는다. - -## Impacted Area -- Docs: docs/runtime-flows.md(admission→활성화 흐름) -- Modules: trex_core/application/intents.py, trex_core/application/portfolio.py(capacity 개념) -- Tests: tests/application/test_intent_concurrency.py, tests/application/test_*admission*.py - -## Notes -- M 값 출처는 REQ-060(per-instance TOML). 기존 REQ-007(admission) 재정의 change-req 동반(find_allocatable_slot/idle 배정 폐기). -- 출처: dcy-020 FR-2, FR-3, OQ-1, OQ-8. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-2/FR-3/OQ-1/OQ-8 로부터 최초 생성(Proposed). FCFS + M 캡 + capacity_full 거부 + 무치환 + over-cap(신규만 차단) + already_active. - -#### Intent -- 신호 기반 활성화/비활성화와 동시 활성 M 제어를 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: 활성화/거부/already_active/over-cap/경합 직렬화 5개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md -- Modules: application/intents.py, portfolio.py -- Tests: test_intent_concurrency.py, admission 테스트 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. REQ-007 재정의 change-req·REQ-060(M) 의존. - -#### Validation Plan -- FCFS/거부/over-cap/경합 단위·통합 테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-028(+BAT-030 단일 경로·capacity_full e2e) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Interface/req-061_asset-keyed-intent-contract.md b/docs/srs/Interface/req-061_asset-keyed-intent-contract.md deleted file mode 100644 index 20e2c0a..0000000 --- a/docs/srs/Interface/req-061_asset-keyed-intent-contract.md +++ /dev/null @@ -1,219 +0,0 @@ -# req-061: intent 계약 자산(portfolio_id) 키 통일 + 중복/무효 규칙 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 전략↔코어 intent 계약을 **코어 native 키인 `slot_id`** 로 통일한다(CHG-20260610-03, 사용자 결정으로 FR-7 의 portfolio_id 키를 slot_id 로 재정의). 자산 중심 영구 슬롯이라 universe 모든 자산에 슬롯이 선존재하므로 매수도 slot_id 로 지정 가능하다. 스냅샷 `slots[]` 가 계약의 1차 단위이며 portfolio_id 는 표시/자산 정체성으로 잔존(계약 키 아님). (dcy-020 FR-7 재정의/NFR-4) - -## Requirement -- 매수·매도 intent 를 모두 **`slot_id`** 키로 식별한다. 매수 = 그 슬롯 활성화(`inactive→pending_buy`, symbol 은 슬롯의 `portfolio_id` 로 resolve), 매도 = 비활성화(`holding→pending_sell`). 매도는 이미 slot_id 계약(불변). **매수는 기존 portfolio_id 계약에서 slot_id 로 이동하되, 외부 전략 무중단을 위해 portfolio_id 를 레거시 dual-accept**(자산 중심 모드에서 portfolio_id → `find_slot_by_portfolio` 로 슬롯 매핑); portfolio_id 매수 계약 완전 폐기는 BAT-δ. 중복/무효 신호 규칙: 이미 활성(`pending_buy`/`holding`/`pending_sell`)인 슬롯에 매수 → 멱등 거부(`already_active`); 보유(holding) 아닌 슬롯에 매도 → 거부(`not_holding`). `intent_id` 멱등(intent_log) 보존. - -## Acceptance Criteria -1. Given 매수 신호, When `slot_id` 로 제출(자산 중심 모드), Then 그 슬롯이 `inactive→pending_buy` 로 활성화되고 symbol 은 슬롯의 portfolio_id 로 resolve 된다. -2. Given 이미 활성(pending_buy/holding/pending_sell)인 슬롯, When 매수, Then `already_active` 로 멱등 거부된다. -3. Given 보유(holding) 아닌 슬롯, When 매도, Then `not_holding`(또는 not-holding 사유)로 거부된다. -4. Given 레거시 매수(`portfolio_id`), When 제출, Then `find_slot_by_portfolio` 로 슬롯에 매핑되어 동일하게 처리된다(dual-accept, 무중단). -5. Given 같은 `intent_id` 재전송, When 처리, Then 슬롯 중복 전이 없이 최초 결정과 동일 응답(멱등). -6. Given 보유(holding) 슬롯, When **증액 매수 intent**(qty+price)를 제출, Then `holding→pending_buy` 로 추가 매수가 집행된다(dcy-021, 이미 active 라도 증액은 허용 — `already_active` 예외). -7. Given 보유 슬롯, When **부분 매도 intent**(qty+price)를 제출, Then 명시 수량으로 `holding→pending_sell` 집행된다(집행 수량 정책 REQ-062). 사이징 intent 는 명령형(전략이 qty+price 계산·전송), core 는 target/의도 수량을 저장·추적하지 않는다. - -## Impacted Area -- Docs: docs/runtime-flows.md(intent 흐름), docs/interface-contract.md(메시지 계약) -- Modules: trex_core/application/intents.py, 메시지 핸들러/스키마 -- Tests: tests/application/test_messaging_intent_consumer.py, tests/application/test_*intent*.py - -## Notes -- 기존 REQ-007(intent API) 재정의 change-req 동반(sell-by-slot_id 폐기·자산 키 통일). NFR-4(전략 계약 호환): 외부 전략 모듈 동시 전환 또는 한시적 하위호환 — REQ/배치 cutover 에서 명시. -- 출처: dcy-020 FR-7, NFR-4. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-7/NFR-4 로부터 최초 생성(Proposed). intent 자산(portfolio_id) 키 통일 + sell-by-slot_id 폐기 + already_active/not_holding. - -#### Intent -- 매수·매도 신호를 자산 키로 통일하는 계약 변경을 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: 매도 자산키/already_active/not_holding/intent_id 멱등 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, docs/interface-contract.md -- Modules: application/intents.py, 메시지 핸들러/스키마 -- Tests: test_messaging_intent_consumer.py, intent 테스트 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (REQ-007 재정의 change-req 로 기존 폐기) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. NFR-4 외부 전략 계약 cutover 명시. - -#### Validation Plan -- 자산 키 매도/거부 규칙/멱등 단위·통합 테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-03 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020 FR-7 재정의 — 사용자 결정) intent 계약 키를 `portfolio_id` → **`slot_id`**(buy·sell 공통)로 재정의. 근거: trex-core 는 슬롯 기준 관리이고 스냅샷이 `slots[]` 를 단위로 주므로 코어 native 키(slot_id)로 round-trip 하는 것이 일관적이며 portfolio-hub 식별 체계의 계약 누수를 막는다. 자산 영구 슬롯이라 매수도 slot_id 로 지정 가능(슬롯 선존재). 매도는 이미 slot_id(불변); 매수만 portfolio_id→slot_id 이동(레거시 portfolio_id dual-accept, 폐기 BAT-δ). Approved 유지(사용자 재승인). - -#### Intent -- 전략↔코어 계약을 슬롯 native 키로 통일. - -#### Acceptance Criteria Delta -- Updated: AC 전체를 slot_id 기준으로 재작성(AC-1 매수 slot_id 활성화·symbol resolve, AC-4 portfolio_id dual-accept 추가). -- Removed: "매도 portfolio_id 제출" 전제(매도는 slot_id 유지). - -#### Impacted Area Delta -- Modules: application/intents.py(BuyIntent slot_id·_admit_buy_asset_centric), 메시지 핸들러 buy 파싱, runtime/cli.py enter -- Tests: test_asset_centric_admission(slot_id), 메시지 consumer -- 연동: REQ-059(자산 admission)·REQ-007(buy intent) — buy 키 slot_id 로 정합(BAT-029). - -#### Delivery Trace Delta -- Batches: BAT-029 -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (BAT-029) -- Existing Implementation Invalidated: BAT-028 의 portfolio_id 매수 admission 은 dual-accept 로 흡수(레거시 경로 유지) — 무효화 아님 -- Status Recommendation: keep-current (Approved, 사용자 재승인) -- Follow-up Action: BAT-029 구현. REQ-059/007 정합. - -#### Validation Plan -- BAT-029: 매수 slot_id 활성화 / portfolio_id dual-accept / already_active / 매도 slot_id / 멱등 단위·통합. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-029(+BAT-030 trading_date 제거·e2e slot_id 매수) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. - -### CHG-20260610-04 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-021(포지션 사이징) 반영: intent 계약에 **사이징 intent**(보유 슬롯 증액 매수 / 부분 매도)를 추가. 명령형 — 전략이 스냅샷 cash+quantity 로 **정수 주식 수(qty)+가격(price)** 을 계산해 보내고, core 는 실행만 한다(target/의도 수량 미저장·미추적). 증액은 이미 active 슬롯에도 허용(전량 매수 활성화의 `already_active` 와 구분). - -#### Intent -- 보유 자산의 수량 조정을 기존 명령형 buy/sell 계약의 최소 additive 확장으로 표현(dcy-021 OQ-1/OQ-2 확정). - -#### Acceptance Criteria Delta -- Added: AC-6(증액 매수 intent → holding→pending_buy), AC-7(부분 매도 intent qty → holding→pending_sell, 명령형·core 미저장) -- Updated: 없음(기존 활성/청산 계약 보존) -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/interface-contract.md(사이징 intent payload), docs/runtime-flows.md -- Modules: trex_core/application/intents.py(증액/부분 매도 admission), messaging_handlers.py(payload) -- Tests: tests/application/test_*intent*.py, 사이징 admission 신규 - -#### Delivery Trace Delta -- Batches: dcy-021 신규 배치(예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (신규 사이징 계약 경로) -- Existing Implementation Invalidated: no (기존 slot_id 매수·전량 매도 계약 보존, additive) -- Status Recommendation: revert-to-approved (사이징 계약 미구현 — dcy-021 배치에서 구현·검증) -- Follow-up Action: dcy-021 배치 구현 후 Implemented 복귀. - -#### Validation Plan -- 증액 매수·부분 매도 intent admission 단위 + end-to-end(REQ-066). 명령형·멱등·core 미저장 검증. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음. - -### CHG-20260610-05 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-022 실행 정책) buy/sell intent 계약에 **실행 정책 필드**(optional `timeout_sec`·`slippage_pct`) 추가. `timeout_sec`: 주문 유효시간(N초 미체결 시 core 가 cancel_order 송신·롤백). `slippage_pct`: 체결 보호밴드(entry_price 기준 ±pct, entry_price 없으면 무시). 두 필드 모두 optional — 미지정 시 기존 경로 하위호환. `timeout_sec` 미지정 시 운영자 per-instance config(`default_order_timeout_sec`) 적용(미설정=OFF=무기한). Implemented → Approved 되돌림(신규 계약 필드 미구현, BAT-032 에서 재구현·confirm-req-implemented 복귀). - -#### Intent - -- 전략이 intent 제출 시 실행 정책을 함께 실어 미체결 고착을 원천 차단한다(dcy-022 FR-1). - -#### Acceptance Criteria Delta - -- Added: - - AC-8: buy/sell intent 에 optional `timeout_sec` 포함 시 core 가 파싱해 슬롯에 기록하고 집행 루프가 timeout 초과 시 cancel_order 를 송신한다. - - AC-9: buy/sell intent 에 optional `slippage_pct` 포함 시 `entry_price` 기준 실효 지정가를 산출해 주문을 제출한다(`entry_price` 없으면 무시). - - AC-10: `timeout_sec`/`slippage_pct` 미지정 시 기존 매수/매도 경로와 동일하게 처리된다(하위호환). -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta - -- Docs: docs/interface-contract.md(payload 확장) -- Modules: BuyIntent/SellIntent(+timeout_sec/slippage_pct), messaging_handlers.py(파싱), intents.py(transit 전달), slot models(+timeout_sec/slippage_pct 컬럼), execution.py(dispatch slippage·poll timeout) -- Tests: timeout/slippage 파싱·집행 단위, 하위호환 회귀 - -#### Delivery Trace Delta - -- Batches: BAT-032 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 계약 필드 경로) -- Existing Implementation Invalidated: no (기존 slot_id 매수·매도 계약 보존, additive) -- Status Recommendation: revert-to-approved (신규 필드 미구현 — BAT-032 에서 구현·검증) -- Follow-up Action: BAT-032 구현 후 Implemented 복귀. - -#### Validation Plan - -- timeout_sec/slippage_pct 파싱·슬롯 기록·집행 단위. 미지정 회귀. end-to-end timeout 취소 시나리오. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Interface/req-063_unified-operator-slot-view.md b/docs/srs/Interface/req-063_unified-operator-slot-view.md deleted file mode 100644 index a317970..0000000 --- a/docs/srs/Interface/req-063_unified-operator-slot-view.md +++ /dev/null @@ -1,135 +0,0 @@ -# req-063: 운영자 표면 단일화 — slots = 자산 포지션 단일 뷰 - -- Status: Implemented -- Type: Interface -- Priority: Medium -- Owner: project maintainers - -## Intent -- universe(자산 리스트)와 slots(슬롯 상태)의 이중 표면(BAT-022 혼동 원인)을 해소하기 위해, 슬롯=자산 1:1 모델에서 `trex-core slots` 가 자산 포지션을 단일 뷰로 제공한다. (dcy-020 FR-8) - -## Requirement -- `trex-core slots` 가 자산 슬롯(슬롯=자산)을 단일 뷰로 표시한다: `portfolio_id`/symbol/status(inactive/pending_buy/holding/pending_sell/error)/holding 수량 미러/`universe_detached`(tombstone)/`retired`. universe(자산 리스트)와 slots(자산 포지션)는 같은 자산 집합 위의 view 가 되며, 데몬 RPC(스냅샷)로 통일한다(직접 DB 쓰기 없음). 운영자는 자산별 포지션·활성 여부·tombstone 여부를 한 표면에서 본다. - -## Acceptance Criteria -1. Given universe N개 자산, When `trex-core slots`, Then N개 자산 슬롯이 status·holding·tombstone 표시와 함께 단일 뷰로 출력된다. -2. Given tombstone(universe-detached holding) 슬롯, When `slots`, Then `universe_detached` 로 구분 표시된다. -3. Given `--status` 필터, When 조회, Then 자산 슬롯 status 로 필터된다. - -## Impacted Area -- Docs: docs/runtime-flows.md, docs/interface-contract.md(snapshot envelope slots[]) -- Modules: trex_core/runtime/cli.py, snapshot envelope 빌더 -- Tests: tests/application/test_messaging_snapshot_consumer.py, CLI 테스트 - -## Notes -- 기존 REQ-032(운영자 CLI)·REQ-028/029(snapshot envelope) 와 연계 change-req. BAT-022 슬롯 가시화의 자산 중심 재정의. -- 출처: dcy-020 FR-8. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 FR-8 로부터 최초 생성(Proposed). `trex-core slots` = 자산 포지션 단일 뷰(universe↔slots 이중 표면 해소). - -#### Intent -- 운영 관측 단일화(자산별 포지션·활성·tombstone)를 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: 단일 뷰 출력/tombstone 표시/status 필터 3개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, docs/interface-contract.md -- Modules: runtime/cli.py, snapshot envelope 빌더 -- Tests: test_messaging_snapshot_consumer.py, CLI 테스트 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no (REQ-032/028/029 연계 change-req) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. - -#### Validation Plan -- slots 단일 뷰 출력·tombstone·필터 단위테스트 green. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs / draft-batch. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-029(+BAT-030 trading_date 키 제거·buyable=inactive 필터) — 모든 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 29/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. - -### CHG-20260611-01 - -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-025) 운영자 CLI(`trex-core slots`/`universe`)의 데몬 RPC 를 `trex.snapshot.req` → **`trex.slots.req`**(REQ-073)로 교체한다. 운영자 조회는 **`spy=true`** 로 발신(기존 `actor="operator"`/`touch_watchdog=false` 대체 — 비침습 유지). `--status` 필터는 새 `filter`(status 단일: all/inactive/pending/holding/error)로 매핑. 출력 행은 REQ-074 병합 스키마(asset_quantity+qty 미러+warned, cash_balance+cash_estimated, watchdog nested, paused) 사용. universe↔slots 단일 표면 의도(본 REQ Intent)는 새 표면에서 완성된다. - -#### Intent - -- 운영자 단일 뷰를 새 read 표면 위로 이전(비침습·필터·병합 행 유지). - -#### Acceptance Criteria Delta - -- Updated: - - AC-1~3 의 underlying RPC 가 `trex.slots.req` 이고, 조회는 `spy=true`(워치독 무터치)로 수행된다. `--status` → `filter` 매핑(buyable→inactive 동치 포함). -- Added: - - AC-4: `slots` 출력 행에 REQ-074 병합 필드(asset_quantity/qty/warned/cash_balance/cash_estimated/watchdog/paused)가 표시된다. -- Removed: none - -#### Impacted Area Delta - -- Modules: trex_core/runtime/cli.py(`_cmd_slots`/`_cmd_universe` — 새 큐·spy·filter·행 필드) -- Docs: docs/interface-contract.md CLI↔RPC 매핑(REQ-075) -- Tests: tests/runtime/test_cli_operator.py(새 payload·행 표시) - -#### Delivery Trace Delta - -- Batches: 신규 batch(REQ-073/074/075 와 동일 배치, dcy-025) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (신규 batch 에서 갱신 AC 검증) -- Existing Implementation Invalidated: yes (snapshot RPC 기반 CLI evidence 가 표면 교체로 무효화) -- Status Recommendation: Implemented → Approved (batch 재구현·재검증 후 복귀) -- Follow-up Action: draft-batch → 구현/검증 → confirm-req-implemented - -#### Validation Plan - -- 신규 BAT: CLI payload(`trex.slots.req`/spy=true/filter 매핑) 단위 + 행 필드 표시 단위 + buyable→inactive 동치 회귀. diff --git a/docs/srs/Interface/req-066_position-sizing-resize.md b/docs/srs/Interface/req-066_position-sizing-resize.md deleted file mode 100644 index f8a15bc..0000000 --- a/docs/srs/Interface/req-066_position-sizing-resize.md +++ /dev/null @@ -1,95 +0,0 @@ -# req-066: 보유 자산 포지션 사이징 — 증액·부분 감액 (수량/avg_price 재계산) - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- dcy-021: 자산 중심 영구 슬롯(dcy-020)의 "수량 가변 전제" 위에, 보유(holding) 자산 슬롯의 **수량을 holding 을 유지한 채 조정**(증액·부분 감액)하는 end-to-end 동작과 수량/avg_price 재계산 규칙을 정의한다. 전량 청산만 종료(inactive)한다. (issue #1, dcy-020 OQ-4 후속) - -## Requirement -- 보유 슬롯 **증액**: 같은 슬롯이 `HOLDING→PENDING_BUY→HOLDING` 로 순환하며 **실제 체결분만큼** 보유 수량이 누적되고, `avg_price = (기존수량×기존평단 + 체결수량×체결가) / 총수량` 가중평균으로 재계산된다(REQ-057 전이·REQ-061 intent 계약 사용). -- 보유 슬롯 **부분 감액**: 같은 슬롯이 `HOLDING→PENDING_SELL` 로 전이하고, 체결 후 브로커 정본 수량을 반영해 잔량>0 이면 `HOLDING`(**cost basis=avg_price 유지**), 잔량 0(전량)이면 `INACTIVE` 로 종료한다(집행 수량 정책은 REQ-062). -- **부분 체결**: 증액·감액 모두 **실제 체결분만** 슬롯 수량/원장에 반영하고, 미체결 잔량은 core 가 추적·재시도하지 않는다(재수렴은 전략이 스냅샷 기준으로 재발행). 실현손익은 core 가 계산하지 않고 ledger/portfolio-hub 가 소유한다. -- 활성 자산 수(M 캡 대상, REQ-059)는 증액으로 증가하지 않는다(이미 active 인 자산의 수량 변경). -- 기존 활성/전량 청산/admission 경로는 무회귀(additive 확장). - -## Acceptance Criteria -1. Given 100주 holding(avg 1000) 슬롯, When 50주(@1200) 증액 매수가 전량 체결, Then 슬롯은 holding 유지·수량 150·avg_price = (100×1000+50×1200)/150 = 1066.67(반올림 규칙 적용)로 재계산된다. -2. Given 100주 holding 슬롯, When 60주 부분 매도가 전량 체결, Then 슬롯은 holding 유지·수량 40·avg_price(cost basis) 불변이다. -3. Given 100주 holding 슬롯, When 전량(또는 잔고 이상) 매도가 체결, Then 슬롯은 INACTIVE 로 종료되고 점유 필드가 초기화된다. -4. Given 증액/부분 매도 주문이 일부만 체결(예: 50 중 30), When 집행, Then 실제 체결분(30)만 슬롯 수량·ledger 에 반영되고 미체결 잔량은 추적되지 않는다. -5. Given dcy-020 기존 경로(신규 활성·전량 청산·admission·M 캡), When 사이징 도입 후 회귀, Then 무회귀(green)다. - -## Impacted Area -- Docs: docs/data-model.md(수량 가변·avg_price 재계산), docs/runtime-flows.md(사이징 흐름) — REQ-067 에서 정합 -- Modules: trex_core/domain/slot/service.py(qty 누적·avg_price 가중평균·cost basis 유지), trex_core/domain/slot/state.py(REQ-057 change-req), trex_core/application/execution.py(부분 체결 반영), trex_core/application/intents.py(REQ-061 change-req) -- Tests: 증액 avg_price 가중평균, 부분 매도 잔량/전량 분기, 부분 체결 실제만 반영, 회귀 가드 - -## Notes -- 계약 형태(명령형 qty+price)는 dcy-021 OQ-1, 단위(정수 주식 수+가격)는 OQ-2, avg_price/cost basis/실현손익은 OQ-3, 트리거(intent 당 1회)는 OQ-4 확정. -- 본 REQ 는 사이징의 **도메인 수량/avg_price 의미·end-to-end 동작**을 소유한다. 상태 전이(REQ-057)·intent 계약(REQ-061)·집행 매도 수량(REQ-062)의 계약 변경은 각 change-req 로 기록한다. -- 반올림 규칙: avg_price 는 기존 슬롯 `avg_price`(float/NUMERIC) 정밀도를 따른다(별도 통화 반올림 정책 없음). 주식 수는 정수. -- 출처: dcy-021 FR-1/FR-2 + NFR-1/NFR-2. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-021(포지션 사이징, issue #1)로부터 최초 생성(Proposed). 보유 슬롯 증액·부분 감액 end-to-end + 수량/avg_price 재계산. - -#### Intent -- 자산 영구 슬롯의 수량 가변 전제 위에 보유 포지션 리사이즈를 정규화. - -#### Acceptance Criteria Delta -- Added: 증액 가중평균/부분 감액 잔량·전량/부분 체결 실제만/무회귀 5개 기준 -- Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: data-model/runtime-flows(REQ-067) -- Modules: domain/slot service·state(REQ-057), application/execution·intents(REQ-061/062) -- Tests: 증액·부분 매도·부분 체결·회귀 - -#### Delivery Trace Delta -- Batches: dcy-021 신규 배치(예정) / Releases: none - -#### Revalidation Impact -- Reverification Needed: yes / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: confirm-req → dcy-021 배치. - -#### Validation Plan -- 단위(avg_price 가중평균·잔량 분기·부분 체결) + run-the-app end-to-end. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-021(confirmed, handoff 승인, OQ-1~4 Answered)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md(Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: suggest-batch-reqs → draft-batch. - -#### Validation Plan -- 승인 후 dcy-021 배치 planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-031(release-candidate) verification — 단위 378 / 통합 422 passed + run-the-app 사이징 e2e(증액 가중평균·부분 매도 잔량·전량 매도) green. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-068_execution-policy-intent-contract.md b/docs/srs/Interface/req-068_execution-policy-intent-contract.md deleted file mode 100644 index 25a0db6..0000000 --- a/docs/srs/Interface/req-068_execution-policy-intent-contract.md +++ /dev/null @@ -1,97 +0,0 @@ -# req-068: intent 실행 정책 계약 — timeout_sec + slippage_pct - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- buy/sell intent 계약에 **실행 정책 필드**(`timeout_sec`·`slippage_pct`)를 추가해 전략이 "어떻게 체결할지"를 함께 선언한다. core 는 수신 측 먼저 배포 가능(optional, 하위호환). (dcy-022 FR-1) - -## Requirement -- buy/sell intent payload 에 optional 필드 추가: `timeout_sec`(주문 유효시간 — N초 미체결 시 core 가 cancel_order 송신·롤백) · `slippage_pct`(체결 보호밴드 — entry_price 기준 ±pct 실효 지정가). 두 필드 모두 optional — 미지정 시 기존 동작 하위호환(무기한 지정가/시장가). `timeout_sec` 미지정 시 per-instance TOML `default_order_timeout_sec`(opt-in, 미설정=OFF=무기한) 를 기본값으로 적용하고 per-intent 값이 override 한다. `slippage_pct` 기본값 없음(미지정=보호밴드 없음). core 수신 측 먼저 배포(누락 시 무시 — orb-v1 발신과 lockstep 불필요). deprecate 없음(전부 신규 optional 필드, 기존 계약 보존). - -## Acceptance Criteria -1. Given buy intent 에 `timeout_sec=60` 포함, When core 수신, Then 슬롯에 `timeout_sec=60` 이 기록되어 집행 루프가 읽을 수 있다. -2. Given buy intent 에 `slippage_pct=0.1` 포함, When core 수신, Then 슬롯에 `slippage_pct=0.1` 이 기록되어 dispatch 시 실효 지정가 산출에 쓰인다. -3. Given `timeout_sec` 미지정 intent, When per-instance config `default_order_timeout_sec=120`, Then 슬롯에 `timeout_sec=120` 이 기록된다(config 기본값 적용). -4. Given `timeout_sec` 미지정 + config 미설정, When 수신, Then 슬롯 `timeout_sec` 이 None — 기존 무기한 동작과 동일(하위호환). -5. Given 기존 buy/sell intent(timeout/slippage 필드 없음), When 수신, Then 기존 경로와 byte-동등하게 처리된다(회귀 없음). -6. Given per-intent `timeout_sec` 와 config `default_order_timeout_sec` 둘 다 있음, When 수신, Then per-intent 값이 우선한다(override). - -## Impacted Area -- Docs: docs/interface-contract.md(payload 확장) -- Modules: trex_core/application/intents.py(BuyIntent/SellIntent +timeout_sec/slippage_pct, IntentService +default_order_timeout_sec), trex_core/application/messaging_handlers.py(파싱), trex_core/domain/slot/models.py(+컬럼), trex_core/domain/slot/service.py(transit_slot 전달), trex_core/adapters/persistence/sql/012_add_execution_policy_fields.sql(DB) -- Config: DaemonInstanceConfig +default_order_timeout_sec -- Tests: 파싱·슬롯 기록·config 기본값·하위호환 단위 - -## Notes -- `timeout_sec` 는 코어-타이밍(N초 후 cancel_order 송신) 이며 IOC/FOK 등 네이티브 TIF 는 후속 주기(dcy-022 OQ-1). -- `slippage_pct` 는 `entry_price` 있을 때만 유효(OQ-2). 필드 역할: `entry_price`=기준·목표, `slippage_pct`=허용폭 → 실효 limit = entry_price*(1±pct). `timeout_sec` ≠ `forced_close_at`(포지션 만기 청산, REQ-037). -- 출처: dcy-022 FR-1, OQ-1/OQ-2/OQ-3/OQ-4. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-022 FR-1 로부터 최초 생성(Proposed). intent 실행 정책 계약(timeout_sec + slippage_pct) 신규 정의. - -#### Intent -- 실행 정책 optional 필드 계약을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: AC-1~6 (timeout_sec 기록·config 기본값·slippage_pct 기록·하위호환·override 6개 기준) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/interface-contract.md -- Modules: BuyIntent/SellIntent, messaging_handlers, intents, slot models, service, migration 012 -- Tests: 파싱·기록·config·하위호환 단위 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 BAT-032 편성. - -#### Validation Plan -- timeout_sec/slippage_pct 파싱·슬롯 기록·config 기본값·하위호환 단위 테스트. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-022(confirmed, OQ-1~4 Answered, Freeze 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032 편성. - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Interface/req-071_operator-cancel-pending-backstop.md b/docs/srs/Interface/req-071_operator-cancel-pending-backstop.md deleted file mode 100644 index 62665a5..0000000 --- a/docs/srs/Interface/req-071_operator-cancel-pending-backstop.md +++ /dev/null @@ -1,89 +0,0 @@ -# req-071: 운영자 강제 취소 백스톱 — trex-core cancel - -- Status: Implemented -- Type: Interface -- Priority: Medium -- Owner: project maintainers - -## Intent -- timeout 미지정·취소 실패로 잔여 고착된 pending 슬롯을 운영자가 `trex-core cancel ` 로 강제 취소·롤백할 수 있는 백스톱 경로를 제공한다. dcy-013 Alert-only 는 기본 유지(REQ-014 회귀 없음). (dcy-022 FR-5) - -## Requirement -- CLI `trex-core cancel ` 가 daemon 의 control RPC 채널(`trex.control.req`, REQ-043)에 `cancel_pending` 액션을 전송한다. daemon 수신 시: ① 슬롯이 `pending_buy`/`pending_sell` 이면 `broker.cancel_order` 를 송신하고, 다음 집행 루프 poll 에서 rollback 이 수렴한다(기존 is_terminal_reject/is_partial_cancel 경로). ② 슬롯이 HOLDING/INACTIVE 이면 `not_pending` 응답(무동작). ③ 슬롯 없으면 `slot_not_found`. 기존 kill_switch·pause 와 동일하게 운영자 단일-writer 준수(슬롯 직접 쓰기 없음 — cancel_order 만 송신, 상태 전이는 집행 루프). - -## Acceptance Criteria -1. Given pending_buy 슬롯, When `trex-core cancel `, Then broker.cancel_order 가 호출되고 응답 `{status: "ok", slot_id: ...}` 를 반환한다. -2. Given cancel_order 가 호출된 후, When 집행 루프 다음 poll, Then 슬롯이 inactive/holding 으로 수렴한다(기존 is_terminal_reject/is_partial_cancel 경로 재사용). -3. Given pending_sell 슬롯, When `trex-core cancel `, Then pending_buy 와 동일하게 cancel_order 호출 + ok 응답. -4. Given holding/inactive 슬롯, When `cancel` 시도, Then `{status: "ignored", reason: "not_pending"}` 응답(무동작). -5. Given 존재하지 않는 slot_id, When `cancel` 시도, Then `{status: "error", reason: "slot_not_found"}`. -6. Given `--dry-run` 옵션, When `cancel`, Then cancel_order 미송신·응답 없이 대상 슬롯 정보만 출력(안전 확인). - -## Impacted Area -- Docs: docs/runtime-flows.md(백스톱 취소 흐름) -- Modules: trex_core/runtime/cli.py(`cancel` 서브커맨드), trex_core/application/messaging_handlers.py(`cancel_pending` 액션 추가), execution.py / broker: cancel_order 호출 배선(daemon 제어 핸들러) -- Tests: tests/runtime/test_cli.py(cancel 서브커맨드), messaging_handlers cancel 단위 - -## Notes -- 운영자 처분은 백스톱(FR-5) — timeout 미지정 또는 cancel_order 실패 잔여 고착 대비. dcy-013 stale-alert(Alert-only) 는 회귀 없이 유지. -- 슬롯 직접 쓰기 없이 cancel_order 만 송신 → 집행 루프가 다음 poll 에서 전이. 단일 writer 불변식 유지(REQ-013 NFR-2). -- 출처: dcy-022 FR-5, NFR-1. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-022 FR-5 로부터 최초 생성(Proposed). 운영자 강제 취소 백스톱 CLI + control RPC 액션. - -#### Intent -- 잔여 고착 pending 슬롯의 운영자 처분 경로를 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: AC-1~6 (pending 취소, 집행 루프 수렴, holding 무동작, not_found, dry-run 6개 기준) - -#### Impacted Area Delta -- Modules: cli.py(cancel 서브커맨드), messaging_handlers(cancel_pending), daemon 제어 배선 -- Tests: cli cancel, messaging cancel_pending 단위 - -#### Delivery Trace Delta -- Batches: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current - -#### Validation Plan -- cancel CLI 송신 + control RPC cancel_pending + 집행 루프 수렴 단위. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- delivery input 으로 승격. - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) - -#### Delivery Trace Delta -- Batches: none - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: BAT-032. - -#### Validation Plan -- 승인 후 batch planning 에서 AC 검증 기준으로 사용. - -### CHG-20260611-IMPL -- Date: 2026-06-11 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-032(release-candidate) verification — 실행 정책(timeout_sec·slippage_pct) 계약·집행·운영자 cancel 백스톱 전 AC acceptance mapping 존재(신규 71 tests), 단위 425 passed 회귀 없음, baseline 2문서 수동 확인(M5). Blocking Issues 없음. diff --git a/docs/srs/Interface/req-073_slots-req-rpc-contract.md b/docs/srs/Interface/req-073_slots-req-rpc-contract.md deleted file mode 100644 index 5ecbcf6..0000000 --- a/docs/srs/Interface/req-073_slots-req-rpc-contract.md +++ /dev/null @@ -1,206 +0,0 @@ -# req-073: 전략 read 표면 — `trex.slots.req` RPC 계약 (filter/spy, snapshot.req 즉시 교체) - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 자산 중심 영구 슬롯 모델(dcy-020)에서 전략 read 표면을 슬롯 단일 테이블 RPC 로 재설계한다. `trex.snapshot.req`(view/assets[]/available_slots/watchdog[] 구조)를 폐기하고 `trex.slots.req` 하나로 교체해, 전략·운영자가 슬롯 테이블 하나로 universe·포지션·워치독을 읽게 한다. (dcy-025 FR-1/2/4/5/6) - -## Requirement -- per-instance 큐 `trex.slots.req`(기존 큐명 규칙 — default 는 literal, 그 외 `.{instance_key}` 접미)를 신설하고 `trex.snapshot.req` 파이프라인(큐 선언·핸들러·legacy stub 경로)을 **같은 배포에서 제거**한다(즉시 교체, 과도기 없음 — 전략 모듈 lockstep cutover, dcy-025 OQ-1). -- 요청 payload: `{filter?: str, spy?: bool}`. - - `filter` 는 **status 단일 값**(상호배타) `{all, inactive, pending, holding, error}`. 미지정 시 `all`. `pending` = `pending_buy ∪ pending_sell`. `inactive` 판정 키는 슬롯 **status**(hub `asset_quantity` 아님). 파생 view(ALL/BUYABLE/HELD)와 `View` enum 은 폐기한다 — 매수 가능 목록 = `filter=inactive`. - - `spy=true` 면 워치독을 갱신하지 않는다(생존 신호 미발생 — 기존 `actor="operator"`/`touch_watchdog=false` 의 일반화, REQ-034 정합). 기본 `false`(holding 슬롯 워치독 터치). -- 응답 payload: `{slots: [...], available_slots: int|null, degraded: bool, reason, message}`. `assets[]`·`watchdog[]` 는 응답에 포함하지 않는다(워치독은 슬롯 행에 흡수 — REQ-074). `available_slots` 는 capacity hint — `max_active_slots` 미설정 시 `null`, 설정 시 `max(0, M − count_active)`. admission gate(`capacity_full`)는 별도로 유지되며 `available_slots` 가 이를 대체하지 않는다. -- `strategy_id`/`strategy_key`/`strategy_params`/`active_strategy_id` 는 본 표면에서 제외한다(요청 필터·응답 필드 모두 없음). 인스턴스=다중 전략이나 전략↔자산 할당 모델은 portfolio-hub 주도 후속 discovery — 확정 시 strategy 필드를 additive 로 재도입한다(dcy-025 OQ-4). - -## Acceptance Criteria -1. Given 기동된 데몬, When 토폴로지 선언, Then `trex.slots.req`(per-instance) 큐가 존재하고 `trex.snapshot.req` 큐·핸들러·legacy stub 경로는 존재하지 않는다. -2. Given `{filter: "inactive"}` 요청, When 처리, Then status 가 `inactive` 인 슬롯 행만 반환된다(매수 가능 목록). `{filter: "pending"}` 은 `pending_buy ∪ pending_sell`, 미지정은 전체. 잘못된 filter 값은 `all` 또는 명시 거부로 일관 처리된다. -3. Given 응답 payload, When 키 집합 검사, Then `slots`/`available_slots`/`degraded`/`reason`/`message` 가 있고 `assets`·`watchdog`·strategy 계열 키는 없다. -4. Given `spy=true` 요청, When holding 슬롯 포함 조회, Then 워치독 `last_seen_at` 이 갱신되지 않는다. Given 기본(`spy` 미지정/false) 요청, Then holding 슬롯 워치독이 갱신된다(REQ-034 시맨틱 보존). -5. Given degraded 상태(universe fetch 실패 등), When 조회, Then `degraded=true` + `reason` 이 기존 REQ-028 신호 의미대로 운반된다. -6. Given `max_active_slots` 미설정 인스턴스, When 조회, Then `available_slots = null`. Given `max_active_slots = M` 설정 인스턴스 + 현재 active N, When 조회, Then `available_slots = max(0, M − N)`. `available_slots` 는 hint 이며 admission gate 를 대체하지 않는다. - -## Impacted Area -- Docs: docs/interface-contract.md(snapshot→slots 계약 — REQ-075), docs/runtime-flows.md -- Modules: trex_core/adapters/messaging/topology.py(큐명), trex_core/application/messaging_handlers.py(slots 핸들러·snapshot 핸들러 제거; (CHG-20260612-02) status 필터 repo 위임 + watchdog 조회 재사용), trex_core/application/portfolio.py(`View` enum 폐기) -- Tests: tests/application/(slots 핸들러 filter/spy/응답 키 집합), tests/adapters/messaging/(토폴로지) - -## Notes -- 즉시 교체(과도기 없음)이므로 전략 모듈(trex-orb-v1) 소비 측을 같은 전환점에 lockstep 변경해야 한다(dcy-025 R-1/NFR-3) — 배포 전 orb-v1 의 `trex.slots.req` 대응 준비 확인. dcy-020 cutover 선례와 동일하게 단일 전환점 관리. -- 행 스키마(자산·워치독·overlay 흡수, 정본 라벨)는 REQ-074 가 소유한다. -- 운영자 CLI 의 새 표면 사용은 REQ-063 change-req(CHG-20260611-01). -- 출처: dcy-025 FR-1/2/4/5/6, NFR-1(워치독 일관성)·NFR-3(cutover 안전)은 본 REQ AC/Notes 에 흡수. - -## Change Log - -### CHG-20260615-02 - -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented, BAT-042). 신규 AC-7(strategy_state 맵+occupancy_seq additive, batched 조립) 구현·검증 완료 — `tests/application/test_slots_handler_strategy_state.py` + 기존 `test_slots_handler.py` 회귀 green. 기존 AC-1~6 불변. 479 passed, ruff clean. Implemented 복귀. - -### CHG-20260615-01 - -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-028/issue #26 C-1) `trex.slots.req` 응답의 각 슬롯 행에 **strategy_state 맵**(`{: {version,data,updated_at}}`, REQ-079/074)과 **occupancy_seq**(int, REQ-081)를 additive 로 추가한다. 핸들러는 반환 slot_id 들로 `StrategyStateStore`(REQ-079) batched 조회 후 group by 해 행별 맵으로 조립한다(추가 왕복 없음). 미보유 시 omit/`{}`. 기존 envelope 키(`slots`/`available_slots`/`degraded`/`reason`/`message`)·filter·spy·degraded 동작은 불변. AC-3 의 "strategy 계열 키 없음"은 dcy-025 가 제외한 strategy_* **reasoning** 필드(strategy_id/params/active)를 가리키며, 본 변경의 `strategy_state`(opaque 상태 맵)·`occupancy_seq`는 그와 구분되는 신규 additive 필드다. - -#### Intent - -- 전략 데몬이 슬롯과 함께 opaque strategy-state 를 추가 왕복 없이 read(C-1 piggyback). write 는 별도 RPC(REQ-080). - -#### Acceptance Criteria Delta - -- Added: - - AC-7(신규): 행에 `strategy_state` 맵(보유 시)과 `occupancy_seq` 가 포함되고, batched 조회로 조립되어 추가 RPC 왕복이 없다. strategy_* reasoning 필드 부재는 유지(AC-3 정합). -- Updated: - - AC-3: 응답 키 집합에서 제외되는 "strategy 계열"은 strategy_* reasoning 필드로 한정하고 `strategy_state`/`occupancy_seq` additive 필드는 허용함을 명확화. -- Removed: 없음 - -#### Impacted Area Delta - -- Docs: docs/interface-contract.md(strategy_state·occupancy_seq 행 필드 — REQ-082) -- Modules: trex_core/application/messaging_handlers.py(slots 핸들러 strategy_state batched 조립), trex_core/application/portfolio.py(slot_to_dict) -- Tests: tests/application/(strategy_state 맵·occupancy_seq 행 조립, batched 조회) - -#### Delivery Trace Delta - -- Batches: BAT-035(dcy-025, 기존 Implemented 근거) 불변 — 신규 AC-7 은 dcy-028 batch 에서 구현·검증. -- Releases: REL-006(REQ-073 released) — 기존 계약 영향 없음(additive). 신규 필드는 REL-007 이후 dcy-028 batch. - -#### Revalidation Impact - -- Reverification Needed: yes (신규 AC-7 만) -- Existing Implementation Invalidated: no (기존 envelope/filter/spy 동작 보존) -- Status Recommendation: revert-to-approved (Implemented → Approved — 신규 additive AC 구현·검증 대기, 선례 REQ-002/006 CHG-20260612) -- Follow-up Action: confirm-req → dcy-028 batch 에서 REQ-079/080/081 과 함께 구현·검증 → confirm-req-implemented - -#### Validation Plan - -- slots 핸들러 테스트: 행에 strategy_state 맵(보유/미보유)·occupancy_seq 포함, batched 조회로 추가 왕복 없음, 기존 키 집합/filter/spy/degraded 회귀. - -### CHG-20260612-02 - -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary - -- (change-req, issue #25) slots RPC 핸들러(`make_slots_rpc_handler`)가 status 필터링을 `SlotRepository.list_slots(instance_key, status_filter=...)`(REQ-002/006) 에 위임해 per-request full scan + Python 필터를 제거한다. 추가로 watchdog 갱신용 holding 조회를, 응답 필터가 `all`/`holding` 이라 이미 holding 을 포함한 경우 fetch 결과를 재사용해 두 번째 커넥션을 생략한다(그 외 필터는 기존대로 `list_holding_slots`). **관찰 가능한 응답 계약(AC-1~6)은 불변** — filter 결과·키 집합·spy·degraded·available_slots 동일. AC delta 없음 → **Implemented 유지**(BAT-040 재검증). - -#### Intent - -- issue #25 (1) DB 레벨 status 필터 + (2) 두 쿼리 통합. orb-v1 이 매 cycle `{filter:"inactive"}` 로 요청할 때 200-slot 인스턴스에서 전 행 fetch 를 반복하던 비용 제거. 동작 보존 내부 최적화. - -#### Acceptance Criteria Delta - -- Added/Updated/Removed: 없음 (AC-1~6 관찰 계약 불변, 핸들러 내부 위임만) - -#### Impacted Area Delta - -- Docs: 없음 -- Modules: `trex_core/application/messaging_handlers.py`(`make_slots_rpc_handler` — `list_slots(status_filter)` 위임 + watchdog 조회 재사용) -- Tests: 기존 filter/spy/응답 키 집합 테스트 회귀 + status_filter 위임 확인 - -#### Delivery Trace Delta - -- Batches: BAT-040 (예정, issue #25) -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-040 에서 filter/spy/degraded/available_slots 회귀) -- Existing Implementation Invalidated: no (관찰 응답 동일, 내부 위임만 — AC evidence 유효) -- Status Recommendation: **Implemented 유지** -- Follow-up Action: BAT-040 구현·검증으로 재확인(REQ-002/006 Implemented 복귀와 동일 배치) - -#### Validation Plan - -- BAT-040: 기존 `test_filter_*`/`test_spy_*`/`test_response_keys_*`/`test_available_slots_*` 회귀 green + status_filter 위임 경로(필터별 list_slots 인자) 확인. - -### CHG-20260612-IMPL -- Date: 2026-06-12 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-037 (release-candidate) — AC-3 반전(available_slots 포함)·AC-6 신규(null/잔여/0 클램프) 16 tests passed, 423 전체 회귀 없음. Blocking Issues 없음. - -### CHG-20260612-01 -- Date: 2026-06-12 -- Author: Justin Ko - -#### Change Summary -- 응답 payload 에 `available_slots: int|null` 추가. orb-v1 이 매 cycle `{filter: "inactive"}` 로 슬롯을 요청할 때 capacity 가 차 있어도 intent 를 쏴야만 `capacity_full` 을 알 수 있던 문제 해소. `max_active_slots` 미설정 시 `null`(제한 없음), 설정 시 `max(0, M − active)` 정수. - -#### Intent -- slots RPC 를 read 표면으로 유지하면서도 capacity 힌트를 제공해 불필요한 intent 왕복을 줄인다. admission gate(`capacity_full`)는 그대로 유지 — `available_slots` 는 hint 이며 orb-v1 이 skip 최적화에 사용한다. - -#### Acceptance Criteria Delta -- Updated: - - AC-3: 응답 키 집합에서 `available_slots` 제외 → **포함**으로 반전. `assets`·`watchdog`·strategy 계열은 여전히 제외. -- Added: - - AC-6: `available_slots` 값 규칙 — `max_active_slots` 미설정 시 `null`, 설정 시 `max(0, M − count_active)`. 응답의 `available_slots` 는 hint 이며 admission gate 를 대체하지 않는다. -- Removed: 없음 - -#### Impacted Area Delta -- Modules: - - `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler` 응답에 `available_slots` 추가 - - `trex_core/domain/slot/service.py` — `compute_slot_capacity` 호출 경로 확인 -- Tests: `tests/application/test_slots_handler.py` — AC-3 키 집합 검사 및 AC-6 값 검증 갱신 -- Docs: `docs/interface-contract.md` — 응답 스키마 반영 (REQ-075 소관) - -#### Delivery Trace Delta -- Batches: BAT-035 (REQ-073 구현/확인) — AC-3 evidence 무효. 신규 batch 필요. -- Releases: REL-006 (배포 완료) — 운영 중 발견, 비파괴적 필드 추가(additive). - -#### Revalidation Impact -- Reverification Needed: yes -- Existing Implementation Invalidated: yes (AC-3 — `available_slots` 부재 검증이 반전됨) -- Status Recommendation: Implemented → **Approved** (차기 batch 구현·검증 후 복귀) -- Follow-up Action: draft-batch → implementation → verification → confirm-req-implemented - -#### Validation Plan -- 응답에 `available_slots` 키 존재 확인. -- `max_active_slots` 설정 인스턴스: active N 개일 때 `available_slots = M − N`, active ≥ M 이면 `0`. -- `max_active_slots` 미설정: `available_slots = null`. -- 기존 AC-1/2/4/5 회귀 없음 확인. - -### CHG-20260611-01 -- Date: 2026-06-11 -- Author: Justin Ko - -#### Change Summary -- dcy-025 FR-1/2/4/5/6 로부터 최초 생성(Proposed). `trex.slots.req` RPC 계약(filter=status 단일/spy 워치독 무터치/응답 slots+health 만/strategy 제외/즉시 교체). - -#### Intent -- 전략 read 표면을 슬롯 단일 테이블로 재설계 — assets/available_slots/watchdog 병렬 구조와 파생 view 폐기. - -#### Acceptance Criteria Delta -- Added: - - AC-1~5 신규 -- Updated/Removed: none - -#### Impacted Area Delta -- Docs: interface-contract/runtime-flows -- Modules: topology/messaging_handlers/portfolio(View 폐기) -- Tests: slots 핸들러/토폴로지 - -#### Delivery Trace Delta -- Batches: none(승인 후 draft-batch) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no(신규) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current(Proposed) -- Follow-up Action: confirm-req - -#### Validation Plan -- 신규 batch: 토폴로지 선언/제거 단위, filter·spy·응답 키 집합 단위, 워치독 터치/무터치, degraded 신호 회귀. diff --git a/docs/srs/Interface/req-080_strategy-state-set-rpc.md b/docs/srs/Interface/req-080_strategy-state-set-rpc.md deleted file mode 100644 index 6903c85..0000000 --- a/docs/srs/Interface/req-080_strategy-state-set-rpc.md +++ /dev/null @@ -1,87 +0,0 @@ -# req-080: `trex.slots.strategy_state.set` write RPC 계약 — occupancy_seq·version CAS - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 전략 데몬이 슬롯의 opaque strategy-state 를 write 하는 신규 RPC 계약을 정의한다. read 는 기존 `trex.slots.req`(REQ-073) 에 piggyback 하고(추가 왕복 없음), write 만 별도 RPC 로 분리해 `evaluate()` 순수성과 재시도 격리를 보존한다. (dcy-028 FR-2/OQ-2, issue #26 C-2) - -## Requirement -- 신규 RPC `trex.slots.strategy_state.set` 를 기존 계약 패턴(direct reply-to + JSON + per-instance `queue_name({base}.{instance_key})`)으로 신설한다. -- 요청 payload: `{slot_id, strategy_id, expected_version:int, expected_occupancy_seq:int, data:str}`. `correlation_id = "{slot_id}:{strategy_id}:{expected_version}"`(멱등 키), delivery persistent. -- 성공 응답: `{ok:true, slot_id, strategy_id, version:}`. -- 실패 응답 reason 전체 집합 = `{occupancy_changed, version_conflict, slot_inactive, state_too_large, bad_request}`: - - `occupancy_changed`: 슬롯 현재 occupancy_seq ≠ expected_occupancy_seq → `{ok:false, reason, occupancy_seq:}` (저장 안 함; 데몬 re-read 후 fresh restart). - - `version_conflict`: stored version ≠ expected_version → `{ok:false, reason, version:}`. - - `slot_inactive`: 슬롯 inactive/미존재 → `{ok:false, reason}` (저장 안 함). - - `state_too_large`: `data` 가 size cap(REQ-079/NFR) 초과 → `{ok:false, reason}` (저장 안 함). - - `bad_request:`: malformed payload(기존 intent 핸들러 패턴) → `{ok:false, reason}`. -- 핸들러는 REQ-079 `StrategyStateStore` 의 CAS 로 위임한다(저장 의미는 REQ-079 소유). `data` 는 opaque 로 전달하고 핸들러는 해석하지 않는다. -- 동일 `correlation_id` 재전송은 부작용 없음(멱등). 미인식 reason 에 대해 소비자 fail-closed 를 계약에 명시한다. - -## Acceptance Criteria -1. Given 기동된 데몬, When 토폴로지 선언, Then `trex.slots.strategy_state.set`(per-instance) 큐가 존재하고 direct reply-to 로 응답한다. -2. Given 유효 요청(occupancy_seq·version 일치), When set, Then `{ok:true, version:<+1>}` 를 반환하고 저장된다. -3. Given occupancy_seq 불일치, Then `{ok:false, reason:"occupancy_changed", occupancy_seq:}`. Given version 불일치, Then `{ok:false, reason:"version_conflict", version:}`. 둘 다 저장되지 않는다. -4. Given inactive 슬롯, Then `{ok:false, reason:"slot_inactive"}`. Given size cap 초과, Then `{ok:false, reason:"state_too_large"}`. Given malformed payload, Then `{ok:false, reason:"bad_request:"}`. -5. Given 동일 `correlation_id` 재전송, When 처리, Then 부작용 없이 동일 결과가 반환된다. -6. Given 응답 reason, When 집합 검사, Then `{occupancy_changed, version_conflict, slot_inactive, state_too_large, bad_request}` 외 값이 없다. - -## Impacted Area -- Docs: docs/interface-contract.md(strategy_state.set 계약 — REQ-082), docs/runtime-flows.md(write 흐름 — REQ-082) -- Modules: trex_core/adapters/messaging/topology.py(큐명), trex_core/application/messaging_handlers.py(신규 set 핸들러 — REQ-079 store 위임), trex_core/runtime/daemon.py(핸들러 와이어링) -- Tests: tests/application/(set 핸들러 6 reason 케이스 + 멱등), tests/adapters/messaging/(토폴로지) - -## Notes -- 저장·CAS 의미는 REQ-079 가 소유한다 — 본 REQ 는 RPC 계약(요청/응답 shape, reason 운반, 멱등, 큐). 핸들러는 store 로 위임만 한다. -- read piggyback(REQ-073/074)과의 비대칭(read 무료/write 분리)은 dcy-028 OQ-2 의도. 사이클당 다중 슬롯 write 시 N개 set RPC — optional batch 변형은 v1 범위 밖 후속. -- 출처: dcy-028 FR-2(C-2), OQ-2(별도 RPC), reject reason 집합(trex-tsl #4705990067 답변 정합). - -## Change Log - -### CHG-20260615-03 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042 verification — `trex.slots.strategy_state.set` 큐(topology/rabbitmq declare) + `make_strategy_state_set_handler`(store 위임) + daemon consumer 스레드. AC-1~6 evidence: `tests/application/test_strategy_state_set_handler.py`(성공+5 reason+bad_request 2종+멱등+reason 집합). 479 passed, ruff clean. Blocking Issues 없음. - -### CHG-20260615-02 -- Date: 2026-06-15 -- Author: Justin Ko -- Type: status -- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(6개) 충족, source dcy-028(OQ-2 정합) 일치, reject reason 집합 trex-tsl #4705990067 답변과 정합. blocker 없음. - -### CHG-20260615-01 -- Date: 2026-06-15 -- Author: Justin Ko - -#### Change Summary -- dcy-028 FR-2/OQ-2 로부터 최초 생성(Proposed). strategy_state.set write RPC 계약 + occupancy_seq·version CAS + reject reason 전체 집합. - -#### Intent -- write 를 별도 RPC 로 분리해 evaluate 순수성·재시도 격리 보존, read 는 slots.req piggyback. - -#### Acceptance Criteria Delta -- Added: 큐/성공/occupancy·version reject/inactive·size·bad_request/멱등/reason 집합 6개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: interface-contract.md, runtime-flows.md(REQ-082) -- Modules: topology.py, messaging_handlers.py, daemon.py -- Tests: set 핸들러 reason 케이스, 토폴로지 - -#### Delivery Trace Delta -- Batches: none (편성 예정) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성(REQ-079/081/082 + REQ-073/074 CHG 동일 batch) - -#### Validation Plan -- set 핸들러 단위 테스트(6 reason 케이스 + 멱등) + 토폴로지 선언 테스트로 RPC 계약 검증. diff --git a/docs/srs/Interface/req-086_liquidation-reason-actor-exposure.md b/docs/srs/Interface/req-086_liquidation-reason-actor-exposure.md deleted file mode 100644 index 9803509..0000000 --- a/docs/srs/Interface/req-086_liquidation-reason-actor-exposure.md +++ /dev/null @@ -1,53 +0,0 @@ -# req-086: 청산 reason+actor 동반 노출 — 안전망/전략 구분 가시화 - -- Status: Implemented -- Type: Interface -- Priority: Medium -- Owner: project maintainers - -## Intent -- watchdog 소유 안전망 청산이 `reason=stop_loss`로만 보여 "전략의 진짜 손절"로 오해됐다(2026-06-15 064400). 신규 필드 없이, 기존 `reason`(조건축)과 기존 `actor`(주체축)를 **함께** 노출해 "안전망 자율청산(actor=safety_sell)"과 "전략 손절(actor=strategy)"을 운영자가 구분하게 한다. (dcy-029 FR-3, OQ-4=Option A) - -## Requirement -- 기존 `reason` 어휘 `{stop_loss, take_profit, forced_close_at, market_close_liquidation}`(safety_sell 발화)와 전략 SellIntent의 reason을 계약으로 고정한다(신규 reason 값·신규 분류 필드 없음). -- 슬롯 전이 로그/이벤트/조회 뷰에 `reason`과 `actor`(`safety_sell`/`strategy`/`execution_loop`)를 **함께** 노출한다. 특히 주문 결과 로깅(REQ-085)·조회 CLI(REQ-088)·청산 로그에 actor가 reason과 같이 보이도록 한다. -- `docs/interface-contract.md`에 reason+actor 어휘와 "안전망 자율청산 family vs 전략 매도" 구분을 명문화한다. -- 기존 `slots.req`/이벤트/전이 기록의 기존 동작은 깨지 않는다(additive 노출). - -## Acceptance Criteria -1. Given safety_sell이 stop_loss로 발화, When 전이/로그, Then `reason=stop_loss` + `actor=safety_sell`이 함께 기록된다. -2. Given 전략 SellIntent 매도, When 전이/로그, Then `actor`가 전략 주체로 기록되어 안전망과 구분된다. -3. Given 청산 로그 라인, When 운영자가 확인, Then reason만으로 오해되지 않도록 actor가 동반 노출된다. -4. Given safety_sell 4경로(stop_loss/take_profit/forced_close_at/market_close_liquidation), When 발화, Then 각 reason이 actor=safety_sell과 함께 정확히 기록된다. -5. Given 기존 reason 소비처(`_SELL_REJECT_RETRYABLE` 등), When 변경 적용, Then 기존 동작 회귀 없음(reason 값 불변). -6. Given interface-contract.md, When 갱신 후, Then reason+actor 어휘와 구분 규칙이 문서화돼 있다. - -## Impacted Area -- Docs: docs/interface-contract.md(reason+actor 어휘), docs/runtime-flows.md(청산 분류 노출) -- Modules: trex_core/application/safety_sell.py·execution.py(로그에 actor 동반), 조회 뷰(REQ-088)·주문결과 로깅(REQ-085)과 협조. `slot_transition_log`는 이미 actor 기록. -- Tests: safety_sell 각 경로 reason·actor 단위, 노출 형식 단위 - -## Notes -- 코드 확인: `slot_transition_log`는 이미 `actor`를 기록하고, safety_sell은 이미 구분된 reason으로 발화한다. 본 REQ는 둘을 **함께 노출**(로그/뷰/계약 문서)하는 가시화가 핵심이며 신규 필드·신규 reason 값은 만들지 않는다(dcy-029 OQ-4 Option A). -- 출처: dcy-029 FR-3, OQ-4. - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-3로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-046 release-candidate, dcy-029 P1). 근거: 단위/문서 AC 매핑(REQ-086 reason+actor 로그·interface-contract; REQ-088 operator_report 집계·net PnL·KST 경계·CLI report; REQ-089 docs/operations.md 4영역; REQ-090 runtime-flows·project-structure 정합). 14 passed(report+safety_sell), 전체 537 passed/47 skipped 회귀 0, ruff clean. report direct-DB·Loki 실 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-088_operator-pnl-query-cli.md b/docs/srs/Interface/req-088_operator-pnl-query-cli.md deleted file mode 100644 index 01a1cdf..0000000 --- a/docs/srs/Interface/req-088_operator-pnl-query-cli.md +++ /dev/null @@ -1,57 +0,0 @@ -# req-088: 운영 조회 CLI — 보유·오늘 체결·실현 PnL·대기 주문 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: project maintainers - -## Intent -- 일일 실현 PnL을 `ledger_outbox`에서 수기 계산해야 했다. 운영자가 단일 CLI 명령으로 현재 보유·오늘 체결·오늘 실현 PnL(수수료·세금 반영)·대기 주문을 조회해 일일 리포트 자동화의 기반을 만든다. (dcy-029 FR-5, OQ-1=CLI, RPC 없음) - -## Requirement -- 운영자 CLI(`trex_core/runtime/cli.py`)에 신규 서브커맨드(예: `report` 또는 `pnl`)를 추가해 다음을 출력한다: - - **현재 보유**: holding 슬롯의 symbol/portfolio_id/qty/avg_price. - - **오늘 체결**: 당일(trading_date, KST 경계) 체결 건수/내역. - - **오늘 실현 PnL**: 수수료·세금 반영(BAT-038 정책과 동일 계산), `ledger_outbox`/체결 기록을 단일 권위 출처로 집계. - - **대기 주문**: pending_* 슬롯. -- 조회는 **DB-backed 시점 집계**다. 신규 RabbitMQ RPC는 추가하지 않는다(dcy-029 OQ-1). 데몬 미경유로 DB를 직접 읽거나 기존 read 경로를 재사용한다. -- 청산/체결 항목에 reason+actor(REQ-086)를 함께 보여 안전망/전략 구분이 되게 한다. -- 출력은 사람용 표/요약 + 가능하면 머신 파싱용(예: `--json`) 형태를 고려한다(자동화 기반). - -## Acceptance Criteria -1. Given holding 슬롯 존재, When `report` 실행, Then 보유 symbol/qty/avg_price가 출력된다. -2. Given 당일 체결 N건, When 실행, Then 오늘 체결 건수/내역이 출력된다. -3. Given 당일 매도 체결, When 실행, Then 실현 PnL이 수수료·세금 반영 값으로 출력되고 `ledger_outbox` 직접 집계와 일치한다. -4. Given pending_* 슬롯, When 실행, Then 대기 주문이 출력된다. -5. Given "오늘" 경계, When trading_date(KST) 기준, Then 전일 체결은 오늘 집계에서 제외된다. -6. Given 신규 표면, When 실행, Then 신규 RabbitMQ RPC 없이 동작한다(CLI/DB-backed, OQ-1). - -## Impacted Area -- Docs: docs/runtime-flows.md(운영자 CLI 조회 흐름), docs/operations.md(사용법 — REQ-089) -- Modules: trex_core/runtime/cli.py(신규 서브커맨드), PnL 집계 read(신규 헬퍼 — `ledger_outbox`/체결 집계), 기존 slot read 재사용 -- Tests: 집계 함수 단위(수수료·세금·trading_date 경계), CLI 서브커맨드 단위 - -## Notes -- PnL 계산은 BAT-038(매도 증권거래세+수수료) 정책을 단일 출처로 재사용(dcy-029 NFR-3/A-3). 부분 정산·재시도 중 outbox 상태는 집계 시점 기준으로 명시. -- 출처: dcy-029 FR-5, OQ-1. - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-5로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-046 release-candidate, dcy-029 P1). 근거: 단위/문서 AC 매핑(REQ-086 reason+actor 로그·interface-contract; REQ-088 operator_report 집계·net PnL·KST 경계·CLI report; REQ-089 docs/operations.md 4영역; REQ-090 runtime-flows·project-structure 정합). 14 passed(report+safety_sell), 전체 537 passed/47 skipped 회귀 0, ruff clean. report direct-DB·Loki 실 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Interface/req-098_alarm-envelope-wire-contract.md b/docs/srs/Interface/req-098_alarm-envelope-wire-contract.md deleted file mode 100644 index db44ba0..0000000 --- a/docs/srs/Interface/req-098_alarm-envelope-wire-contract.md +++ /dev/null @@ -1,99 +0,0 @@ -# req-098: 알람 envelope wire 계약 정의 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev - -## Intent -- 제품군 모든 모듈(core/orb/tsl/kdh)이 알람을 발신할 때 사용할 표준 메시지 envelope 스키마를 trex-core가 wire 계약으로 정의하고 `docs/interface-contract.md`에 명시한다. -- 이 계약은 trex-notifier가 알람을 수집·분류할 때의 파싱 기준이 되며, 다른 모듈 repo(orb/tsl/kdh)와 trex-notifier가 이 문서를 단일 출처(source of truth)로 참조한다. -- 기존 wire 계약(slots RPC/trex.events/trex.control.req)은 일절 변경하지 않는다. - -## Requirement -- trex-core는 알람 envelope의 표준 JSON 스키마를 아래 필드로 정의하고, `docs/interface-contract.md`에 신규 섹션 `Interface: trex.alerts (알람 채널, dcy-033)`으로 버전드 명시해야 한다. - - `source` :: str — 알람 발신 모듈 식별자 (예: `"trex-core"`, `"trex-orb-v1"`, `"trex-tsl-v1"`, `"kis-data-hub"`) - - `severity` :: `"critical"` | `"warn"` | `"info"` — 등급 (소문자 리터럴) - - `code` :: str — 알람 식별 코드 (예: `"kill_switch_fired"`, `"fill_price_unresolvable"`) - - `title` :: str — 사람이 읽는 1행 요약 - - `fields` :: dict[str, any] — 선택 구조화 컨텍스트 (예: `{"slot_id": "...", "symbol": "..."}`) - - `occurred_at` :: ISO-8601 datetime string (UTC 또는 KST+09:00 offset 포함) - - `schema_version` :: str — 현재 값 `"1"` (semver 주 버전 문자열; 소비자는 미인식 버전 무시·경고) -- 계약 섹션에 routing key 규약(`alarm.{severity}.{source}`)과 exchange 이름(`trex.alerts`)을 함께 기술한다. -- 이 스키마를 변경(필드 추가 포함)하면 `schema_version`을 올리거나 additive 변경 여부를 명시하도록 Update Trigger를 문서에 포함한다. - -## Acceptance Criteria - -1. Given `docs/interface-contract.md`에 `Interface: trex.alerts (알람 채널, dcy-033)` 섹션이 존재하지 않을 때, When REQ-098 구현을 완료하면, Then 해당 섹션이 위 7개 필드(source/severity/code/title/fields/occurred_at/schema_version)의 JSON 스키마 명세를 포함해야 한다. -2. Given 섹션이 추가됐을 때, When 명세를 검토하면, Then `severity` 값이 `"critical"` | `"warn"` | `"info"` 세 리터럴로 제한되고, `schema_version="1"`이 현재 버전으로 명시되어야 한다. -3. Given 명세가 추가됐을 때, When 기존 interface-contract.md의 다른 섹션(slots RPC/trex.events/trex.control.req/strategy_state)을 검토하면, Then 기존 섹션의 내용이 변경되지 않아야 한다(가산·격리 불변 — diff는 신규 섹션 추가뿐). -4. Given envelope 스키마 명세가 완성됐을 때, When routing key 규약을 확인하면, Then `alarm.{severity}.{source}` 형식이 명시되어야 한다. -5. Given 명세가 추가됐을 때, When Update Trigger를 확인하면, Then 알람 envelope 스키마 변경(breaking/additive 모두) 시 이 문서를 갱신해야 한다는 트리거가 포함되어야 한다. -6. Given 다른 모듈 repo(orb/tsl/kdh) 개발자가 알람 emitter를 구현할 때, When `docs/interface-contract.md`의 알람 계약 섹션을 읽으면, Then 자체 `trex_core` import 없이 envelope 구성에 필요한 모든 정보(필드·타입·필수 여부·schema_version·routing key)를 얻을 수 있어야 한다. - -## Impacted Area -- Docs: `docs/interface-contract.md` — 알람 채널 신규 섹션 추가(기존 섹션 불변) -- Modules: 코드 변경 없음(이 REQ는 문서 계약만; emitter 구현은 REQ-100) -- Tests: `docs/interface-contract.md` diff가 기존 섹션 변경 0임을 확인하는 검증(batch verification 단계에서 수동 검토 또는 diff assertion) - -## Notes -- 이 REQ는 **계약 문서화**만 담당한다. envelope을 실제로 직렬화·발행하는 Python 코드(알람 emitter)는 REQ-100이 담당한다. -- `fields` 값은 opaque dict — core/notifier 모두 내용을 강제 파싱하지 않는다. 각 code별 권장 fields는 ALARM-INVENTORY.md를 참조한다. -- `occurred_at` 형식: 권장은 KST+09:00 offset-aware ISO-8601(기존 코어 로깅 관례와 통일). UTC도 수용. -- 다른 모듈(orb/tsl/kdh)은 `trex_core`를 import하지 않는다. 이 계약은 **문서 규약**이며 런타임 공유 코드가 아니다(Discovery 제약 A-1). -- trex-notifier가 `schema_version`이 미인식인 envelope을 수신하면 경고 로그 후 무시한다(소비자 책임 — notifier REQ 범위). -- dcy-033 성공기준 S-1(CRITICAL 알람 텔레그램 도달)·S-3(wire 계약 무회귀)의 선행 계약. - -## Change Log - -### CHG-20260621-02 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- Status: Approved → Implemented. BAT-049 confirm-batch-verification 팀장 승인(QC PASS-WITH-NOTES blocker 0 + 팀장 실인프라 e2e 통과). - -#### Delivery Trace Delta -- Batches: BAT-049 (release-candidate) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no - -### CHG-20260621-01 -- Date: 2026-06-21 -- Author: trex-core-dev - -#### Change Summary -- REQ-098 신규 생성(Proposed). dcy-033 FR-1 대응: 알람 envelope wire 계약 문서화. - -#### Intent -- dcy-033(제품군 알람 전달 체계) confirmed Discovery에서 요구하는 표준 알람 envelope 스키마를 trex-core wire 계약으로 정식화한다. - -#### Acceptance Criteria Delta -- Added: AC-1~6 (신규 생성) -- Updated: (없음) -- Removed: (없음) - -#### Impacted Area Delta -- Docs: - - `docs/interface-contract.md` 신규 섹션 추가 -- Modules: - - 코드 변경 없음 -- Tests: - - interface-contract diff 검증(기존 섹션 불변 확인) - -#### Delivery Trace Delta -- Batches: (미정, M2 이후 큐잉) -- Releases: (미정) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 시 REQ-099/100/101과 묶어 단일 batch로 처리 예정 - -#### Validation Plan -- `docs/interface-contract.md`에 알람 채널 섹션이 추가됐는지 수동 확인 -- 기존 섹션 diff=0 확인(grep 또는 수동 비교) -- 7개 필드·severity 리터럴·schema_version·routing key 규약이 모두 명시됐는지 체크리스트 검토 diff --git a/docs/srs/Interface/req-104_reconcile-warning-strategy-gate.md b/docs/srs/Interface/req-104_reconcile-warning-strategy-gate.md deleted file mode 100644 index aaf68f2..0000000 --- a/docs/srs/Interface/req-104_reconcile-warning-strategy-gate.md +++ /dev/null @@ -1,143 +0,0 @@ -# req-104: 전략 차단 게이트 — pause ∨ reconcile-warning 중 하나라도 걸리면 차단 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev - -## Intent -- 현재 전략 차단은 pause(수동 durable) 채널에만 의존한다. reconcile-warning(자동 durable) 채널을 추가해, **pause ∨ reconcile-warning 중 하나라도 존재하면 해당 슬롯/포트폴리오의 전략 수행(intent 수용 + slots read 차단 표면)을 차단**한다. -- dcy-034 FR-2 대응: 두 채널이 독립적으로 작동하며 OR 논리로 게이트를 확장한다. -- slots RPC wire 계약(`trex.slots.req` 응답 스키마)에서 `reconcile_warned` 플래그를 additive 필드로 노출해 운영자가 차단 원인을 식별할 수 있게 한다(NFR-4 최소 침습). -- **차단 범위(OQ-B 확정)**: reconcile-warning 차단은 **외부 전략 intent/read 전용**이다. **내부 안전망(safety-sell · watchdog · kill-switch)은 차단 예외**다. 근거: 집행 수량 정본은 브로커 잔고(execution.py)이므로 안전망은 수량 오류에 안전하며, 차단 시 포지션이 무보호로 방치된다. - -## Requirement -- 시스템은 `IntentService._admit_buy` 및 `_admit_sell` 수행 시 대상 슬롯의 `pause_store` 또는 `ReconcileWarningStore` 중 하나라도 차단 기록이 있으면 intent를 `rejected`(reason: `reconcile_warned` 또는 `paused`)로 반환해야 한다. -- **내부 안전망 경로(safety-sell / kill-switch / watchdog)는 reconcile-warning 차단을 우회해야 한다.** 이 경로들은 `IntentService._admit_*` 게이트를 거치지 않는 별도 내부 경로를 사용하며, reconcile-warning이 걸린 슬롯에 대해서도 안전망 매도를 실행할 수 있어야 한다(OQ-B 확정, 차단 대상은 외부 전략 intent만). -- 시스템은 slots RPC 핸들러(`make_slots_rpc_handler`)에서 각 슬롯 행에 `reconcile_warned: bool` 필드를 추가해 reconcile-warning 차단 여부를 노출해야 한다. -- `slot_to_dict` 함수는 `ReconcileWarningStore`를 선택적으로 주입받고, `reconcile_warned` 필드를 산출해야 한다(pause_store의 `paused` 필드 처리 방식과 동형). -- 기존 `paused` 필드 및 `warned` 필드는 변경 없이 유지해야 한다(additive only, NFR-4). -- reconcile-warning 차단으로 rejected된 buy intent는 `reason="reconcile_warned"` 를 반환해야 한다. - -## Acceptance Criteria -1. Given 슬롯의 portfolio_id가 ReconcileWarningStore에 warned 상태일 때, When buy intent가 제출되면, Then status=`rejected`, reason=`reconcile_warned`가 반환된다(전략 진입 차단). -2. Given 슬롯의 portfolio_id가 ReconcileWarningStore에 warned 상태일 때, When sell intent가 제출되면(외부 전략 경로), Then status=`rejected`, reason=`reconcile_warned`가 반환된다(전략 매도 차단). **단, safety-sell / kill-switch / watchdog의 내부 안전망 매도 경로는 이 차단을 받지 않으며 정상 실행되어야 한다(OQ-B 확정, 차단 예외).** -3. Given pause도 reconcile-warning도 없는 정상 슬롯에서, When buy/sell intent가 제출되면, Then 기존 admission 로직이 그대로 동작한다(오발동 없음). -4. Given pause만 있는 슬롯에서, When slots RPC가 호출되면, Then `paused=True`, `reconcile_warned=False` 필드가 반환된다(기존 paused 동작 불변). -5. Given reconcile-warning만 있는 슬롯에서, When slots RPC가 호출되면, Then `paused=False`, `reconcile_warned=True` 필드가 반환된다. -6. Given pause와 reconcile-warning이 모두 있는 슬롯에서, When slots RPC가 호출되면, Then `paused=True`, `reconcile_warned=True`가 모두 반환된다. -7. Given ReconcileWarningStore가 주입되지 않은 구성에서, When slots RPC가 호출되면, Then `reconcile_warned=False`(또는 필드 부재 시 기본값)로 반환되어 기존 클라이언트가 깨지지 않는다(하위 호환). -8. Given 기존 `warned` 필드(슬롯 qty ≠ hub asset_quantity 로컬 계산), When 신규 필드가 추가돼도, Then `warned` 필드 정의와 동작이 변경되지 않는다. - -## Impacted Area -- Docs: `docs/interface-contract.md` (slots RPC 응답 스키마 `reconcile_warned` 필드 추가 명시) -- Modules: - - `trex_core/application/portfolio.py` (`slot_to_dict` — `reconcile_warned` 필드 추가) - - `trex_core/application/intents.py` (`IntentService._admit_buy`, `_admit_sell` — reconcile-warning 게이트 추가) - - `trex_core/application/messaging_handlers.py` (`make_slots_rpc_handler` — ReconcileWarningStore 주입 파라미터 추가) - - `trex_core/runtime/daemon.py` (ReconcileWarningStore 주입 배선) -- Tests: - - `tests/application/test_intents.py` (reconcile_warned 차단 시나리오, 정상 슬롯 오발동 없음) - - `tests/application/test_messaging_handlers.py` (slots RPC reconcile_warned 필드 노출) - - `tests/application/test_portfolio.py` (`slot_to_dict` reconcile_warned 필드 검증) - -## Notes -- **safety-sell / kill-switch / watchdog 차단 예외(OQ-B 확정)**: 내부 안전망(safety-sell REQ-037~039, kill-switch REQ-045, watchdog REQ-034) 경로는 reconcile-warning 차단 예외다. 집행 수량 정본은 브로커 잔고(`execution.py`)이므로 안전망은 수량 오류에 안전하며, 차단하면 포지션이 무보호로 방치된다. 이 경로들은 `IntentService._admit_*` 게이트를 거치지 않는 별도 내부 호출 경로를 사용하므로 구조적으로 차단을 우회한다. batch design에서 경로 분리 구조를 명시적으로 확인한다. -- `reconcile_warned` 필드는 `slot_to_dict` 단에서 `ReconcileWarningStore.is_warned(slot_id)` 또는 `slot_id in list_warned()` 조회로 산출한다. batch design에서 성능(N+1) 고려해 bulk 조회 방식 선택. -- AMQP wire 계약 변경 사항: `trex.slots.req` 응답 행에 `reconcile_warned: bool` 필드 additive 추가. trex-orb-v1·trex-tsl-v1이 이 필드를 무시해도 하위 호환이 유지되는지 batch design에서 확인 필요. -- dcy-034 FR-2, NFR-4 대응. - -## Change Log - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-050(PR #31, ced7eda) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. pause∨reconcile-warning OR 게이트, 안전망 우회(OQ-B), slots RPC reconcile_warned 필드, 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-050 (core-A) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 없음. - -#### Validation Plan -- BAT-050 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-104 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). -- OQ-B 결정 반영: reconcile-warning 차단 범위 = 외부 전략 intent/read 전용. 내부 안전망(safety-sell/watchdog/kill-switch)은 차단 예외로 확정. AC-2에 예외 명시. -- Intent, Requirement, Notes에 OQ-B 결정 근거(집행 수량 정본=브로커, 차단 시 포지션 무보호) 추가. - -#### Intent -- 팀장 confirm-req 결정(OQ-B 차단 예외 확정)을 REQ에 반영. - -#### Acceptance Criteria Delta -- Added: 없음. -- Updated: AC-2(safety-sell/kill-switch/watchdog 차단 예외 명시). -- Removed: 없음. - -#### Impacted Area Delta -- Notes: 안전망 경로 차단 예외 설계 원칙 구체화. -- Modules: `intents.py` — 차단 게이트가 외부 전략 intent 경로에만 적용됨을 구현 시 명시. -- Tests: safety-sell/kill-switch가 reconcile-warning 슬롯에서도 실행됨을 확인하는 테스트 추가 필요. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch design에서 안전망 경로 분리 구조 명시 확인. - -#### Validation Plan -- 외부 intent reconcile_warned 차단 테스트 pass / safety-sell 차단 우회 테스트 pass / slots RPC reconcile_warned 필드 반환 테스트 pass / 기존 paused/warned 동작 회귀 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-104 신규 생성 (Proposed) — dcy-034 FR-2 대응, draft-req 단계. - -#### Intent -- pause 단독 게이트를 pause ∨ reconcile-warning OR 게이트로 확장해 자동 차단 채널을 활성화. - -#### Acceptance Criteria Delta -- Added: AC-1~8 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: interface-contract.md 갱신 예정. -- Modules: portfolio.py, intents.py, messaging_handlers.py. -- Tests: 신규 게이트 테스트. - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 wire 계약 영향 확인 포함 batch design. - -#### Validation Plan -- reconcile_warned 차단 및 정상 슬롯 오발동 없음 테스트 pass / slots RPC reconcile_warned 필드 반환 테스트 pass / 기존 paused/warned 동작 회귀 pass. diff --git a/docs/srs/Interface/req-105_sync-cli-command.md b/docs/srs/Interface/req-105_sync-cli-command.md deleted file mode 100644 index 4901ab5..0000000 --- a/docs/srs/Interface/req-105_sync-cli-command.md +++ /dev/null @@ -1,148 +0,0 @@ -# req-105: trex-core sync 명령 — 슬롯 수량을 브로커에 맞춰 갱신 + warning 해제 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev - -## Intent -- 운영자가 `trex-core sync` 명령으로 **브로커 잔고(정본)를 기준으로 대상 슬롯의 보유 수량을 갱신**(INACTIVE→HOLDING 전이 포함)하고, 갱신 후 브로커와 일치하면 해당 슬롯의 reconcile-warning을 자동 해제한다. -- cold-start 시 실계좌에 이미 보유 중인 종목(B 케이스)의 슬롯 onboarding 경로이기도 하다. -- dcy-034 FR-3, OQ-2/OQ-3 확정 반영: **브로커 잔고 정본 유지, 슬롯 qty ← 브로커**. 브로커에만 있고 portfolio-hub에 없는 종목(슬롯 부재)은 sync 대상 아님(자동 슬롯 생성 금지). -- portfolio-hub WAC(보유 평단) read 엔드포인트(FR-7, portfolio-hub **dcy-008 / REQ-043** `GET /v1/portfolios/{id}/holdings/cost-basis`) 가용 시 슬롯 avg_price를 seed한다. 포트 미가용 또는 조회 실패 시 수량만 갱신하고 WARN 로그를 남긴다(graceful degrade). -- **INACTIVE→HOLDING 전이 엣지 추가 승인(OQ-A)**: `ALLOWED_TRANSITIONS`에 `(INACTIVE, HOLDING)` 전이를 추가한다. 이 전이는 **actor=sync 또는 reconcile/reconcile_sync로 제한**되며, 일반 전략 경로(INACTIVE→PENDING_BUY→HOLDING)는 유지된다. transition_log reason은 `onboarding` 또는 `reconcile_sync`류로 식별 가능하게 기록한다. **wire 계약 무영향**: SlotState enum 상태값 5개(INACTIVE/PENDING_BUY/HOLDING/PENDING_SELL/ERROR) 불변, 전이 엣지만 추가이므로 slots RPC 스키마·상태값 변화 없음. - -## Requirement -- 시스템은 `trex-core sync [--portfolio-id | --all]` 서브커맨드를 제공해야 한다. -- sync는 portfolio-hub 유니버스에 있어 슬롯이 생성된 종목만 대상으로 한다. 브로커에만 있고 슬롯이 없는 종목은 sync가 건드리지 않고 unmanaged 경고를 로그에 남긴다(자동 슬롯 생성 금지). -- sync 실행 시 대상 슬롯마다 브로커 잔고(`BrokerPort.get_balance()`)를 조회하여 슬롯 qty를 브로커 수량으로 갱신해야 한다. 브로커 수량 > 0이고 슬롯이 INACTIVE이면 **INACTIVE→HOLDING 전이(actor=sync, reason=onboarding/reconcile_sync)**를 수행해야 한다. 이 전이는 OQ-A 승인으로 `ALLOWED_TRANSITIONS`에 추가되는 새 엣지를 사용하며 일반 전략 진입 경로와 구분된다. -- portfolio-hub WAC read 포트(FR-7: **portfolio-hub dcy-008 / REQ-043**, `GET /v1/portfolios/{id}/holdings/cost-basis`)가 연결되어 있으면, sync 시 보유 평단(WAC)을 읽어 슬롯 avg_price에 seed해야 한다. 포트가 없거나 조회 실패 시 avg_price seed를 건너뛰고 WARN 로그를 남긴다(graceful degrade). -- sync 후 슬롯 qty == 브로커 수량이면 `ReconcileWarningStore.clear_warned(slot_id)`로 reconcile-warning을 해제해야 한다. -- sync 실행 전 before 상태, 실행 후 after 상태(슬롯 qty, 브로커 qty, warning 해제 여부)를 출력해야 한다. -- sync는 confirmation 프롬프트(`--yes` 스킵 가능)와 `--dry-run` 모드를 지원해야 한다. live 계좌에서는 `--yes --live` 이중 확인을 요구해야 한다. -- sync는 직접-DB 경로(pause/resume 방식)로 구현하며, 데몬 RPC를 거치지 않는다. - -## Acceptance Criteria -1. Given cold-start 상태(슬롯 INACTIVE, 브로커 보유 qty > 0)에서 `trex-core sync --portfolio-id ` 실행 시, When sync가 완료되면, Then 슬롯 qty = 브로커 qty이고 슬롯 상태가 HOLDING으로 전이되며 reconcile-warning이 해제된다(S-2, OQ-A 승인 전이). -2. Given 슬롯 qty ≠ 브로커 qty인 B 케이스에서 sync 실행 시, When sync가 완료되면, Then 슬롯 qty = 브로커 qty로 갱신되고 reconcile-warning이 해제된다. -3. Given 브로커에만 있고 슬롯이 없는 종목(슬롯 부재)에서 sync 실행 시, When sync가 실행되면, Then 슬롯이 자동 생성되지 않고 unmanaged 경고가 로그에 남는다. -4. Given portfolio-hub WAC read 포트(portfolio-hub dcy-008 / REQ-043 `GET /v1/portfolios/{id}/holdings/cost-basis`)가 연결되어 있을 때, When sync를 실행하면, Then 슬롯 avg_price에 WAC 값이 seed된다. -5. Given portfolio-hub WAC read 포트가 없거나 조회 실패 시, When sync를 실행하면, Then avg_price seed 없이 수량 갱신만 수행하고 WARN 로그를 남긴다(graceful degrade). -6. Given `--dry-run` 플래그로 sync 실행 시, When 명령이 실행되면, Then 슬롯 qty 변경 없이 before/after 예상치만 출력된다. -7. Given live 계좌에서 `--yes` 없이 sync 실행 시, When 명령이 실행되면, Then 오류 메시지와 함께 실행을 거부한다(live double-ack 게이트). -8. Given sync 실행 후 슬롯 qty가 여전히 브로커 수량과 다를 경우(갱신 실패 등), When sync가 완료되면, Then reconcile-warning이 해제되지 않는다. -9. Given `--all` 플래그로 sync 실행 시, When 명령이 실행되면, Then 인스턴스의 모든 B-warned 슬롯에 대해 sync가 실행된다. -10. Given INACTIVE→HOLDING 전이 시, When transition_log 또는 로그를 확인하면, Then actor 및 reason 필드에 sync/onboarding(또는 reconcile_sync)이 기록되어 일반 전략 진입 경로(INACTIVE→PENDING_BUY)와 구분된다(OQ-A actor 식별). - -## Impacted Area -- Docs: `docs/operations.md` (cold-start onboarding 절차 신설), `docs/runtime-flows.md` (sync 흐름 추가) -- Modules: - - `trex_core/runtime/cli.py` (`sync` 서브커맨드 추가, `_build_parser`, `_cmd_sync`) - - `trex_core/application/reconciliation.py` (sync 로직 또는 별도 `sync_slot_to_broker` 함수 — batch design에서 결정) - - `trex_core/application/ports.py` 또는 `application/portfolio.py` (WAC read 포트 의존 명시) - - `trex_core/adapters/portfolio/` (WAC read 어댑터 — portfolio-hub FR-7 REQ 선행 의존) -- Tests: - - `tests/application/test_sync.py` 또는 `tests/integration/test_sync.py` (cold-start onboarding, warning 해제, unmanaged 슬롯 처리, dry-run, WAC seed 유무) - - `tests/runtime/test_cli.py` (sync 서브커맨드 인수 파싱, 확인 프롬프트, live double-ack) - -## Notes -- **FR-7 의존 구체화(OQ-A 팀장 결정)**: portfolio-hub WAC read 엔드포인트는 **portfolio-hub dcy-008 / REQ-043**, `GET /v1/portfolios/{id}/holdings/cost-basis`. 2026-06-25 confirm-discovery 완료. 가용 시 avg_price seed, 미가용/실패 시 수량만 갱신 + WARN(graceful degrade). 구현 시 portfolio-hub REQ-043 완료 여부 확인 필요. -- **INACTIVE→HOLDING 전이 엣지 추가(OQ-A 팀장 승인)**: `state.py:ALLOWED_TRANSITIONS`에 `(INACTIVE, HOLDING)` 엣지 추가가 승인되었다. 이 전이는 **actor=sync 또는 reconcile로 제한**되며 일반 전략 경로(INACTIVE→PENDING_BUY→HOLDING)는 유지된다. transition_log reason은 `onboarding` 또는 `reconcile_sync`류로 식별. **wire 무영향**: SlotState enum 5개(inactive/pending_buy/holding/pending_sell/error) 불변, 엣지만 추가이므로 slots RPC 스키마·상태값 변화 없음. batch design에서 `_BASE_TRANSITIONS` 확장 또는 별도 `_SYNC_TRANSITIONS` 집합 구성을 결정한다. -- sync 대상 슬롯 결정 방법(portfolio_id 명시 vs all warned 슬롯 vs all INACTIVE 슬롯)은 batch design에서 구체화. -- dcy-034 FR-3, OQ-2/3, OQ-A(팀장 2026-06-25 confirm), R-1 대응. - -## Change Log - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-051(PR #32, 07b393d) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. sync CLI(--portfolio-id/--all/--dry-run/--live), INACTIVE→HOLDING 전이, WAC seed, warning 해제, 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-051 (core-B) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: M3 배포 시 cold-start 운영 검증. - -#### Validation Plan -- BAT-051 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-105 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). -- OQ-A 결정 반영: INACTIVE→HOLDING 전이 엣지 추가 승인(actor=sync/reconcile 제한, wire 무영향 명시). -- FR-7 의존 구체화: portfolio-hub dcy-008 / REQ-043 (`GET /v1/portfolios/{id}/holdings/cost-basis`) 로 참조 갱신. -- AC-10 신규 추가(전이 actor 식별). - -#### Intent -- 팀장 confirm-req 결정(OQ-A 승인 + FR-7 참조 구체화)을 REQ에 반영. - -#### Acceptance Criteria Delta -- Added: AC-10 신규(INACTIVE→HOLDING 전이 actor/reason 식별). -- Updated: AC-1(OQ-A 전이 명시), AC-4(FR-7 참조 구체화), AC-5(graceful degrade 명시). -- Removed: 없음. - -#### Impacted Area Delta -- Notes: FR-7 참조 및 전이 엣지 추가 내용 구체화. -- Modules: `trex_core/domain/slot/state.py` — `ALLOWED_TRANSITIONS`에 `(INACTIVE, HOLDING)` 엣지 추가 필요(batch implementation 단계). -- Tests: AC-10 actor 식별 테스트 신규. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch design에서 state.py 전이 집합 확장 방식 결정 및 actor 제한 구현 방안 확정. - -#### Validation Plan -- cold-start onboarding 시나리오 테스트 pass / INACTIVE→HOLDING 전이 actor 식별 테스트 pass / warning 해제 확인 / unmanaged 슬롯 자동 생성 없음 테스트 pass / FR-7 graceful degrade 테스트 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-105 신규 생성 (Proposed) — dcy-034 FR-3 대응, draft-req 단계. - -#### Intent -- cold-start 시 운영자가 안전하게 슬롯을 브로커에 맞춰 onboarding하는 표준 CLI 경로 신설. - -#### Acceptance Criteria Delta -- Added: AC-1~9 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: operations.md 신설, runtime-flows.md 갱신. -- Modules: cli.py, reconciliation.py. -- Tests: sync 통합 테스트 신규. - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: INACTIVE→HOLDING 전이표 추가 여부 팀장 확인 후 batch design 진행. - -#### Validation Plan -- cold-start onboarding 시나리오 테스트 pass / warning 해제 확인 / unmanaged 슬롯 자동 생성 없음 테스트 pass. diff --git a/docs/srs/Interface/req-106_verify-cli-command.md b/docs/srs/Interface/req-106_verify-cli-command.md deleted file mode 100644 index acb9d48..0000000 --- a/docs/srs/Interface/req-106_verify-cli-command.md +++ /dev/null @@ -1,167 +0,0 @@ -# req-106: trex-core verify 명령 — C 불일치 슬롯 재대조 + warning 해제 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev - -## Intent -- 운영자가 portfolio-hub CLI로 원장을 정정한 뒤 `trex-core verify [--portfolio-id ]` 명령으로 **C 불일치(원장↔슬롯) 차단 슬롯을 재대조**하여, 일치 시 warning을 해제하고 불일치 시 사유와 함께 해제를 거부한다. -- dcy-034 FR-4, OQ-2/5, **OQ-C 확정** 반영: read-중심 명령(슬롯 수량 변경 없음), 강제 해제(force-clear) 없음. -- 재대조 로직은 현행 `reconcile_positions`(브로커 경유 2-대조)를 재사용한다(OQ-5). -- **verify는 C 케이스(원장↔슬롯 수량 불일치) 전용**이다. B 케이스(슬롯↔브로커 수량 불일치)는 sync 명령(REQ-105)이 해소 경로이며 verify 범위에 포함되지 않는다(OQ-C 확정). - -## Requirement -- 시스템은 `trex-core verify [--portfolio-id ]` 서브커맨드를 제공해야 한다. -- verify는 대상 슬롯(portfolio_id 기반)에 대해 `reconcile_positions`를 다시 실행해(브로커=정본, 원장↔브로커 대조) 일치 여부를 판정해야 한다. -- 대조 결과 일치(원장 수량 == 브로커 수량)이면 `ReconcileWarningStore.clear_warned(slot_id)` 로 reconcile-warning을 해제하고 "해제됨" 메시지를 출력해야 한다. -- 대조 결과 불일치이면 warning을 해제하지 않고 불일치 사유(원장 수량, 브로커 수량, 차이)를 출력하고 비-0 종료코드를 반환해야 한다(강제 해제 경로 없음). -- verify는 슬롯 qty를 변경하지 않는다(read-중심 — 슬롯 수량 변경 없음). -- verify는 C 케이스(case_code='C')로 warned된 슬롯만을 대상으로 한다. **B 케이스(슬롯↔브로커 수량 불일치)에 대한 해소는 sync 명령(REQ-105) 책임이며, verify는 B 케이스에 적용하지 않는다(OQ-C 확정, C 전용).** - -## Acceptance Criteria -1. Given C 불일치로 차단된 슬롯에서 운영자가 portfolio-hub CLI로 원장을 정정하고 `trex-core verify --portfolio-id ` 실행 시, When 원장 수량 == 브로커 수량이면, Then reconcile-warning이 해제되고 "해제됨" 메시지가 출력된다(S-3 1차). -2. Given C 불일치로 차단된 슬롯에서 `trex-core verify` 실행 시, When 원장 수량 ≠ 브로커 수량이면, Then reconcile-warning이 해제되지 않고 불일치 사유(원장qty, 브로커qty, 차이)가 출력되며 비-0 종료코드를 반환한다(S-3 2차, 강제 해제 없음). -3. Given verify 실행 시, When 어떤 경우에도, Then 슬롯 qty가 변경되지 않는다(read-중심 불변식). -4. Given reconcile-warning이 없는 슬롯에 대해 verify 실행 시, When 명령이 실행되면, Then 이미 해제 상태임을 알리는 메시지 출력 후 0 종료코드로 반환한다(idempotent). -5. Given `--json` 플래그로 verify 실행 시, When 명령이 실행되면, Then `{portfolio_id, slot_id, result: "cleared"|"rejected", hub_qty, broker_qty, reason?}` JSON이 stdout에 출력된다. -6. Given `trex-core verify` (--portfolio-id 없이) 실행 시, When 명령이 실행되면, Then 인스턴스의 모든 C-warned 슬롯에 대해 재대조를 수행하고 결과를 슬롯별로 출력한다. - -## Impacted Area -- Docs: `docs/operations.md` (원장 정정 후 verify 절차 추가), `docs/runtime-flows.md` (verify 흐름 추가) -- Modules: - - `trex_core/runtime/cli.py` (`verify` 서브커맨드 추가) - - `trex_core/application/reconciliation.py` (`reconcile_positions` 재사용 또는 호출 래퍼 — batch design에서 결정) -- Tests: - - `tests/runtime/test_cli.py` (verify 서브커맨드 인수 파싱) - - `tests/application/test_verify.py` 또는 `tests/integration/test_verify.py` (일치→해제, 불일치→거부, read-중심 불변식, idempotent) - -## Notes -- OQ-5 확정: verify의 재대조는 `reconcile_positions`(원장↔브로커 2-대조, 브로커=정본)를 재사용한다. 별도 대조 로직을 만들지 않는다. -- verify는 read-중심이므로 pause/resume의 live double-ack 게이트는 불필요하다. --yes, --live 옵션이 필요 없다. -- **OQ-C 확정: verify는 C 전용**. B 케이스(슬롯↔브로커 수량 불일치)는 sync(REQ-105)로 해소한다. verify가 B-warned 슬롯 ID로 호출되면 "C 케이스 대상 아님" 안내 후 0 종료코드를 반환한다(batch design에서 메시지 확정). -- dcy-034 FR-4, OQ-2/5, OQ-C(팀장 2026-06-25 confirm), S-3 대응. - -## Change Log - -### CHG-20260630-01 -- Date: 2026-06-30 -- Author: 팀장(Claude) — change-req (라이브 릴리즈 핫픽스 + postmortem) - -#### Change Summary -- **REL-010 STEP2 배포 스모크서 `trex-core verify` ImportError 핫픽스.** Status `Implemented` 유지(구현 결함 수정, AC 불변). - -#### Intent / 증상·근본원인 (핫픽스 예외 경로) -- **증상**: 운영 배포(`ababf80`) 후 `trex-core verify --config ... --portfolio-id ...` 실행 시 `ImportError: cannot import name 'make_broker' from 'trex_core.adapters.broker'`. -- **근본원인**: `runtime/cli.py` `_cmd_verify()`가 **코드베이스에 존재하지 않는** `trex_core.adapters.broker.make_broker`를 import(정의 0건, 참조는 verify 한 곳뿐). 형제 명령 `_cmd_sync()`는 올바르게 `trex_core.runtime.daemon._build_broker`를 사용. → verify 경로에만 잘못된 import가 박혀 있었음. -- **수정**: `_cmd_verify`를 `from trex_core.runtime.daemon import _build_broker` / `_build_broker(cfg)`로 교체(sync와 동일, 검증된 경로). 커밋 `51cc3cc`. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 (기존 AC 동일 — 깨진 구현을 정정) - -#### Delivery Trace Delta -- 핫픽스 커밋 `51cc3cc`(main 직접, fast-path). 회귀 테스트 2종 추가(`tests/runtime/test_cli_operator.py`): `test_verify_cmd_broker_import_does_not_raise`(paper config+DB stub로 verify 경로 import 통과 검증), `test_verify_cmd_uses_build_broker_not_make_broker`(make_broker 심볼 부재 구조검증). - -#### Verification -- 전체 스위트 747 passed / 47 skipped / 0 failed(회귀 0). **운영 재검증(2026-06-30, `51cc3cc`)**: verify OK(slot already cleared) · sync --dry-run OK(broker_qty_zero guard SKIP) · report OK. - -#### Postmortem (테스트 갭) -- **왜 미검출**: BAT-051 verification이 verify의 **broker import 경로를 런타임 실행하지 않음**(import가 함수 내부라 호출 시에만 트리거). 단위 테스트가 verify 명령을 끝까지 돌리지 않아 phantom import가 통과됨. -- **재발 방지**: 위 회귀 테스트 2종으로 import 경로를 실제 실행/구조검증. 후속 권고 — operator CLI 명령들의 lazy import를 스모크 수준으로 한 번씩 실행하는 테스트 추가 검토. - -#### Revalidation Impact -- Reverification Needed: no (회귀테스트+운영 재검증 완료) / Existing Implementation Invalidated: no -- Status Recommendation: implemented -- Rollback: `git revert 51cc3cc` (CLI 전용, trading/wire/daemon 무관 — 실행 중 데몬 무영향). -- ⚠️ 거버넌스 노트: 라이브 블로커 해소를 위해 **main 직접 푸시**(사전 결재 게이트 절차 생략) — 사장님 사후 추인. - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-051(PR #32, 07b393d) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. verify CLI(--portfolio-id/--json), C케이스 재대조+warning 해제, B케이스 거부+sync 안내, 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-051 (core-B) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 없음. - -#### Validation Plan -- BAT-051 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-106 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). -- OQ-C 결정 반영: verify = C 케이스(원장↔슬롯) 전용 확정. B 케이스는 sync(REQ-105) 책임. Intent, Requirement, Notes에 "C 전용, B는 sync 책임" 명시. - -#### Intent -- 팀장 confirm-req 결정(OQ-C 확정: verify는 C 전용)을 REQ에 반영. - -#### Acceptance Criteria Delta -- Added: 없음. -- Updated: 없음(기존 AC는 이미 C 케이스 중심으로 작성됨). -- Removed: 없음. - -#### Impacted Area Delta -- Notes: OQ-C 확정 및 B-warned 슬롯 호출 시 처리 방침 추가. -- Modules: cli.py verify 서브커맨드 — B-warned 슬롯 필터링 로직 추가 필요. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch design에서 B-warned 슬롯 필터링 메시지 확정. - -#### Validation Plan -- 일치→해제, 불일치→거부 시나리오(C 케이스) 테스트 pass / B-warned 슬롯에 verify 적용 시 "대상 아님" 처리 테스트 pass / 슬롯 qty 불변 검증 / 기존 reconcile_positions 회귀 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-106 신규 생성 (Proposed) — dcy-034 FR-4 대응, draft-req 단계. - -#### Intent -- C 불일치 차단 해소를 위한 운영자 read-중심 재대조 경로 신설. 강제 해제 없이 재대조 일치 시에만 차단 해제. - -#### Acceptance Criteria Delta -- Added: AC-1~6 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: operations.md, runtime-flows.md 갱신. -- Modules: cli.py, reconciliation.py. -- Tests: verify 시나리오 테스트 신규. - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch design. - -#### Validation Plan -- 일치→해제, 불일치→거부 시나리오 테스트 pass / 슬롯 qty 불변 검증 / 기존 reconcile_positions 회귀 pass. diff --git a/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md b/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md deleted file mode 100644 index 7aeb71a..0000000 --- a/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md +++ /dev/null @@ -1,150 +0,0 @@ -# req-110: 운영 리포트 WAC 실현손익 통합 — 전 인스턴스 공통 - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev -- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req) - -## Intent -- 현재 `operator_report.py`(REQ-088)의 `realized_pnl(trades)`은 당일 net 현금흐름(`Σ매도대금 − Σ매수대금 − 수수료 − 세금`)을 사용한다. 중장기 인스턴스에서 전일 매수→당일 매도 시 cost basis가 반영되지 않아 실현손익이 왜곡된다. portfolio-hub WAC API(`/pnl/realized`)를 소비해 전 인스턴스 공통으로 정확한 실현손익을 산출하고 리포트에 표시한다. (dcy-035 FR-2, FR-3, FR-4, OQ-1=전 인스턴스 WAC 통일, NFR-2) - -## Requirement -- `operator_report.py`의 `OperatorReport`에 WAC 실현손익 관련 필드를 추가한다: `realized_pnl_wac: float`, `realized_pnl_wac_by_asset: dict[str, float]`, `data_completeness_warning: bool`. (CHG-20260626-02: `buy_fee_only_assets` 필드 제거 — 아래 매수 fee 편입 결정 반영.) -- 운영 리포트(`trex-core report` CLI)는 당일 net 대신 portfolio-hub WAC 실현손익(`/pnl/realized`)을 **전 인스턴스 공통**으로 사용해 실현손익을 표시한다. -- 매도가 있는 자산: WAC 기준 실현손익(`(매도가 − WAC) × qty − fee − tax`)을 자산별로 분해해 출력한다. portfolio-hub WAC는 **매수 수수료를 cost basis에 편입**(REQ-041 CHG-20260626-01)하므로 이 실현손익은 매수·매도 fee를 모두 정확히 반영한다. -- 매수만 한 기간(해당 자산의 매도 0): 실현손익 0으로 표시한다. **매수 수수료는 cost basis(WAC)에 편입되어 매도 시 실현**되므로, 별도 "수수료 비용" 라인은 두지 않는다(CHG-20260626-02 — 별도 라인은 매도 시 실현분과 이중계상되어 제거). -- portfolio-hub 응답의 `data_completeness_warning: true` 시 리포트 출력에 경고를 표면화한다(텍스트/JSON 모두 포함). -- `render_text` / `render_json`을 WAC 모드에 맞게 갱신한다. -- CLI `report` 서브커맨드에서 portfolio-hub 설정(`cfg.portfolio_hub.base_url`)이 있을 때 WAC 경로를 사용하고, 미설정이면 REQ-111 degrade 경로로 위임한다. - -## Acceptance Criteria -1. Given 전일 매수 + 당일 매도 시나리오(portfolio-hub `/pnl/realized` 모킹 응답), When `report` 실행, Then 실현손익이 WAC 기준 정확값(`(매도가 − WAC) × qty − fee − tax`)으로 표시되고 당일 net 왜곡값과 다르다. -2. Given 동일 날 **닫힌** 매수+매도(일중, buy_qty==sell_qty) 시나리오, When `report` 실행, Then WAC 실현손익이 기존 당일 net과 **정확히 일치**한다(`==`, NFR-2 회귀 보장). (CHG-20260626-02: 매수 fee 편입(REQ-041)으로 일중 닫힌 거래는 WAC==net이 엄격 성립 — `≈`가 아니라 `==`로 단언.) -3. Given 매수만 한 기간(매도 0), When `report` 실행, Then 실현손익 0으로 표시되고 별도 수수료 비용 라인은 없다(매수 fee는 cost basis 편입, 매도 시 실현). (CHG-20260626-02: 기존 `buy_fee_only_assets` 별도 라인 제거.) -4. Given portfolio-hub 응답에 `data_completeness_warning: true`, When `report` 실행, Then 텍스트 출력과 `--json` 출력 모두에 completeness 경고가 표시된다. -5. Given 자산별 실현손익 분해, When `report` 실행, Then `realized_pnl_wac_by_asset`에 자산(symbol)별 WAC 실현손익이 분해된다. -6. Given `--json` 플래그, When `report` 실행, Then `render_json` 출력에 `realized_pnl_wac`, `realized_pnl_wac_by_asset`, `data_completeness_warning` 필드가 포함된다. (CHG-20260626-02: `buy_fee_only_assets` 제거.) -7. Given portfolio-hub `base_url` 미설정(cfg.portfolio_hub None 또는 base_url 미정의), When `report` 실행, Then REQ-111 degrade 경로로 처리된다(에러 표시). - -## Impacted Area -- Docs: - - `docs/operations.md` — 리포트 실현손익 정의 갱신(당일 net → WAC 기준, 매수일 수수료 표시 명시) - - `docs/runtime-flows.md` — operator report 흐름에 portfolio-hub WAC 호출 경로 추가 -- Modules: - - `trex_core/application/operator_report.py` — `OperatorReport` 필드 확장, 기존 `realized_pnl(trades)` 함수는 legacy/테스트 목적 유지(NFR-2 회귀 비교용), WAC 모드 신규 함수/빌더, `render_text`/`render_json` 갱신 - - `trex_core/runtime/cli.py` — `_cmd_report` 내 portfolio-hub WAC 호출 분기, `from_dt`/`to_dt` 파라미터 전달 (OQ-2 deferred: 기본값=오늘 KST 경계로 구현) -- Tests: - - 신규: 멀티데이 픽스처(전일 매수+당일 매도) 통합 테스트, 매수만 기간 테스트, data_completeness_warning 테스트, NFR-2 일중 회귀 단위 테스트 - - 기존 `test_operator_report.py` 회귀 (당일 net 함수 동작 보존) - -## Notes -- OQ-2(기간 모델): from_dt/to_dt는 오늘(KST 경계) 기본값으로 구현. 기간 범위 확장(지정 기간)은 design/팀장 결정 대기 → 기본 구현은 "당일 KST 매도 기준" 유지, `--from`/`--to` CLI 옵션 추가는 OQ-2 확정 후 별도 change-req 또는 design 인계. -- OQ-3(수수료 출처): 매수만 한 날의 수수료는 `ledger_outbox` 당일 거래에서 집계(기존 경로 재사용). portfolio-hub 거래내역(REQ-039) 사용 여부는 design 인계. -- OQ-4(N콜 vs 집계): 슬롯↔portfolio 1:1이므로 portfolio_id별 N콜이 기본 구현. 집계 최적화는 design 인계. -- 기존 `realized_pnl(trades)` 함수는 삭제하지 않고 유지한다(NFR-2 회귀 비교, 기존 테스트 보존). -- NFR-1(wire 계약 불변): report는 read-only 운영 표면 — slots RPC/AMQP 계약 무관. REQ 명시 불필요. -- NFR-3(데몬 본업 격리): `_cmd_report`는 CLI 직접 실행 경로이며 데몬 루프와 무관 — 격리는 구조적으로 보장됨. - -## Change Log - -### CHG-20260626-03 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- BAT-052 구현·검증 완료(PR #33, 머지 커밋 81d06d9). confirm-batch-verification + confirm-req-implemented. Approved → Implemented. - -#### Intent -- dcy-035 FR-2/FR-3/FR-4 delivery 완료 확인 및 상태 전환. - -#### Acceptance Criteria Delta -- Updated: - - none (모든 AC-1~7 검증 완료, 상태 전환만) -- Added: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: 없음 -- Modules: 변경 없음 (구현 완료 상태 유지) -- Tests: 717 passed / 47 skipped / 0 fail (BAT-052 verification) - -#### Delivery Trace Delta -- Batches: - - BAT-052 (verified, PR #33, 머지 커밋 81d06d9, 2026-06-26) -- Releases: - - none (M3 배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: none - -### CHG-20260626-02 -- Date: 2026-06-26 -- Author: 팀장 (Claude) - -#### Change Summary -- 사장님 결정(2026-06-26): portfolio-hub WAC에 매수 수수료 편입(REQ-041 CHG-20260626-01). 그 결과 (1) 일중 닫힌 거래에서 WAC 실현손익 == 당일 net이 **엄격히** 성립 → AC-2를 `≈`에서 `==`로 강화. (2) 매수 fee가 cost basis에 편입되므로 매수전용일의 별도 "수수료 비용" 라인(`buy_fee_only_assets`)은 매도 시 실현분과 **이중계상** → 제거. - -#### Intent -- 매수 fee 편입 결정과 정합: 단일 정확 실현손익 숫자, NFR-2 엄격 성립, 이중계상 제거. - -#### Acceptance Criteria Delta -- Updated: - - AC-2: 일중 닫힌 거래 WAC==net **엄격 일치**(`==`)로 강화. - - AC-3: 매수전용일 별도 수수료 비용 라인 제거(매수 fee는 cost basis 편입·매도 시 실현). - - AC-6: `--json` 필드에서 `buy_fee_only_assets` 제거. -- Removed: - - `OperatorReport.buy_fee_only_assets` 필드 및 관련 산출/렌더. - -#### Impacted Area Delta -- Modules: operator_report.py(buy_fee_only_assets 제거, NFR-2 테스트 강화), cli.py(해당 표시 제거) -- Tests: NFR-2 일중 회귀 테스트를 `==` 단언으로 강화, buy_fee_only_assets 테스트 제거/대체 - -#### Revalidation Impact -- Reverification Needed: yes (BAT-052 보정 후 재검증) -- Existing Implementation Invalidated: partial (BAT-052 구현 중 buy_fee_only_assets 부분) -- Status Recommendation: keep-current (Approved) -- Follow-up Action: BAT-052 보정 → 재QC → confirm-batch-verification - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev - -#### Change Summary -- dcy-035 FR-2/FR-3/FR-4, OQ-1(전 인스턴스 WAC 통일), NFR-2(일중 회귀)로부터 최초 생성(Proposed). - -#### Intent -- 중장기 인스턴스 실현손익 왜곡 해소 + 단기 회귀 보장 + 매수일 수수료 표시 + completeness 경고 표면화. - -#### Acceptance Criteria Delta -- Added: - - AC-1~7 신규 -- Updated: - - none -- Removed: - - none - -#### Impacted Area Delta -- Docs: - - docs/operations.md, docs/runtime-flows.md -- Modules: - - operator_report.py 확장, cli.py _cmd_report 분기 -- Tests: - - 신규 멀티데이/매수전용/completeness/NFR-2 테스트, 기존 회귀 - -#### Delivery Trace Delta -- Batches: - - none (미배치) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/Interface/req-116_slots-req-strategy-field-and-filter.md b/docs/srs/Interface/req-116_slots-req-strategy-field-and-filter.md deleted file mode 100644 index f43ca57..0000000 --- a/docs/srs/Interface/req-116_slots-req-strategy-field-and-filter.md +++ /dev/null @@ -1,144 +0,0 @@ -# req-116: `trex.slots.req` 전략 필드 노출 + `strategy` 요청 필터 (additive) - -- Status: Implemented -- Type: Interface -- Priority: High -- Owner: trex-core-dev - -## Intent - -- 신규 전략 데몬 trex-tvr-v1(TVR — Target-Value Rebalancing)이 `trex.slots.req` 응답 행에서 `strategy_name`·`strategy_params`·`markets` 를 읽어 자기 슬롯을 식별하고 리밸런싱 목표 비중(`target_weight` 등)을 파라미터로 수신해야 한다. 또한 `strategy="tvr_v1"` 요청 필터로 자기 전략 슬롯만 수신하려 한다. -- REQ-074 AC-5 는 dcy-025 OQ-4 당시 결정을 유보한 것이었고, 본 REQ 는 그 additive 재도입(dcy-025 OQ-4 후속)이다. **기존 소비자(orb·tsl)에게 byte-동일 기본 동작을 보장**하면서 필드를 추가한다. -- 근거: trex-tvr-v1 Discovery(dcy-001/dcy-002 v3 FR-8, confirmed) — portfolio-hub 측 `strategy_name`/`markets`/`strategy_params` 노출 REQ(①)가 선행 배포된 뒤 본 REQ(②)를 배포한다. - -## Requirement - -### 행 필드 추가 (additive — 기존 필드 불변) - -- `slot_to_dict()`(application/portfolio.py)가 `SnapshotStore` 의 `PortfolioItem` 에서 아래 세 필드를 읽어 슬롯 행에 추가한다: - - `strategy_name: str | null` — portfolio-hub `strategy_name`. PortfolioItem 에 없거나 빈 값이면 `null`. - - `strategy_params: dict` — portfolio-hub `strategy_params` opaque dict. PortfolioItem 에 없으면 `{}`. - - `markets: list[str]` — portfolio-hub `markets` 배열. PortfolioItem 에 없으면 `[]`. -- 이 세 필드의 정본 출처는 portfolio-hub(SnapshotStore 경유)이다. hub 응답에 없으면 safe default(null/`{}`/`[]`)를 사용하고 데몬을 중단하지 않는다. -- hub_read.py(`get_portfolios`) 가 portfolio-hub 응답에서 `strategy_name` 과 `markets` 를 파싱해 `PortfolioItem` 에 담는다. 구버전 hub 응답(미존재 키)에서는 각각 `None`/`[]` 로 safe-default 한다. -- `PortfolioItem` 도메인 객체에 `strategy_name: Optional[str]` 과 `markets: list[str]` 필드를 추가한다. - -### 요청 `strategy` 필터 추가 (additive — 기존 필터 불변) - -- `trex.slots.req` 요청 payload 에 선택적 파라미터 `strategy: str` 을 추가한다. -- 지정 시: `strategy_name == strategy` 인 슬롯 행만 반환한다. 대소문자 exact-match. -- 미지정 시: 기존과 완전 동일(전체 반환). -- `strategy` 필터는 기존 `filter`(status 필터) 와 **AND 조합**된다. 두 필터를 동시에 지정하면 status 조건과 strategy 조건 모두 만족하는 행만 반환한다. -- `available_slots` 힌트는 `strategy` 필터 적용 여부와 **무관하게 인스턴스 전체 활성 슬롯 기준**으로 계산한다(capacity 의미 불변 — REQ-073 AC-6). - -### interface-contract.md 갱신 - -- `trex.slots.req` 요청 payload 에 `strategy: str (optional)` 파라미터를 추가한다. -- 응답 슬롯 행 스키마에 `strategy_name`·`strategy_params`·`markets` 필드와 정본 소스 테이블을 추가한다. -- REQ-074 AC-5 주석("strategy 필드 없음")을 본 REQ 에 의한 additive 재도입으로 갱신한다. - -## Acceptance Criteria - -1. **행 필드 노출**: Given portfolio-hub 가 `strategy_name="tvr_v1"`, `strategy_params={"target_weight": 0.05}`, `markets=["KRX"]` 를 반환하는 환경에서, When `trex.slots.req` 조회, Then 해당 슬롯 행에 `strategy_name="tvr_v1"`, `strategy_params={"target_weight": 0.05}`, `markets=["KRX"]` 가 포함된다. - -2. **hub 미제공 safe-default**: Given portfolio-hub 응답에 `strategy_name`·`markets` 키가 없는 구버전 응답 환경에서, When `trex.slots.req` 조회, Then 행의 `strategy_name=null`, `strategy_params={}`, `markets=[]` 이고 데몬이 중단되지 않는다. - -3. **strategy 필터 — 매칭**: Given `strategy_name="tvr_v1"` 인 슬롯과 `strategy_name="orb_v1"` 인 슬롯이 공존하는 인스턴스에서, When `{strategy:"tvr_v1"}` 요청, Then `strategy_name="tvr_v1"` 인 슬롯 행만 반환된다. - -4. **strategy 필터 — 미지정**: Given 동일 인스턴스에서, When `strategy` 없이 요청(기존 포맷), Then 모든 슬롯 행이 반환되고 응답 내용이 `strategy` 도입 전과 동일하다(기존 필드 불변 — 신규 필드만 추가). - -5. **AND 조합**: Given `strategy_name="tvr_v1"` 인 holding 슬롯과 `strategy_name="tvr_v1"` 인 inactive 슬롯이 있는 환경에서, When `{filter:"holding", strategy:"tvr_v1"}` 요청, Then `strategy_name="tvr_v1"` + status `holding` 인 슬롯만 반환된다. - -6. **available_slots 불변**: Given `max_active_slots=10`, 현재 active 3개인 인스턴스에서, When `{strategy:"tvr_v1"}` 요청(tvr_v1 슬롯이 1개뿐이라도), Then `available_slots=7`(인스턴스 전체 기준). strategy 필터가 available_slots 계산에 영향을 미치지 않는다. - -7. **orb·tsl 회귀 없음**: Given 기존 orb-v1 또는 tsl-v1 소비자가 `strategy` 파라미터 없이 기존 요청 포맷을 전송하는 경우, When 처리, Then 응답 키 집합 및 기존 필드 값이 변경 전과 동일하다(신규 `strategy_name`·`strategy_params`·`markets` 필드 추가는 backward compatible — unknown 필드 무시 계약). - -8. **계약 테스트 갱신**: Given 슬롯 계약 테스트(`EXPECTED_SLOT_FIELDS` 등)가 존재하는 경우, When 본 REQ 구현, Then 계약 테스트가 신규 필드를 포함하도록 갱신되고 통과한다. - -## Impacted Area - -- Docs: - - `docs/interface-contract.md` — 요청 파라미터(`strategy` 추가) + 행 스키마(`strategy_name`·`strategy_params`·`markets` 추가) + 정본 소스 테이블 + REQ-074 AC-5 주석 갱신 -- Modules: - - `trex_core/application/portfolio.py` — `PortfolioItem` 에 `strategy_name: Optional[str]`·`markets: list[str]` 필드 추가; `slot_to_dict()` 에서 SnapshotStore 의 `PortfolioItem` strategy 필드 읽기 - - `trex_core/adapters/portfolio/hub_read.py` — `get_portfolios()` 에서 `strategy_name`·`markets` 파싱 추가 - - `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler()` 에서 `strategy` 파라미터 파싱 + strategy 필터 적용(행 후처리 or slot_to_dict 이후 Python 필터) -- Tests: - - `tests/application/test_slots_handler.py` (또는 신규) — strategy 필터 AC-3/4/5, available_slots 불변 AC-6, 회귀 AC-7 - - `tests/application/test_slot_to_dict.py` (또는 기존 행 스키마 테스트) — strategy 필드 노출 AC-1/2 - - 슬롯 계약 테스트(`EXPECTED_SLOT_FIELDS` 등) — AC-8 갱신 - -## Notes - -- **REQ-074 AC-5 와의 관계**: REQ-074 AC-5 는 dcy-025 OQ-4 당시 "전략↔자산 할당 모델 미확정" 사유로 strategy 필드 전체를 제외했다. 본 REQ 는 그 유보 결정을 additive 로 개정(OQ-4 후속 확정)하며, REQ-074 의 기존 AC(1~6) 는 불변이다. REQ-074 에 change-req 를 별도로 기록하지 않고 본 REQ Notes 에 관계를 명기하는 것으로 충분하다 — 이유: REQ-074 는 Implemented 상태이고 AC-5 의 "strategy 계열 제외" 불변식 자체를 파괴하지 않으며, 본 REQ 가 additive 재도입이라는 별도 REQ 근거를 남기는 것이 추적성 측면에서 더 명확하다. 단, 구현 batch 에서 REQ-074 CHG 항목으로도 참조를 남긴다. -- **라이브 wire 계약 변경 — 고위험**: orb-v1·tsl-v1 이 현재 `trex.slots.req` 를 소비 중이다. 신규 필드는 additive(unknown 필드 무시)이므로 orb·tsl 파손 없음이 예상되나, 계약 테스트(AC-8)·회귀 테스트(AC-7) 필수. 구현은 worktree+PR+QC 정식 플로로 진행한다. -- **배포 선행 조건**: portfolio-hub 측 `strategy_name`/`markets`/`strategy_params` 노출 REQ(①)가 배포된 뒤 본 REQ(②)를 배포한다. 선행 배포 전에는 safe-default(AC-2) 로 동작한다. -- **`strategy` 필터 적용 위치**: `strategy_name` 은 SnapshotStore(in-memory) 에 있으므로 DB 레벨 필터가 아닌 Python 레벨 후처리 필터로 구현한다. status 필터(`list_slots(status_filter=...)`)가 먼저 DB 에서 실행되고, 그 결과에 strategy 필터를 Python 으로 적용한다. available_slots 는 필터 이전에 계산한다(AC-6). -- **`strategy` 값 오타/미존재**: `strategy` 로 지정한 값에 매칭되는 슬롯이 없으면 빈 `slots=[]` 를 반환한다(에러 없음 — REQ-073 AC-2 의 unrecognized filter 처리 선례). -- **`PortfolioItem.markets` 타입**: portfolio-hub 가 `markets: list[str]` 를 반환하지 않는 경우(None 또는 키 부재) `[]` 로 처리. `PortfolioItem.markets` 기본값 `field(default_factory=list)`. -- **Open Question**: portfolio-hub 가 `strategy_name` 필드를 `active_strategy_id` 와 별도로 반환하는지, 아니면 `active_strategy_id` 를 `strategy_name` 의미로 사용하는지 portfolio-hub REQ(①) 확정 후 확인 필요. 구현 전에 portfolio-hub 응답 스키마를 검증한다. (현재 hub_read.py 는 `active_strategy_id` 를 파싱하지만 `strategy_name` 은 별도 필드일 수 있다.) - -## Change Log - -### CHG-20260704-03 -- Date: 2026-07-04 -- Author: 팀장(Claude) — confirm-req-implemented - -#### Change Summary -- Approved → **Implemented**: bat-055 구현 완료. 독립 QC **opus 승격** PASS-WITH-NOTES(blocker 0) — 777 passed 일치, AC 8/8, ★watchdog 굶김 없음 실증 프로브 확인(필터는 응답에만 적용), 미지정 요청 byte-동일 검증. PR #37 사장님 결재·머지(dc6d7a9) = confirm-batch-verification 완료. -- **배포 게이트**: portfolio-hub REQ-055(PR #20) 선행 배포 후 본 변경 배포(선행 전엔 safe-default 무해). - -#### Delivery Trace Delta -- Batches: bat-055_20260704_slots-strategy-field-filter (merged, PR #37) - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Implemented - -### CHG-20260704-02 -- Date: 2026-07-04 -- Author: 팀장(Claude) — confirm-req - -#### Change Summary -- Proposed → Approved: 팀장 검토·승인. tvr e2e 배관 ②(흘려주기). Notes의 오픈질문(strategy_name 별도 필드 여부)은 portfolio-hub REQ-055(Approved)가 `active_strategy_id`와 **별도 필드 `strategy_name`으로 확정** — 필드명 4종(active_strategy_id/strategy_name/strategy_params/markets) 상호 잠금. 라이브 wire 계약 변경이므로 구현은 worktree+PR+QC 정식 플로, 배포는 REQ-055 후. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Revalidation Impact -- Reverification Needed: no / Existing Implementation Invalidated: no -- Status Recommendation: Approved - -### CHG-20260704-01 -- Date: 2026-07-04 -- Author: trex-core-dev - -#### Change Summary -- trex-tvr-v1 Discovery(dcy-001/dcy-002 v3 FR-8, confirmed)로부터 최초 생성(Proposed). `trex.slots.req` additive 확장 — 행 `strategy_name`·`strategy_params`·`markets` 필드 추가 + 요청 `strategy` 필터 추가. REQ-074 AC-5 additive 재도입(dcy-025 OQ-4 후속). - -#### Intent -- trex-tvr-v1 이 `strategy="tvr_v1"` 필터로 자기 슬롯만 수신하고 행의 `strategy_params.target_weight` 로 리밸런싱할 수 있도록 wire 계약을 확장. - -#### Acceptance Criteria Delta -- Added: - - AC-1~8 신규 -- Updated: none -- Removed: none - -#### Impacted Area Delta -- Docs: docs/interface-contract.md (요청·응답 스키마 갱신) -- Modules: portfolio.py / hub_read.py / messaging_handlers.py -- Tests: slots 핸들러·행 스키마·계약 테스트 - -#### Delivery Trace Delta -- Batches: none (승인 후 draft-batch) -- Releases: none - -#### Revalidation Impact -- Reverification Needed: no (신규) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current (Proposed) -- Follow-up Action: confirm-req → suggest-batch-reqs → draft-batch - -#### Validation Plan -- 신규 batch: 행 필드 단언(AC-1/2), strategy 필터 단위(AC-3/4/5), available_slots 불변(AC-6), orb·tsl 회귀(AC-7), 계약 테스트 갱신(AC-8). diff --git a/docs/srs/Migration/req-064_pool-to-permanent-slot-cutover.md b/docs/srs/Migration/req-064_pool-to-permanent-slot-cutover.md deleted file mode 100644 index bd14b94..0000000 --- a/docs/srs/Migration/req-064_pool-to-permanent-slot-cutover.md +++ /dev/null @@ -1,126 +0,0 @@ -# req-064: 풀 모델 → 자산 중심 DB 재초기화 (구 cutover 마이그레이션) - -- Status: Implemented -- Type: Migration -- Priority: High -- Owner: project maintainers - -## Intent -- 익명 풀 모델(일일 daily_slot 행) 데이터베이스를 자산 중심 영구 슬롯 substrate 로 전환한다. **데이터 보존 cutover 는 폐기**(CHG-20260610-03): 마이그레이션 009 의 부분 유니크 인덱스가 legacy 풀 데이터(같은 자산이 closed+재진입 holding 으로 portfolio_id 중복)에 적용 불가한 결함 + dev/페이퍼 컨텍스트로, **DB wipe + 자산 중심 재초기화**로 전환한다. (dcy-020 NFR-2/R-1; 결함 출처는 실 DB 점검 2026-06-10) - -## Requirement -- `trex-core-reset` 도구로 slot-core 테이블(daily_slot/daily_symbol_entries/slot_transition_log/intent_log/ledger_outbox/snapshot_pause/killswitch_exclusion)을 **wipe** 한 뒤 마이그레이션을 재적용해 DB 가 깨끗한 자산 중심 스키마(009 부분 유니크 인덱스·010 inactive CHECK·011 컬럼 drop)에 착지하게 한다. 빈 테이블에는 인덱스가 정상 생성되고, 자산 중심에서 (instance_key, portfolio_id)는 universe reconcile 가 항상 채우므로 설계상 유일하다. 파괴적·DB 전역이며 dry-run 기본 + `--yes` 적용, 실행 전 백업을 전제로 한다. 보유 포지션 carry-over 는 하지 않으며, 재초기화 후 universe reconcile(REQ-058) + `reconcile_core_holdings`(REQ-062)로 운영자가 재편입한다. - -## Acceptance Criteria -1. Given 기존 풀 모델(또는 임의 상태) DB, When `trex-core-reset --yes`, Then slot-core 테이블이 wipe 되고 `daily_slot` 컬럼셋이 `EXPECTED_SLOT_FIELDS`(자산 중심)와 일치하며 부분 유니크 인덱스·inactive CHECK 가 존재한다. -2. Given 재초기화 후, When 데몬 기동, Then universe reconcile(REQ-058)·admission(REQ-059)·safety-sell 이 회귀 없이 동작한다. -3. Given 안전성, When dry-run 실행, Then 어떤 행도 변경되지 않고 wipe 대상 행수만 보고된다. 재초기화는 멱등이다(2회차도 0행·schema_ok). -4. Given 보유 포지션, When 재초기화(데이터 보존 폐기), Then 보유는 소실되며 재초기화 후 브로커 보유가 `reconcile_core_holdings`(REQ-062)의 unmanaged 경로로 표면화되어 운영자가 재편입한다. 롤백 = 사전 백업 복원. - -## Impacted Area -- Docs: docs/runtime-flows.md(재초기화 절차), 릴리즈 Rollback Plan(백업 복원) -- Modules: trex_core/runtime/reset.py, trex_core/adapters/persistence/sql/(009~011) -- Tests: tests/runtime/test_reset.py(PG-gated), 회귀 스위트 - -## Notes -- 라이브 데이터 영향 — 파괴적·DB 전역. 배포 게이트·운영자 입회·**사전 백업 필수**. 실행은 release(REL) 단계 운영자 작업으로 confirm-release 전 백업 evidence 필요. -- 구 cutover 도구(`trex-core-cutover`/`runtime/cutover.py`)는 폐기·삭제(CHG-20260610-03). -- 출처: dcy-020 NFR-2, R-1 + 마이그레이션 009 legacy 충돌 결함. - -## Change Log - -### CHG-20260610-01 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- dcy-020 NFR-2/R-1 로부터 최초 생성(Proposed). 일일행→영구행 cutover + 보유 carry-over + instance_key 태깅 + 롤백 계획. - -#### Intent -- 라이브 풀 모델 데이터를 무손실로 영구 슬롯 모델로 이행하는 cutover 를 backlog 정규화. - -#### Acceptance Criteria Delta -- Added: 보유 carry-over/기동 회귀/롤백/instance_key 태깅 4개 기준 -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/runtime-flows.md, 릴리즈 Rollback Plan -- Modules: persistence cutover 마이그레이션, 이행 스크립트 -- Tests: cutover round-trip, 회귀 스위트 - -#### Delivery Trace Delta -- Batches: none -- Releases: none - -#### Revalidation Impact -- Reverification Needed: yes (cutover 리허설/회귀 필수) -- Existing Implementation Invalidated: no (이행 절차 신규) -- Status Recommendation: keep-current -- Follow-up Action: confirm-req. 별도 배치(BAT-δ)·운영자 입회·confirm-release 전 리허설 evidence. - -#### Validation Plan -- cutover 리허설(보유 보존·기동 회귀·롤백) + run-the-app evidence. - -### CHG-20260610-02 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). 필수 필드·구체 AC 충족, source dcy-020(confirmed, handoff 승인)과 모순 없음. - -#### Intent -- delivery input 으로 승격(라이브 cutover 는 별도 배치·운영자 입회). - -#### Acceptance Criteria Delta -- Added: 없음 / Updated: 없음 / Removed: 없음 - -#### Impacted Area Delta -- Docs: docs/srs/index.md (Register Approved) / Modules: 없음 / Tests: 없음 - -#### Delivery Trace Delta -- Batches: none / Releases: none - -#### Revalidation Impact -- Reverification Needed: no (승인 단계) / Existing Implementation Invalidated: no / Status Recommendation: keep-current / Follow-up Action: 별도 cutover 배치(BAT-δ), confirm-release 전 리허설 evidence. - -#### Validation Plan -- 승인 후 cutover 배치 planning 에서 본 REQ AC 를 검증 기준으로 사용. - -### CHG-20260610-03 -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary -- **데이터 보존 cutover → DB 재초기화로 재정의.** BAT-030 구현 중 실 DB 점검에서 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 중복 데이터(같은 portfolio_id 가 closed+holding 2행)에 적용 불가 → cutover·데몬 부팅 모두 차단되는 결함 확인. 사용자 결정으로 데이터 보존 마이그레이션을 폐기하고 `trex-core-reset`(wipe+재초기화)로 대체. 구 `trex-core-cutover` 도구·테스트 삭제. - -#### Intent -- 빈 테이블에 자산 중심 스키마를 깨끗이 착지시켜 009 결함을 코드 수정 없이 우회하고, 전환 절차를 단순화(데이터 보존 불요 — dev/페이퍼·신규 인스턴스 컨텍스트). - -#### Acceptance Criteria Delta -- Updated: AC-1~4 를 cutover(보유 carry-over/롤백 역마이그레이션) → 재초기화(wipe+스키마 착지/기동 회귀/dry-run·멱등/보유 재편입+백업 복원) 기준으로 재정의 -- Removed: 보유 무손실 carry-over, instance_key 태깅(전역 wipe 라 불필요), 역마이그레이션 롤백 - -#### Impacted Area Delta -- Modules: trex_core/runtime/reset.py(신규), runtime/cutover.py(삭제) -- Tests: tests/runtime/test_reset.py(신규), test_cutover_rehearsal.py(삭제) -- Docs: data-model/runtime-flows/project-structure(도구 서술 교체) - -#### Delivery Trace Delta -- Batches: BAT-030 -- Releases: none (실 재초기화는 REL 단계) - -#### Revalidation Impact -- Reverification Needed: yes (reset 도구 PG 테스트로 충족) -- Existing Implementation Invalidated: yes (cutover 도구 폐기) -- Status Recommendation: keep-current (Implemented — reset evidence 로 대체 충족) -- Follow-up Action: 실 DB 재초기화는 REL 단계 운영자(백업 선행). - -#### Validation Plan -- tests/runtime/test_reset.py(wipe+스키마 착지·dry-run·멱등·재편입 가능) + run-the-app e2e. - -### CHG-20260610-IMPL -- Date: 2026-06-10 -- Author: Justin Ko -- Type: status -- Summary: Approved → Implemented (confirm-req-implemented), 이후 CHG-20260610-03 으로 재정의 반영. 근거: BAT-030(`trex-core-reset` 도구 + PG reset 테스트 5건; 실 재초기화는 REL 운영자) — 재정의 AC 가 release-candidate 배치 verification 의 evidence 와 연결됨(단위 367/PG 26/MQ 21 passed + run-the-app 자산 중심 e2e). Blocking Issues 없음. diff --git a/docs/srs/Non-Functional/req-024_rabbitmq-availability-degrade-policy.md b/docs/srs/Non-Functional/req-024_rabbitmq-availability-degrade-policy.md deleted file mode 100644 index 62e4c07..0000000 --- a/docs/srs/Non-Functional/req-024_rabbitmq-availability-degrade-policy.md +++ /dev/null @@ -1,143 +0,0 @@ -# req-024: RabbitMQ 가용성 degrade 정책 (버퍼링 금지 + 보수적 중단 + core 안전 매도 독립) - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: project maintainers - -## Intent -- HTTP fallback 없이 RabbitMQ 단일 transport 로 전환되므로(dcy-006 OQ-1, R-3 High), broker 장애가 trex-core 의 신규 매매를 통제 불능 상태로 만들지 않도록 명시적인 degrade 정책을 강제한다. 열린 포지션은 core 자율 안전 매도가 transport·전략과 독립으로 보호하는 전제를 별도 backlog 로 가시화한다. - -## Requirement -- RabbitMQ broker 가 불가용일 때 trex-core 는 아래 정책을 따른다(dcy-006 OQ-4). - 1. **버퍼링 금지**: 새 intent 메시지를 로컬 디스크/큐에 적재해 복구 후 자동 재생하지 않는다. 지나간 결정의 지연 실행 위험을 차단한다. - 2. **신규 매매 보수적 중단**: 스냅샷 RPC 타임아웃·intent 발행 실패 시 전략 측은 매매를 보류하고, trex-core 는 신규 슬롯 전이를 유입시키지 않는다(복구 후 신선 데이터로 재판단). -- `MessagingTransport` 어댑터는 connection drop / 재연결 백오프 / 신선도(staleness) 상태를 명확히 노출해 application 레이어가 위 정책을 일관되게 적용할 수 있게 한다. -- core 자율 안전 매도(손절 stop-loss, 시간 만기 forced_close) 는 RabbitMQ 가용성 및 전략 모듈 동작 여부와 **독립**으로 수행되어야 한다는 architecture 전제를 본 REQ 의 명시적 contract 로 둔다(현재 미구현, dcy-006 A-5 — 본 REQ 는 정책 contract 만 정의하고 구현은 별도 Discovery 로 위임). - -## Acceptance Criteria -1. Given RabbitMQ broker 가 불가용, When 전략이 intent 를 발행하려 시도, Then trex-core 는 새 슬롯 전이를 일으키지 않는다(보수적 중단). -2. Given RabbitMQ broker 가 일시 불가용 후 복구, When 복구 시점에 broker 또는 클라이언트에 누적된 intent 가 있는지 확인, Then trex-core 측에 로컬 버퍼링된 intent 가 없고 자동 재생도 일어나지 않는다(버퍼링 금지). -3. Given RabbitMQ connection drop, When 어댑터 상태를 조회, Then `connected/disconnected/reconnecting` 신선도 상태가 application 레이어에 노출되어 degrade 분기에 사용된다. -4. Given dcy-006 A-5(core 자율 안전 매도)가 본 변경 범위 외에 별도 트랙으로 존재함, When 본 REQ 를 검증, Then 본 REQ 는 정책 contract(독립 동작 전제)만 강제하고 구현 자체는 후속 Discovery/REQ 로 추적되도록 Notes 에 명시되어 있다. -5. Given 복구 후 첫 사이클, When 전략이 신규 매매를 결정, Then 결정은 복구 후 신선 스냅샷에 근거하고, 복구 전 결정의 자동 실행은 없다. - -## Impacted Area -- Docs: docs/runtime-flows.md(degrade 분기 흐름), docs/interface-contract.md(어댑터 신선도 상태 노출) — REQ-026 에서 정합. -- Modules: REQ-022 어댑터의 connection 상태/재연결 루프 노출, application 레이어의 broker-down 분기. -- Tests: broker 중단 주입 후 신규 intent 전이 없음, 복구 후 자동 재생 없음, connection 상태 전이 노출 테스트. - -## Notes -- 출처: dcy-006 NFR-3, OQ-4, R-3, A-5. -- core 자율 안전 매도(stop-loss, forced_close) 는 현재 미구현(`forced_close_at` 필드만 존재, 자동 트리거 로직 없음, stop-loss 부재). 본 REQ 는 그 capability 가 RabbitMQ·전략과 **독립**이어야 한다는 architecture contract 만 backlog 로 둔다 — 실제 구현은 본 transport 범위 밖이며 별도 Discovery 로 추적해야 한다. -- dcy-005 OQ-5(스냅샷 read 가용성) 와 공통 "보수적 안전" 철학을 따른다. -- 본 REQ 의 검증은 stub broker 가용성 토글로 충분히 수행 가능하며, 실 RabbitMQ 인프라 의존성을 강제하지 않는다. - -## Change Log - -### CHG-20260605-01 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- dcy-006 NFR-3 로부터 최초 생성(Proposed). HTTP fallback 부재로 High 영향 가용성 결합(R-3) 처리 방침을 backlog 로 정규화. - -#### Intent -- broker 장애 시 trex-core 의 보수적 중단·버퍼링 금지 정책과 core 자율 안전 매도 독립 전제를 별도 NFR REQ 로 분리해 운영 가시성 확보. - -#### Acceptance Criteria Delta -- Added: - - 보수적 중단, 버퍼링 금지, connection 신선도 노출, A-5 정책 contract 분리, 복구 후 신선 결정 (총 5개 기준) -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/runtime-flows.md, docs/interface-contract.md -- Modules: - - adapters/messaging connection 상태 노출, application degrade 분기 -- Tests: - - broker 중단/복구 시나리오 3종 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성. core 자율 안전 매도 구현은 별도 Discovery 로 분리 트래킹. - -#### Validation Plan -- stub broker 가용성 토글로 보수적 중단·버퍼링 금지·신선도 상태를 검증. A-5 항목은 정책 contract review. - -### CHG-20260605-02 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Proposed -> Approved. confirm-req 승인 게이트 통과. - -#### Intent -- RabbitMQ 가용성 degrade 정책(버퍼링 금지 + 보수적 중단 + core 자율 안전 매도 독립 contract)을 delivery input 으로 승인. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: REQ-022 어댑터의 connection 상태 노출과 동일 batch 편성 권장. core 자율 안전 매도 구현은 별도 Discovery 로 분리 트래킹. - -#### Validation Plan -- 기존 Acceptance Criteria 로 batch delivery 에서 검증. - -### CHG-20260605-03 -- Date: 2026-06-05 -- Author: Justin Ko - -#### Change Summary -- Status 변경: Approved -> Implemented. BAT-007 release-candidate verification evidence 기반. - -#### Intent -- RabbitMQ 가용성 degrade 정책(버퍼링 금지 + 보수적 중단 + core 자율 안전 매도 독립 contract)을 구현 완료 상태로 동기화. - -#### Acceptance Criteria Delta -- Added/Updated/Removed: 없음 - -#### Impacted Area Delta -- Docs/Modules/Tests: 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-007 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: core 자율 안전 매도 구현은 별도 Discovery 로 분리 트래킹. - -#### Validation Plan -- AC-1: tests/adapters/messaging/test_in_memory_transport.py::test_unavailable_transport_raises_on_publish_and_rpc + tests/application/test_messaging_event_publisher.py::test_publish_failure_does_not_break_execution_loop. -- AC-2: MessagingTransport Protocol 에 enqueue 가 없음 + 어댑터 코드 review (publish 실패 → 즉시 raise/DLQ, 로컬 보관 0). -- AC-3: TransportHealth enum + 어댑터 상태 머신 코드 + AC-1 테스트가 disconnected 노출 검증. -- AC-4: REQ-024 본문 Notes + BAT-007 implementation/verification Remaining Risks 에 명시. -- AC-5: 발행 실패가 슬롯 전이를 롤백하지 않음 (test_publish_failure_does_not_break_execution_loop). diff --git a/docs/srs/Non-Functional/req-084_structured-logging-foundation.md b/docs/srs/Non-Functional/req-084_structured-logging-foundation.md deleted file mode 100644 index 4394fb7..0000000 --- a/docs/srs/Non-Functional/req-084_structured-logging-foundation.md +++ /dev/null @@ -1,54 +0,0 @@ -# req-084: 구조화 로깅 기반 — ISO8601(KST) 타임스탬프 + 레벨 + Loki JSON + correlation id - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: project maintainers - -## Intent -- 모든 로그 라인에 시각·레벨이 없어 타임라인 재구성이 불가능했던 문제를 해결한다. ISO8601(KST) 타임스탬프 + 레벨 prefix를 전 로그에 부여하고, Loki 적재용 구조화(JSON) 출력 옵션과 correlation id(intent→order→fill) 전파를 도입해 한 거래의 로그를 단일 키로 묶는다. (dcy-029 FR-1, FR-7) - -## Requirement -- 데몬 기동(및 reset CLI)의 로깅 설정을 단일 지점으로 모아, 모든 로그 라인에 **ISO8601 타임스탬프(KST, Asia/Seoul, 환경 TZ 비의존)** + **로그 레벨** prefix를 부여한다. -- 환경 변수(`TREX_LOG_FORMAT`, 기본 `text`)로 출력 포맷을 선택한다: `text`(사람용 한 줄) / `json`(Loki 적재용 구조화 — 최소 `ts`, `level`, `logger`, `msg`, 가능하면 `correlation_id`). -- **correlation id 전파**: contextvar 기반으로 현재 처리의 correlation id를 보관하고, 로깅 필터가 모든 레코드에 주입한다. intent 수락 시 intent_id(또는 그로부터 파생한 키)를 correlation id로 설정해, 같은 거래의 intent·order·fill 로그가 동일 id를 공유한다. id 미설정 시 빈 값/생략으로 안전하게 동작한다. -- 기존 로깅 호출부(`_log.info/...`)는 메시지를 바꾸지 않아도 새 포맷·correlation id가 자동 적용되어야 한다(호출부 광범위 수정 불요). - -## Acceptance Criteria -1. Given 로깅 설정 적용 후 임의 로그 1줄, When 출력, Then ISO8601(KST) 타임스탬프 + 레벨 + 메시지를 포함한다. -2. Given `TREX_LOG_FORMAT=json`, When 로그 출력, Then 한 줄이 유효 JSON이며 `ts`/`level`/`logger`/`msg` 키를 포함한다. -3. Given `TREX_LOG_FORMAT` 미설정/`text`, When 로그 출력, Then 사람용 한 줄 포맷(타임스탬프+레벨+메시지)이다. -4. Given correlation id가 설정된 컨텍스트, When 로그 출력, Then 레코드(및 JSON `correlation_id`)에 그 id가 포함된다. -5. Given correlation id 미설정, When 로그 출력, Then 예외 없이 빈/생략 correlation id로 출력된다. -6. Given KST 타임스탬프, When 컨테이너 TZ가 UTC여도, Then 타임스탬프는 KST(+09:00) 기준이다. - -## Impacted Area -- Docs: docs/runtime-flows.md(로깅/관측성 흐름 — REQ-090과 공동), docs/project-structure.md(신규 로깅 모듈) -- Modules: 신규 `trex_core/runtime/logging_setup.py`(포맷터+JSON+correlation id 필터·contextvar) 후보, `trex_core/runtime/daemon.py`·`trex_core/runtime/reset.py`(basicConfig 대체), intent 수락 경로(correlation id set) -- Tests: 로그 포맷터 단위(text/json/KST/correlation id), 설정 적용 단위 - -## Notes -- 기존 `logging.basicConfig(level=INFO)`(daemon.py·reset.py)는 format 미지정으로 타임스탬프·레벨 prefix가 없었다(dcy-029 현재상태). -- correlation id 전파 깊이(전 경로 완전 전파 vs 핵심 경로)는 batch design에서 확정. 최소 intent admission→order dispatch 로깅 연결을 보장한다. -- 출처: dcy-029 FR-1·FR-7, OQ-2(Loki In Scope), A-4(배포 Loki 사용 전제). - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-1·FR-7로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-045 release-candidate, dcy-029 P0). 근거: 단위 AC 매핑(logging_setup text/json/KST/cid/idempotent, execution 주문 결과 submit/fill/reject + cid=slot_id + 거부 ERROR). 35 passed(logging+execution_policy), 전체 529 passed/47 skipped 회귀 0, ruff clean. 실 Loki 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Non-Functional/req-087_order-rejection-error-visibility.md b/docs/srs/Non-Functional/req-087_order-rejection-error-visibility.md deleted file mode 100644 index 5b66b60..0000000 --- a/docs/srs/Non-Functional/req-087_order-rejection-error-visibility.md +++ /dev/null @@ -1,53 +0,0 @@ -# req-087: 주문 거부 ERROR 가시성 — 조용한 롤백 제거 - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: project maintainers - -## Intent -- 주문 거부 시 `execution._rollback`이 슬롯을 조용히 전이(`slot_transition_log`에만 기록)해 호가단위 거부(#28)가 로그에 안 보였다. 거부를 **ERROR 레벨 로그**로 즉시 가시화해 반복 거부를 운영자가 즉시 인지하게 한다. (dcy-029 FR-4) - -## Requirement -- 주문 거부로 인한 롤백(`pending_buy→inactive` / `pending_sell→holding`) 시 **ERROR 레벨** 로그를 남긴다. 라인은 side, symbol, slot_id, 거부 사유를 포함한다(REQ-085 order_reject 라인과 동일 지점·동일 키, 레벨만 ERROR). -- correlation id(REQ-084)가 있으면 함께 남긴다. -- 알림 전송 채널(claude-bus/외부 webhook 등)은 본 REQ 범위 밖이다(dcy-029 OQ-3 Deferred) — ERROR 로그까지만. -- 정상 체결/정상 전이 경로의 로그 레벨은 바꾸지 않는다(거부 경로만 ERROR). - -## Acceptance Criteria -1. Given 매수 주문 거부, When 롤백(pending_buy→inactive), Then ERROR 레벨 로그(side/symbol/slot_id/사유)가 남는다. -2. Given 매도 주문 거부, When 롤백(pending_sell→holding), Then ERROR 레벨 로그가 남는다. -3. Given 정상 체결, When 전이, Then ERROR 로그가 남지 않는다(거부 경로만 ERROR). -4. Given `TREX_LOG_FORMAT=json`, When 거부, Then `level=ERROR` + 사유 키가 구조화 출력된다. -5. Given 반복 거부 N회, When grep `level=ERROR`/`event=order_reject`, Then N건 모두 가시화된다. -6. Given 알림 채널, When 거부, Then 본 반복에서는 전송 채널을 호출하지 않는다(Deferred, 로그까지만). - -## Impacted Area -- Docs: docs/runtime-flows.md(주문 집행 거부 가시성) -- Modules: trex_core/application/execution.py(`_rollback`/거부 경로 ERROR 로그), REQ-084·085와 협조 -- Tests: tests/application/test_execution* (거부 ERROR caplog 단위, 정상 경로 비-ERROR) - -## Notes -- REQ-085(주문 결과 로깅)와 동일한 reject 지점에서 협조 — REQ-085는 일관 키, 본 REQ는 거부 라인의 ERROR 레벨 보장. -- 출처: dcy-029 FR-4, OQ-3(알림 Deferred). - -## Change Log - -### CHG-20260616-01 -- Date: 2026-06-16 -- Author: Justin Ko - -#### Change Summary -- dcy-029 FR-4로부터 최초 생성(Proposed). - -### CHG-20260616-02 -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Proposed → **Approved** (confirm-req). 필수 필드·구체 AC 충족, source dcy-029(confirmed, Open OQ 0) 정합. dcy-029 P0/P1 batch 편성 예정. - -### CHG-20260616-IMPL -- Date: 2026-06-16 -- Author: Justin Ko -- Type: status -- Summary: Approved → **Implemented** (confirm-req-implemented, BAT-045 release-candidate, dcy-029 P0). 근거: 단위 AC 매핑(logging_setup text/json/KST/cid/idempotent, execution 주문 결과 submit/fill/reject + cid=slot_id + 거부 ERROR). 35 passed(logging+execution_policy), 전체 529 passed/47 skipped 회귀 0, ruff clean. 실 Loki 적재는 release 운영 검증 위임. Blocking Issues 없음. diff --git a/docs/srs/Non-Functional/req-108_reconcile-blocking-regression-and-nfr.md b/docs/srs/Non-Functional/req-108_reconcile-blocking-regression-and-nfr.md deleted file mode 100644 index f67a0c3..0000000 --- a/docs/srs/Non-Functional/req-108_reconcile-blocking-regression-and-nfr.md +++ /dev/null @@ -1,130 +0,0 @@ -# req-108: 차단형 reconcile 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습 - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: trex-core-dev - -## Intent -- dcy-034 NFR-1~4를 단일 REQ로 통합해, 차단형 reconcile 기능 전반에 걸쳐 적용되는 비기능 요건을 명시한다. -- 주요 불변식: (1) warning은 durable해야 함, (2) 단기 인스턴스 오발동 없음, (3) 자동 정정·자동 청산 금지, (4) wire 계약(slots RPC 스키마) 최소 침습. - -## Requirement -- [NFR-1 내구성] reconcile-warning 차단 상태는 데몬 재시작 후에도 유지되어야 한다. in-memory overlay만으로 저장하지 않는다. -- [NFR-2 회귀 방지] 단기 인스턴스(M1/M2 라이브 orb+tsl) 정상(일치) 운영에서 신규 reconcile-warning 차단이 오발동하지 않아야 한다. 차단은 실제 수량 불일치가 있을 때만 발생한다. -- [NFR-3 자동 정정 금지] reconcile는 탐지+차단까지만 자동이고, 슬롯 수량·원장 변경은 운영자 명령(`sync`/`verify`)으로만 일어난다. 자동 무음 정정·자동 청산을 하지 않는다(issue #15 기조). -- [NFR-4 wire 계약 최소 침습] `SlotState` enum, `ALLOWED_TRANSITIONS`, slots RPC(`trex.slots.req`) 기존 필드는 변경하지 않는다. 신규 노출(`reconcile_warned` 필드)은 additive only로 추가하며, 기존 클라이언트(trex-orb-v1, trex-tsl-v1)가 신규 필드를 무시해도 동작이 깨지지 않아야 한다. - -## Acceptance Criteria -1. Given reconcile-warning이 기록된 상태에서 데몬을 재시작하면, When `ReconcileWarningStore.is_warned(slot_id)` 를 조회하면, Then True가 반환된다(NFR-1, durable). -2. Given 단기 인스턴스의 슬롯 qty == 브로커 qty인 정상 시나리오에서, When boot/일일 reconcile가 실행되면, Then ReconcileWarningStore에 신규 행이 생기지 않는다(NFR-2, 회귀 0, S-4). -3. Given 불일치 탐지(B 또는 C) 후, When reconcile가 자동 실행되면, Then 슬롯 qty, 원장 레코드, 브로커 포지션에 자동 변경이 없다(NFR-3, 탐지+차단만 자동). -4. Given 기존 trex-orb-v1 또는 trex-tsl-v1 클라이언트가 `trex.slots.req` 응답에서 `reconcile_warned` 필드를 수신할 때, When 클라이언트가 해당 필드를 무시하면, Then 기존 동작이 유지된다(NFR-4, additive 하위 호환). -5. Given `domain/slot/state.py`에 새 SlotState 또는 새 전이가 추가되지 않으면, When slots RPC 응답을 생성하면, Then 기존 `status` 필드 값 집합이 유지된다(NFR-4, enum 불변). -6. Given 기존 `ALLOWED_TRANSITIONS`에 없는 전이를 `transit_slot`으로 시도하면, When SlotService가 처리하면, Then 기존 예외(InvalidTransitionError 등)가 발생한다(전이표 불변 확인). - -## Impacted Area -- Docs: `docs/interface-contract.md` (additive 필드 명시, enum 불변 확인) -- Modules: - - `trex_core/domain/slot/state.py` (변경 없음 — 불변 확인 테스트) - - `trex_core/adapters/persistence/` (reconcile_warning_store durable 검증) -- Tests: - - `tests/domain/test_slot_state.py` (SlotState enum 불변, ALLOWED_TRANSITIONS 불변 회귀) - - `tests/application/test_reconciliation.py` (정상 케이스 오발동 없음 회귀, 자동 정정 없음 검증) - - `tests/application/test_messaging_handlers.py` (reconcile_warned additive 필드 하위 호환) - -## Notes -- NFR-4의 slots RPC wire 계약 additive 변경은 REQ-104와 함께 delivery된다. 두 REQ의 AC가 일관되는지 batch design에서 확인한다. -- 단기 인스턴스 회귀(NFR-2/S-4)는 별도 픽스처를 사용한 통합 테스트로 검증한다: 단기 인스턴스 시나리오(당일 슬롯, 슬롯 qty == 브로커 qty)에서 boot reconcile를 실행하고 ReconcileWarningStore가 빈 상태임을 확인. -- dcy-034 NFR-1~4, S-4 대응. - -## Change Log - -### CHG-20260626-01 -- Date: 2026-06-26 -- Author: trex-core-dev -- Status: Approved → Implemented -- Summary: BAT-050/051(PR #31/#32) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. durable 검증, 정상 단기 오발동 없음, 자동 정정 금지, SlotState enum 불변, ALLOWED_TRANSITIONS 불변(+1 sync 전이), additive 하위 호환 전 AC 검증 완료. - -#### Acceptance Criteria Delta -- 없음 - -#### Impacted Area Delta -- 없음 - -#### Delivery Trace Delta -- Batches: BAT-050 (core-A, AC-1~6), BAT-051 (core-B, ALLOWED_TRANSITIONS 14개 반영) -- Releases: M3 (배포 대기) - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Implemented -- Follow-up Action: 없음. - -#### Validation Plan -- BAT-050/051 verification.md AC 매핑 전항목 PASS 확인됨. - -### CHG-20260625-02 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-108 **Proposed → Approved** (confirm-req, 팀장 2026-06-25). - -#### Intent -- 팀장 confirm-req로 상태 승인. 내용 변경 없음(초안 그대로 승인). - -#### Acceptance Criteria Delta -- Added: 없음. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- 없음. - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 이후) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: Approved -- Follow-up Action: batch 편성 후 구현. - -#### Validation Plan -- durable 재시작 검증 / 정상 케이스 오발동 없음 회귀 / SlotState enum+전이표 불변 회귀 / additive 하위 호환 테스트 pass. - -### CHG-20260625-01 -- Date: 2026-06-25 -- Author: trex-core-dev - -#### Change Summary -- REQ-108 신규 생성 (Proposed) — dcy-034 NFR-1~4 대응, draft-req 단계. - -#### Intent -- 차단형 reconcile 도입이 기존 시스템(단기 인스턴스, wire 계약, 도메인 불변식)에 의도치 않은 영향을 주지 않도록 비기능 요건을 명시하고 검증 기준을 확정. - -#### Acceptance Criteria Delta -- Added: AC-1~6 전부 신규. -- Updated: 없음. -- Removed: 없음. - -#### Impacted Area Delta -- Docs: interface-contract.md 갱신 예정. -- Modules: 변경 없음(불변 확인 목적). -- Tests: 불변 회귀 테스트 신규. - -#### Delivery Trace Delta -- Batches: 미정 -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no (신규 REQ) -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-102~107과 동일 batch 편성. - -#### Validation Plan -- durable 재시작 검증 / 정상 케이스 오발동 없음 회귀 / SlotState enum+전이표 불변 회귀 / additive 하위 호환 테스트 pass. diff --git a/docs/srs/Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md b/docs/srs/Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md deleted file mode 100644 index 610ed4b..0000000 --- a/docs/srs/Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md +++ /dev/null @@ -1,99 +0,0 @@ -# req-115: 그날-시작 장중 무중단 보장 — 그날 고정 인증정보 캐시 + portfolio-hub 장애 비중단 - -- Status: Implemented -- Type: Non-Functional -- Priority: High -- Owner: trex-core-dev - -## Intent -- dcy-036 C3/FR-3/NFR-1~3에 따라, 그날-시작 로드 성공 이후 장중 portfolio-hub 장애는 이미 시작된 그날 실행(진입·청산 포함)을 중단시키지 않아야 한다. 그날 인증정보는 캐시로 유지되어 KIS API 호출이 계속되고, 원장 기록은 `ledger_outbox`로 portfolio-hub 복구 후 drain된다. -- 또한 기존 env-only 경로(fetch 미활성)에서 이 REQ의 변경이 아무런 회귀를 발생시키지 않아야 한다(NFR-1). - -## Requirement -- **그날 고정 캐시**: `credential_fetch_enabled = true` 경로에서 그날-시작 체크포인트에 fetch된 `BrokerCredentialPayload`는 그날 동안 메모리 캐시로 유지된다. 장중 portfolio-hub 장애 또는 복구 이벤트가 발생해도 그날 중 자동 재fetch하지 않는다. 다음 그날-시작 체크포인트(다음 날 07:30 KST 또는 중장기 리밸런싱 주기)에서만 새로 fetch한다. -- **KIS API 연속성**: 그날 캐시된 인증정보로 KIS broker adapter(`KISBrokerAdapter`)의 주문 제출·체결 조회·토큰 갱신이 장중 portfolio-hub 장애와 무관하게 계속된다. -- **원장 무손실**: 그날 체결 기록은 `ledger_outbox`(REQ-019)를 통해 durable하게 trex-core PG에 기록되므로 장중 portfolio-hub 장애가 원장 유실로 이어지지 않는다. 이는 기존 구현(REQ-019 Implemented)의 속성을 재확인·명시하는 것이다. -- **회귀 없음**: `credential_fetch_enabled = false`(기존 env-only 경로)에서 이 REQ의 변경이 기존 동작에 영향을 주지 않는다. 기존 테스트가 전부 그린을 유지해야 한다. -- **보안**: 그날 캐시된 인증정보(특히 `app_secret`)는 메모리에만 유지하며 Redis 또는 다른 외부 저장소에 평문 기록하지 않는다. 캐시 만료(다음날 로드 사이클)까지 메모리 내 보존. - -## Acceptance Criteria -1. Given 그날-시작 로드 성공(`credential_fetch_enabled = true`) 후 portfolio-hub를 내려(네트워크 차단) 장중 장애 주입, When BuyIntent 또는 SellIntent를 처리, Then KIS 브로커 어댑터가 캐시된 인증정보로 주문을 처리하고 `PortfolioHubUnavailableError`로 인해 주문이 차단되지 않는다. -2. Given 장중 portfolio-hub 장애 상태에서 체결 발생, When 체결 처리, Then `ledger_outbox`에 durable하게 기록된다 (portfolio-hub 복구 후 drain — 기존 REQ-019 속성 회귀 없음). -3. Given 그날 로드 성공 후 장중 portfolio-hub가 잠시 다운되었다가 복구, When 그날 중 재fetch 트리거 없음, Then 그날 인증정보는 재load 없이 최초 fetch 값 그대로 유지된다 (그날 고정 불변식). -4. Given `credential_fetch_enabled = false`(기존 env-only), When 기존 회귀 테스트 스위트 실행, Then 0 fail — 기존 동작 보존. -5. Given 그날 캐시된 `app_secret`, When Redis 또는 외부 저장소 확인, Then 평문 기록 없음 (메모리 캐시 한정). -6. Given 그날-시작 로드 성공 직후 INFO 로그 확인, When 캐시 상태 기록, Then `app_secret` 평문이 로그에 없고, account_ref 및 캐시 유효 날짜가 기록된다. - -## Impacted Area -- Docs: 없음 (baseline 문서 갱신은 별도 scope) -- Modules: - - `trex_core/application/snapshot_scheduler.py` (또는 그날-시작 후크 레이어) — 그날 고정 캐시 저장소(`_cached_credential: BrokerCredentialPayload | None`, `_credential_date: date | None`) 관리, 장중 재fetch 방지 로직 - - `trex_core/runtime/config.py` 또는 조립 레이어 — 캐시된 `BrokerCredentialPayload`에서 `KISConfig` 재구성 경로 보장 (장중 KIS adapter 재생성 불필요) -- Tests: - - `tests/application/test_snapshot_scheduler.py` — AC-1~3: 장중 portfolio-hub 장애 주입 시나리오 (stub fetch_broker_credential + 캐시 상태 검증) - - `tests/runtime/test_config.py` (또는 회귀 스위트) — AC-4: 기존 env-only 테스트 그린 유지 - - `tests/application/` — AC-5: 캐시가 Redis에 기록되지 않는 검증 (mock Redis 확인) - -## Notes -- AC-1의 "주문 처리 계속"은 KIS broker adapter가 이미 로컬로 보유한 인증정보를 사용한다는 전제다. KIS token cache(Redis, REQ-016)는 KIS access token(≠ app_key/app_secret)을 저장하며 이는 별개 캐시 계층 — 이 REQ가 다루는 것은 `app_key`/`app_secret`/`account_ref`의 "그날 고정" 보장이다. -- `ledger_outbox` 무손실(AC-2)은 REQ-019 Implemented의 기존 속성을 이 컨텍스트에서 재확인·evidence로 명시하는 것이다. 추가 구현은 불필요하나, 테스트에서 outbox 행이 생성됨을 회귀 케이스로 확인한다. -- 중장기 인스턴스에서 그날 고정의 "하루" 경계가 다를 수 있다(dcy-027 리밸런싱 주기 단위). AC-3는 단기(07:30 KST 스냅샷) 기준으로 테스트하고, 중장기는 설계 단계에서 확인 후 반영. -- 보안(AC-5): KIS access token(REQ-016)은 Redis TTL 캐시가 이미 존재하며 이와 혼동하지 않는다. `app_secret`은 Redis에 별도로 쓰지 않음. -- Source: dcy-036 C3, FR-3, NFR-1(회귀안전), NFR-2(보안), NFR-3(관측성 중 캐시 상태 로깅 부분), OQ-2 Answered (그날 고정). - -## Change Log - -### CHG-20260628-01 -- Date: 2026-06-28 -- Author: trex-core-dev - -#### Change Summary -- 신규 생성 (Proposed) — dcy-036 C3/FR-3/NFR-1~3 산출물. - -#### Intent -- dcy-036 FR-3(장중 무중단 보장)과 NFR-1~3을 Non-Functional REQ로 정규화한다. 구현 근거 없이 "완료"로 처리하지 않도록 측정 가능한 AC를 명시. - -#### Acceptance Criteria Delta -- Added: - - AC-1~6 전체 (신규 초안) -- Updated: 없음 -- Removed: 없음 - -#### Impacted Area Delta -- Docs: 없음 -- Modules: - - `snapshot_scheduler.py` 그날 고정 캐시 로직 - - `config.py` 조립 레이어 캐시 경로 -- Tests: - - `test_snapshot_scheduler.py`, `test_config.py` 케이스 추가 - -#### Delivery Trace Delta -- Batches: 미정 (suggest-batch-reqs 대기) -- Releases: 미정 - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 REQ-112~115 일괄 batch 배정 검토. - -#### Validation Plan -- pytest로 AC-1~6 확인. AC-1(장중 장애 후 주문 처리 계속)은 PortfolioHubReadAdapter를 장중 failure stub으로 교체하는 방식으로 격리. AC-4(기존 회귀)는 전체 테스트 스위트 재실행으로 확인. - -### CHG-20260628-02 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- confirm-req: `Proposed` → `Approved`. dcy-036 FR-3/NFR-1~3/OQ-2 추적. 그날 고정 캐시(장중 무재fetch, AC-3)·KIS API 연속성(AC-1)·ledger_outbox 무손실 재확인(AC-2, REQ-019)·app_secret 메모리 한정(AC-5, Redis 미기록)·env-only 회귀보존(AC-4) 적절. KIS access token 캐시(REQ-016)와 구분 명확. blocker 없음. - -### CHG-20260628-03 -- Date: 2026-06-28 -- Author: 팀장(Claude) -- 부분 구현 — BAT-053 PR #34 머지(merge commit 01655ab). 그날 고정 캐시 저장(AC-3), app_secret 메모리 한정(AC-5), ledger_outbox 무손실 재확인(AC-2), env-only 회귀보존(AC-4) 구현·머지 완료. **연기: AC-1(캐시된 인증정보를 실 KIS 거래에 주입=KISBroker 런타임 재조립) → M3 후속 batch.** AC-9 전용 테스트 보강도 후속. Status `Approved` 유지(미충족 AC 존재). - -### CHG-20260628-04 -- Date: 2026-06-28 -- Author: 팀장(Claude) — confirm-req-implemented: `Approved` → `Implemented` -- 근거: BAT-054(dcy-036 KISBroker 양의 경로 주입), 커밋 11decaa. QC(opus) **PASS-WITH-NOTES, Blocker 0** — 독립 테스트 재실행 757 passed/회귀 0. **연기됐던 AC-1(그날-시작 fetch한 인증정보를 실 KISBroker에 `reinitialize()`로 주입)** 충족·단위검증. 그날 고정 캐시 의미론(장중 무재호출, AC-3) 유지, 장중 portfolio-hub 장애에도 그날 실행 무중단(reinit 실패 시 graceful degrade) 검증. -- ⚠️ **단서(정직성)**: AC-1의 **실 KIS mock end-to-end 통합검증은 stub 한정**(코드/단위 레벨 PASS). "그날-시작 fetch → 실 KISBroker → 장중 PH 다운 중 실주문 지속" 통합검증은 **portfolio-hub dcy-010 배포 후 릴리즈 스모크로 수행** = **릴리즈 go 선결 조건**(M2식 "Implemented이나 라이브 미검증" 패턴). -- 후속(백로그): QC Minor #2 — L2 Redis 토큰 secret-회전 invalidate 보강(account_ref+app_key last-4 동일 + app_secret만 회전 시 stale 토큰 재사용 저확률 edge). 실거래라 등록. -- 게이트: 머지(PR)는 사장님 결재 게이트. diff --git a/docs/srs/Testing/req-003_slot-domain-tests-and-contract-guard.md b/docs/srs/Testing/req-003_slot-domain-tests-and-contract-guard.md deleted file mode 100644 index 3b30742..0000000 --- a/docs/srs/Testing/req-003_slot-domain-tests-and-contract-guard.md +++ /dev/null @@ -1,273 +0,0 @@ -# req-003: slot 도메인 단위테스트 이식 및 컬럼 계약 가드 - -- Status: Implemented -- Type: Testing -- Priority: High -- Owner: project maintainers - -## Intent -- 이식의 정확성(도메인 불변식)과 `Slot` 컬럼 계약을 자동 테스트로 강제해, 테스트 우선 검증(Global Rule 3)을 충족하고 향후 영속화 단계의 계약 드리프트를 막는다. - -## Requirement -- trex-orbit 의 순수 도메인 단위테스트 `test_state.py`·`test_service.py` 를 `tests/domain/slot/` 로 이식해 통과시키고, 도메인 패키지에 표준 필드 집합 `EXPECTED_SLOT_FIELDS` 를 선언한 뒤, 순수 계약 테스트 `test_contract.py` 가 `{f.name for f in dataclasses.fields(Slot)} == EXPECTED_SLOT_FIELDS` 를 단언하도록 신설한다. - -## Acceptance Criteria -1. Given 이식한 단위테스트, When `pytest tests/domain/slot/test_state.py tests/domain/slot/test_service.py` 실행, Then 모두 통과한다. (FR-3, NFR-2 검증) -2. Given `EXPECTED_SLOT_FIELDS` 선언과 `test_contract.py`, When `pytest tests/domain/slot/test_contract.py` 실행, Then 통과한다. (NFR-3) -3. Given `Slot` 에 임시 필드를 추가하거나 삭제, When 계약 테스트를 실행, Then 테스트가 실패한다(음성 케이스 재현). -4. Given 계약 테스트, When 구현을 확인, Then 타입 단언은 하지 않고(이름/존재 중심) 타입 매핑은 dcy-001 부록 A / docs/data-model.md 문서에 둔다. - -## Impacted Area -- Docs: docs/data-model.md, dcy-001 부록 A (타입 매핑 계약 기준) -- Modules: trex_core/domain/slot (EXPECTED_SLOT_FIELDS 선언; 부록 A 를 가리키는 주석 포함) -- Tests: tests/domain/slot/{test_state,test_service,test_contract}.py - -## Notes -- NFR-2(불변식 검증)·NFR-3(컬럼 계약 강제)를 본 REQ 로 흡수. FR-3 + FR-5 통합. -- DB-side(information_schema) 드리프트 검사는 이번 범위 밖이며 영속화 REQ 로 인계한다(dcy-001 OQ-2). -- REQ-001(부트스트랩)·REQ-002(도메인 이식) 선행. -- 출처: dcy-001 FR-3, FR-5. - -## Change Log - -### CHG-20260603-01 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- dcy-001 FR-3·FR-5 로부터 최초 생성(Proposed). NFR-2 검증·NFR-3 강제를 흡수. - -#### Intent -- 도메인 테스트 이식 green + 컬럼 계약 가드 요구사항을 backlog 로 정규화. - -#### Acceptance Criteria Delta -- Added: - - 이식 테스트 통과, 계약 테스트 통과, 음성 케이스 실패 재현, 타입 단언 배제 4개 기준 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/data-model.md, dcy-001 부록 A -- Modules: - - trex_core/domain/slot (EXPECTED_SLOT_FIELDS) -- Tests: - - tests/domain/slot/{test_state,test_service,test_contract}.py - -#### Delivery Trace Delta -- Batches: - - none -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: confirm-req 후 batch 편성 - -#### Validation Plan -- `pytest tests/domain/slot` green + 필드 변경 시 계약 테스트 실패 재현으로 검증. - -### CHG-20260603-02 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Proposed → Approved (confirm-req). - -#### Intent -- 승인 게이트(필수 필드·구체 AC·blocker 없음) 충족, source dcy-001(confirmed, handoff 승인)과 모순 없음 확인. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register 상태 Approved) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - none (batch 편성 전) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery batch 편성 - -#### Validation Plan -- 승인 후 batch planning 에서 본 REQ 의 Acceptance Criteria 를 검증 기준으로 사용. - -### CHG-20260603-03 -- Date: 2026-06-03 -- Author: Justin Ko - -#### Change Summary -- Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-001 release-candidate). - -#### Intent -- BAT-001 verification.md 의 evidence(이식 테스트 green / 계약 테스트 green / rogue 필드 음성 케이스 / 타입 단언 배제)로 AC1~AC4 충족 → 구현 완료 동기화. - -#### Acceptance Criteria Delta -- Added: - - 없음 -- Updated: - - 없음 -- Removed: - - 없음 - -#### Impacted Area Delta -- Docs: - - docs/srs/index.md (Register Implemented) -- Modules: - - 없음 -- Tests: - - 없음 - -#### Delivery Trace Delta -- Batches: - - BAT-001 (release-candidate) -- Releases: - - none - -#### Revalidation Impact -- Reverification Needed: no -- Existing Implementation Invalidated: no -- Status Recommendation: keep-current -- Follow-up Action: draft-release 로 release 편성 - -#### Validation Plan -- BAT-001 verification evidence 재참조로 충분. - -### CHG-20260608-01 - -- Date: 2026-06-08 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-012) 컬럼 계약 가드를 `entry_price` → `limit_price` rename 에 맞춰 갱신. `EXPECTED_SLOT_FIELDS`(REQ-002)와 daily_slot 실제 컬럼(information_schema) 대조가 `limit_price` 기준으로 통과. BAT-016 재구현·재검증 완료 → Implemented 유지. - -#### Intent - -- 도메인/스키마 rename 후에도 코드 계약 ↔ DB 컬럼 드리프트 가드가 정확히 작동하도록 동기화. - -#### Acceptance Criteria Delta - -- Updated: - - 컬럼 계약/드리프트 테스트 기준 필드 entry_price → limit_price. - -#### Impacted Area Delta - -- Tests: tests/domain/slot/test_contract.py, tests/adapters/persistence/test_migration_and_drift.py -- Modules: (없음 — 가드는 EXPECTED_SLOT_FIELDS 상수 참조) - -#### Delivery Trace Delta - -- Batches: BAT-016 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-016 에서 완료) -- Existing Implementation Invalidated: no (가드 로직 불변, 기대 필드셋만 rename 반영) -- Status Recommendation: Implemented (change-req → BAT-016 재검증) -- Follow-up Action: confirm-req-implemented(BAT-016). - -#### Validation Plan - -- BAT-016: 도메인 필드셋 가드 + PG 컬럼 드리프트 가드 green. - -### CHG-20260609-01 - -- Date: 2026-06-09 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-019/issue #18) 슬롯 도메인 변경(REQ-002: entry_count 필드 + closed→pending_buy 전이)에 맞춰 테스트 계약 정합: 상태 머신 전이 카운트 14→15(`closed→pending_buy` allowed 이동), 컬럼 계약 가드가 `entry_count` 를 포함, entry_count 증분·보존/closed 재할당 selector 단위 테스트(test_slot_entry_count.py) 추가. BAT-025 검증 완료 → Implemented 유지. - -#### Intent - -- entry_count + closed 재할당 도메인 변경이 회귀 없이 계약(필드셋·전이 집합)으로 강제되도록 테스트를 동기화. - -#### Acceptance Criteria Delta - -- Updated: - - 상태 머신 전이 집합 테스트: `closed→pending_buy` 를 allowed 로, ALLOWED_TRANSITIONS 크기 14→15 로 단언. - - 컬럼 계약 가드: `EXPECTED_SLOT_FIELDS` 에 `entry_count` 포함을 검증. -- Added: - - entry_count 증분/보존, closed→pending_buy clean-slate, find_allocatable_slot 정렬 단위 테스트. - -#### Impacted Area Delta - -- Tests: tests/domain/slot/test_state.py, tests/domain/slot/test_contract.py, tests/domain/slot/test_slot_entry_count.py(신규), tests/application/test_buy_admission_reallocation.py(신규), tests/adapters/persistence/test_migration_and_drift.py(EXPECTED_SLOT_FIELDS 자동 반영) - -#### Delivery Trace Delta - -- Batches: BAT-025 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: yes (BAT-025 에서 완료) -- Existing Implementation Invalidated: no (계약 테스트 확장 — 기존 단언 보존) -- Status Recommendation: Implemented (change-req → BAT-025 재검증) -- Follow-up Action: confirm-req-implemented(BAT-025). - -#### Validation Plan - -- BAT-025: 전이/계약 가드 + 신규 entry_count·reallocation 단위 테스트 green (unit), PG 게이트 드리프트 가드는 test DSN 환경에서 자동 검증. - -### CHG-20260610-01 - -- Date: 2026-06-10 -- Author: Justin Ko - -#### Change Summary - -- (change-req, dcy-020) 슬롯 도메인 계약 가드/단위테스트가 영구 슬롯 모델(REQ-056/057)로 **재정의 예정** — 전이 집합(inactive 중심)·`EXPECTED_SLOT_FIELDS`(entry_count 제거·instance_key 추가)가 바뀐다. 현재 코드/테스트 불변이라 Implemented 유지(keep-current), dcy-020 delivery 배치에서 테스트 재작성·확정. - -#### Intent - -- 영구 슬롯 모델 전환의 테스트 계약 추적성 forward-link. - -#### Acceptance Criteria Delta - -- Updated: 없음(dcy-020 delivery 배치에서 재정의) - -#### Impacted Area Delta - -- Docs: dcy-020, REQ-056/057 -- Modules: (예정) 없음 -- Tests: (예정) test_state/test_contract/test_migration_and_drift 재작성 - -#### Delivery Trace Delta - -- Batches: (예정) dcy-020 상태머신/스키마 배치 -- Releases: none - -#### Revalidation Impact - -- Reverification Needed: no (현 시점) -- Existing Implementation Invalidated: no (코드 불변) -- Status Recommendation: keep-current -- Follow-up Action: dcy-020 delivery 배치에서 재정의 + status 전환. - -#### Validation Plan - -- dcy-020 배치 verification 으로 대체. diff --git a/docs/srs/index.md b/docs/srs/index.md deleted file mode 100644 index f32df66..0000000 --- a/docs/srs/index.md +++ /dev/null @@ -1,219 +0,0 @@ -# Software Requirement Specification Index - -## 목적 -- 이 문서는 프로젝트의 SRS 운영 기준과 requirement 목록을 관리하는 entry point이다. -- 개별 requirement의 상세 내용은 타입별 폴더 하위 `req-XXX_*.md` 파일에서 관리한다. - -## 사용 방법 -1. 새로운 requirement가 필요하면 `Next Requirement ID`를 확인하고 `docs/srs/req-template.md`를 복사해 `docs/srs//req-XXX_.md` 파일을 생성한다. -2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. -3. 작성 또는 수정 후 누락과 모호성을 점검한다. -4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다. -5. `Proposed` requirement를 승인하려면 승인 준비 상태와 blocker를 점검한다. -6. `Approved` requirement는 구현 이슈나 작업 항목으로 연결한다. -7. 구현과 검증이 끝난 requirement는 `confirm-req-implemented` skill로 완료 전환 가능 여부를 점검한다. -8. requirement 변경 시 `change-req` skill을 기준으로 해당 REQ 파일의 `Change Log`를 먼저 갱신한 뒤 본문을 수정한다. -9. 구현 전에는 관련 REQ 파일과 최근 `Change Log`를 기준으로 변경 범위를 판단한다. - -## 상태 정의 -- Proposed: 초안 상태이며 구현 기준으로 확정되지 않음 -- Approved: 구현 기준으로 승인됨 -- Implemented: 구현과 검증이 완료됨 -- Deprecated: 더 이상 유지하지 않음 - -## Status Transition Evidence - -### Proposed -> Approved -- `Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`가 모두 존재해야 한다. -- `Type`은 아래 분류 도표에서 정확하게 하나로 지정되어야 한다. -- `Acceptance Criteria`는 구현 판단에 사용할 수 있을 정도로 구체적이어야 한다. -- 미해결 assumption 또는 open question이 있으면 `Notes`에 명시하고, 승인 blocker 여부를 판단해야 한다. -- 승인 전환 시 해당 REQ 파일의 `Change Log`에 status 변경 사유를 기록해야 한다. - -### Approved -> Implemented -- 각 `Acceptance Criteria`에 대해 test, manual verification, 또는 구현 결과 중 최소 1개의 evidence가 있어야 한다. -- evidence는 requirement 문서 자체 또는 연결된 구현/검증 결과에서 추적 가능해야 한다. -- 미충족 `Acceptance Criteria`가 있으면 `Implemented`로 전환하지 않는다. -- 구현 완료 전환 시 잔여 리스크와 미검증 영역이 있으면 `Change Log`에 기록해야 한다. -- 상태 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. - -### Requirement Change Handling -- 기존 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`)을 수정하기 전에는 새 `Change Log` 항목을 만든다. -- `Change Log`에는 최소한 `Acceptance Criteria Delta`, `Impacted Area Delta`, `Delivery Trace Delta`, `Revalidation Impact`, `Validation Plan`이 포함되어야 한다. -- 변경이 기존 구현 또는 verification evidence를 무효화하면 `Implemented` requirement는 `Approved`로 되돌린다. -- 변경이 승인 전제 자체를 다시 검토하게 만들면 `Approved` 또는 `Implemented` requirement를 `Proposed`로 되돌릴 수 있다. -- 상태 되돌림이 발생하면 `Requirement Register`와 `Recent Change Log Summary`도 함께 갱신한다. - -## Requirement 타입 분류 도표 - -| 타입 | 정의 | 예시 | -|------|------|------| -| **Configuration** | 설정 옵션, 커스터마이징 포인트 | 설정 파라미터, 환경 변수, 옵션 항목 | -| **Data** | 데이터 구조, 저장/로드, 파일 형식, 스키마 | 설정 파일 형식, DB 스키마, API 구조 | -| **Deployment** | 배포 방식, 운영 절차, 배포 환경 | 컨테이너 배포, 스택 관리, 운영 가이드 | -| **Documentation** | 문서 작성, 가이드, 매뉴얼, 안내서 | 사용자 가이드, API 문서, 튜토리얼 | -| **Exception** | 오류 처리, 예외 복구, 재시도 전략 | 오류 복구, 검증 실패 처리, 폴백 전략 | -| **Installation** | 시스템 설치, 환경 구성, 초기화 절차 | Docker 이미지 빌드, DB 마이그레이션, 사전 조건 설정 | -| **Integration** | 외부 시스템 연동, 플러그인, 확장 | 3rd party 연동, 플러그인 인터페이스 | -| **Interface** | 사용자 상호작용 방식 (UI, API, CLI, 제어방식) | CLI 명령어, API 엔드포인트, 웹 UI 기능 | -| **Migration** | 데이터 이전, 버전 업그레이드 | 데이터 마이그레이션, 버전 호환성 | -| **Non-Functional** | 성능, 보안, 확장성, 호환성, 신뢰성 | 성능 목표, 보안 정책, 확장성 | -| **Testing** | 테스트 전략, QA 요구사항, 검증 방식 | 테스트 커버리지, 회귀 테스트, 수락 기준 | - -## Requirement 작성 규칙 -- 각 requirement는 고유 ID를 가진다. 예: `REQ-001` -- 다음 신규 requirement 번호의 source of truth는 이 문서의 `Next Requirement ID` 섹션이다. -- 새 requirement를 추가하거나 ID 충돌이 발생할 수 있는 변경을 했으면 `Next Requirement ID`를 함께 갱신한다. -- 각 requirement는 `Type`을 명시해야 한다. 위 분류 도표 중 정확하게 하나로 선택하며, `docs/srs//` 폴더명도 같은 문자열을 사용한다. -- 각 requirement는 `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`를 반드시 포함한다. -- 모호한 표현("개선한다", "적절히 처리한다") 대신 관찰 가능하고 test 가능한 문장을 사용한다. -- requirement 변경 사항은 해당 REQ 파일의 `Change Log`에 기록한다. -- requirement 변경 시 영향 받는 batch/release와 재검증 필요 여부를 `Change Log`에 남긴다. -- `Recent Change Log Summary`는 최신 5건만 유지한다. -- status 전환은 `Status Transition Evidence` 기준을 따른다. - -## Next Requirement ID - -- Current: `REQ-128` -- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - -## Requirement Register - -| ID | Title | Type | Status | Priority | Owner | Link | -| --- | --- | --- | --- | --- | --- | --- | -| REQ-001 | trex-core Python 프로젝트 부트스트랩 | Installation | Implemented | High | project maintainers | [req-001](./Installation/req-001_trex-core-bootstrap.md) | -| REQ-002 | slot 순수 도메인 이식 및 공개 API 확정 | Interface | Implemented | High | project maintainers | [req-002](./Interface/req-002_slot-pure-domain-port.md) | -| REQ-003 | slot 도메인 단위테스트 이식 및 컬럼 계약 가드 | Testing | Implemented | High | project maintainers | [req-003](./Testing/req-003_slot-domain-tests-and-contract-guard.md) | -| REQ-004 | baseline 문서 planned→실제 정합 | Documentation | Implemented | Medium | project maintainers | [req-004](./Documentation/req-004_baseline-doc-alignment.md) | -| REQ-005 | 슬롯 코어 데몬 런타임 부트스트랩 | Installation | Implemented | High | project maintainers | [req-005](./Installation/req-005_slot-core-daemon-runtime.md) | -| REQ-006 | PostgreSQL SlotRepository 영속화 + 스키마 | Data | Implemented | High | project maintainers | [req-006](./Data/req-006_postgresql-slot-persistence.md) | -| REQ-007 | intent 제출 HTTP API + 멱등 | Interface | Implemented | High | project maintainers | [req-007](./Interface/req-007_intent-submission-api.md) | -| REQ-008 | 슬롯 read-only 조회 HTTP API | Interface | Implemented | High | project maintainers | [req-008](./Interface/req-008_slot-readonly-query-api.md) | -| REQ-009 | 데몬/API/영속화 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-009](./Documentation/req-009_daemon-api-baseline-alignment.md) | -| REQ-010 | 데몬 설정 .env 파일 로딩 | Configuration | Implemented | Medium | project maintainers | [req-010](./Configuration/req-010_daemon-dotenv-loading.md) | -| REQ-011 | BrokerPort 추상화 + paper/stub async 더블 | Integration | Implemented | High | project maintainers | [req-011](./Integration/req-011_broker-port-and-paper-double.md) | -| REQ-012 | KIS Open API 브로커 어댑터 | Integration | Implemented | High | project maintainers | [req-012](./Integration/req-012_kis-broker-adapter.md) | -| REQ-013 | 주문 집행 루프 (claim·송신·체결·롤백) | Integration | Implemented | High | project maintainers | [req-013](./Integration/req-013_order-execution-loop.md) | -| REQ-014 | 주문 핸드오프 durability + reconciliation | Exception | Implemented | High | project maintainers | [req-014](./Exception/req-014_order-handoff-durability-reconciliation.md) | -| REQ-015 | 브로커/집행 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-015](./Documentation/req-015_broker-execution-baseline-alignment.md) | -| REQ-016 | KIS 인증 토큰 Redis 캐시 | Integration | Implemented | Medium | project maintainers | [req-016](./Integration/req-016_kis-token-redis-cache.md) | -| REQ-017 | LedgerPort 추상화 + portfolio-hub 어댑터 + stub | Integration | Implemented | High | project maintainers | [req-017](./Integration/req-017_ledger-port-and-portfolio-hub-adapter.md) | -| REQ-018 | 체결→원장 기록 (집행 루프 fill hook) | Integration | Implemented | High | project maintainers | [req-018](./Integration/req-018_fill-to-ledger-recording.md) | -| REQ-019 | ledger outbox durability + correlation_id 멱등 | Exception | Implemented | High | project maintainers | [req-019](./Exception/req-019_ledger-outbox-durability-idempotency.md) | -| REQ-020 | ledger 레이어 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-020](./Documentation/req-020_ledger-baseline-alignment.md) | -| REQ-021 | messaging 토폴로지 + 메시지 계약 매핑 | Integration | Implemented | High | project maintainers | [req-021](./Integration/req-021_messaging-topology-and-contract-mapping.md) | -| REQ-022 | messaging transport 포트 + RabbitMQ 어댑터 + stub + sync+thread daemon | Integration | Implemented | High | project maintainers | [req-022](./Integration/req-022_messaging-transport-port-and-rabbitmq-adapter.md) | -| REQ-023 | messaging delivery 신뢰성 (effectively-once + durable + DLQ + publisher confirms) | Exception | Implemented | High | project maintainers | [req-023](./Exception/req-023_messaging-delivery-reliability.md) | -| REQ-024 | RabbitMQ 가용성 degrade 정책 (버퍼링 금지 + 보수적 중단 + core 안전 매도 독립) | Non-Functional | Implemented | High | project maintainers | [req-024](./Non-Functional/req-024_rabbitmq-availability-degrade-policy.md) | -| REQ-025 | HTTP `/v1` API 완전 제거 (healthz 포함, HTTP 표면 0) | Interface | Implemented | High | project maintainers | [req-025](./Interface/req-025_http-api-complete-removal.md) | -| REQ-026 | messaging 레이어 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-026](./Documentation/req-026_messaging-baseline-alignment.md) | -| REQ-027 | portfolio-hub read 포트 + 어댑터 + stub | Integration | Implemented | High | project maintainers | [req-027](./Integration/req-027_portfolio-hub-read-port-and-adapter.md) | -| REQ-028 | 종목 스냅샷 read 메시지 계약 + envelope + degraded 신호 | Interface | Deprecated | High | project maintainers | [req-028](./Interface/req-028_snapshot-read-message-contract-and-envelope.md) | -| REQ-029 | 자산 선별(slot overlay) + view 모드 + strategy_key 필터 | Interface | Implemented | High | project maintainers | [req-029](./Interface/req-029_asset-selection-slot-overlay-view-and-strategy-key.md) | -| REQ-030 | 일일 스냅샷 스케줄러 + 옵션 D 현금/포지션 갱신 | Integration | Implemented | High | project maintainers | [req-030](./Integration/req-030_daily-snapshot-scheduler-and-option-d-cash-position.md) | -| REQ-031 | 포지션 정합 검증 + warning 마스킹 | Exception | Implemented | High | project maintainers | [req-031](./Exception/req-031_position-reconciliation-and-warning.md) | -| REQ-032 | 런타임 pause/resume 제어 (+운영자 CLI 표면) | Interface | Implemented | Medium | project maintainers | [req-032](./Interface/req-032_runtime-pause-resume-control.md) | -| REQ-033 | 스냅샷 read 레이어 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-033](./Documentation/req-033_snapshot-read-baseline-alignment.md) | -| REQ-034 | per-slot 워치독 — snapshot RPC 기반 전략 모듈 활성 감지 | Interface | Implemented | High | project maintainers | [req-034](./Interface/req-034_per-slot-watchdog.md) | -| REQ-035 | 슬롯 가격 임계값 도메인 필드 + DB 마이그레이션 | Data | Implemented | High | project maintainers | [req-035](./Data/req-035_slot-price-threshold-fields-and-migration.md) | -| REQ-036 | 자동 매도 내부 경로 — SlotService.sell + idempotency | Interface | Implemented | High | project maintainers | [req-036](./Interface/req-036_auto-sell-internal-path.md) | -| REQ-037 | forced_close_at 만기 자동 매도 루프 | Integration | Implemented | High | project maintainers | [req-037](./Integration/req-037_forced-close-at-safety-sell-loop.md) | -| REQ-038 | stop-loss·take-profit KIS 가격 polling + 트리거 | Integration | Implemented | High | project maintainers | [req-038](./Integration/req-038_stop-loss-take-profit-kis-polling.md) | -| REQ-039 | 장 마감 강제 청산 (TREX_MARKET_CLOSE_LIQUIDATION_TIME) | Integration | Implemented | High | project maintainers | [req-039](./Integration/req-039_market-close-liquidation.md) | -| REQ-040 | update_thresholds intent RPC 액션 | Interface | Implemented | High | project maintainers | [req-040](./Interface/req-040_update-thresholds-intent-rpc.md) | -| REQ-041 | safety-sell 환경 설정 — 5종 env var | Configuration | Implemented | High | project maintainers | [req-041](./Configuration/req-041_safety-sell-env-config.md) | -| REQ-042 | safety-sell baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-042](./Documentation/req-042_safety-sell-baseline-alignment.md) | -| REQ-043 | trex.control.req 제어 채널 + action dispatch | Interface | Implemented | High | project maintainers | [req-043](./Interface/req-043_control-channel-action-dispatch.md) | -| REQ-044 | kill-switch day-scoped 제외 store + 마이그레이션 | Data | Implemented | High | project maintainers | [req-044](./Data/req-044_killswitch-exclusion-store-and-migration.md) | -| REQ-045 | kill-switch 실행 — 범위별 holding 즉시 청산 + 이력 | Interface | Implemented | High | project maintainers | [req-045](./Interface/req-045_kill-switch-execution.md) | -| REQ-046 | kill-switch 당일 제외 masking + 자동해제 + resume | Interface | Implemented | High | project maintainers | [req-046](./Interface/req-046_killswitch-day-exclusion-masking-and-resume.md) | -| REQ-047 | kill-switch baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-047](./Documentation/req-047_killswitch-baseline-alignment.md) | -| REQ-048 | per-instance TOML 설정 로더 + --config 필수 | Configuration | Implemented | High | project maintainers | [req-048](./Configuration/req-048_per-instance-toml-config.md) | -| REQ-049 | instance_key 기반 동적 큐명 + default 하위호환 | Interface | Implemented | High | project maintainers | [req-049](./Interface/req-049_instance-key-dynamic-queue-naming.md) | -| REQ-050 | trex.direct(topic) + trex.broadcast(fanout) exchange + 바인딩 | Integration | Implemented | High | project maintainers | [req-050](./Integration/req-050_direct-broadcast-exchanges-and-binding.md) | -| REQ-051 | multi-instance topology baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-051](./Documentation/req-051_multi-instance-baseline-alignment.md) | -| REQ-052 | safety-sell per-slot 재시도 쿨다운 + 영속 거부 경고 | Integration | Implemented | High | project maintainers | [req-052](./Integration/req-052_safety-sell-per-slot-cooldown.md) | -| REQ-053 | safety-sell 재시도 쿨다운 env 설정 2종 | Configuration | Implemented | Medium | project maintainers | [req-053](./Configuration/req-053_safety-sell-retry-cooldown-env.md) | -| REQ-054 | safety-sell 쿨다운 runtime-flows 정합 | Documentation | Implemented | Low | project maintainers | [req-054](./Documentation/req-054_safety-sell-cooldown-baseline-alignment.md) | -| REQ-055 | closed 슬롯 ↔ ledger_outbox 누락 운영 점검 쿼리 | Exception | Implemented | Medium | project maintainers | [req-055](./Exception/req-055_ledger-gap-ops-check.md) | -| REQ-056 | 자산 영구 슬롯 스키마 재설계 + 마이그레이션 | Data | Implemented | High | project maintainers | [req-056](./Data/req-056_asset-permanent-slot-schema.md) | -| REQ-057 | 자산 영구 슬롯 상태머신 재정의 + 인스턴스 스코프 advisory lock | Interface | Implemented | High | project maintainers | [req-057](./Interface/req-057_asset-slot-state-machine.md) | -| REQ-058 | universe 기반 슬롯 lifecycle reconcile (생성/soft-retire/tombstone) | Integration | Implemented | High | project maintainers | [req-058](./Integration/req-058_universe-slot-reconcile-lifecycle.md) | -| REQ-059 | 활성 capacity 제어 + 신호 기반 admission (FCFS·M캡·무치환·over-cap) | Interface | Implemented | High | project maintainers | [req-059](./Interface/req-059_active-capacity-admission.md) | -| REQ-060 | 동시 활성 최대치(max_active_slots) per-instance TOML 설정 | Configuration | Implemented | High | project maintainers | [req-060](./Configuration/req-060_max-active-slots-config.md) | -| REQ-061 | intent 계약 자산(portfolio_id) 키 통일 + 중복/무효 규칙 | Interface | Implemented | High | project maintainers | [req-061](./Interface/req-061_asset-keyed-intent-contract.md) | -| REQ-062 | 수량 hub 정본+로컬 미러 + holding↔broker/hub 불일치 해소 | Integration | Implemented | High | project maintainers | [req-062](./Integration/req-062_quantity-hub-authority-and-divergence.md) | -| REQ-063 | 운영자 표면 단일화 — slots = 자산 포지션 단일 뷰 | Interface | Implemented | Medium | project maintainers | [req-063](./Interface/req-063_unified-operator-slot-view.md) | -| REQ-064 | 풀 모델 → 자산 중심 DB 재초기화 (구 cutover) | Migration | Implemented | High | project maintainers | [req-064](./Migration/req-064_pool-to-permanent-slot-cutover.md) | -| REQ-065 | 자산 중심 모델 baseline 문서 정합 + 풀 모델 폐기 명시 | Documentation | Implemented | Medium | project maintainers | [req-065](./Documentation/req-065_asset-centric-baseline-alignment.md) | -| REQ-066 | 보유 자산 포지션 사이징 — 증액·부분 감액 (수량/avg_price 재계산) | Interface | Implemented | High | project maintainers | [req-066](./Interface/req-066_position-sizing-resize.md) | -| REQ-067 | 포지션 사이징 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-067](./Documentation/req-067_position-sizing-baseline-alignment.md) | -| REQ-068 | intent 실행 정책 계약 — timeout_sec + slippage_pct | Interface | Implemented | High | project maintainers | [req-068](./Interface/req-068_execution-policy-intent-contract.md) | -| REQ-069 | 주문 timeout 집행 + 취소-체결 race 처리 | Integration | Implemented | High | project maintainers | [req-069](./Integration/req-069_order-timeout-execution.md) | -| REQ-070 | slippage 보호 지정가 집행 | Integration | Implemented | High | project maintainers | [req-070](./Integration/req-070_slippage-limit-price-execution.md) | -| REQ-071 | 운영자 강제 취소 백스톱 (trex-core cancel) | Interface | Implemented | Medium | project maintainers | [req-071](./Interface/req-071_operator-cancel-pending-backstop.md) | -| REQ-072 | 실행 정책 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-072](./Documentation/req-072_execution-policy-baseline-alignment.md) | -| REQ-073 | 전략 read 표면 — trex.slots.req RPC 계약 (filter/spy, snapshot.req 즉시 교체) | Interface | Implemented | High | project maintainers | [req-073](./Interface/req-073_slots-req-rpc-contract.md) | -| REQ-074 | 슬롯 단일 행 스키마 — 자산·워치독·overlay 흡수 + 필드별 정본 라벨 | Data | Implemented | High | project maintainers | [req-074](./Data/req-074_merged-slot-row-schema.md) | -| REQ-075 | slots read 표면 baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-075](./Documentation/req-075_slots-read-surface-baseline-alignment.md) | -| REQ-076 | watchdog timeout per-instance TOML 설정 | Configuration | Implemented | High | project maintainers | [req-076](./Configuration/req-076_watchdog-timeout-per-instance-toml.md) | -| REQ-077 | 중장기 인스턴스 운영 프로파일 — 표준 TOML 레시피 + 기동 검증 | Configuration | Implemented | High | project maintainers | [req-077](./Configuration/req-077_long-term-instance-operation-profile.md) | -| REQ-078 | 중장기 운영 baseline 문서 정합 — 보호 규칙 경계 + forced_close_at 미사용 | Documentation | Implemented | Medium | project maintainers | [req-078](./Documentation/req-078_long-term-operation-baseline-alignment.md) | -| REQ-079 | Slot strategy-state 저장소 — slot_strategy_state 테이블 + StrategyStateStore + occupancy_seq·version CAS | Data | Implemented | High | project maintainers | [req-079](./Data/req-079_slot-strategy-state-store.md) | -| REQ-080 | trex.slots.strategy_state.set write RPC 계약 — occupancy_seq·version CAS | Interface | Implemented | High | project maintainers | [req-080](./Interface/req-080_strategy-state-set-rpc.md) | -| REQ-081 | 슬롯 occupancy_seq 정합성 가드 + close 시 strategy_state 삭제 불변식 | Data | Implemented | High | project maintainers | [req-081](./Data/req-081_slot-occupancy-seq-guard-and-close-delete.md) | -| REQ-082 | strategy-state baseline 문서 정합 — read/write/삭제 흐름 + occupancy_seq 가드 | Documentation | Implemented | Medium | project maintainers | [req-082](./Documentation/req-082_strategy-state-baseline-alignment.md) | -| REQ-083 | KRX 호가단위(tick) 산출 유틸 — 가격→tick + 배수 내림/올림 | Integration | Implemented | High | project maintainers | [req-083](./Integration/req-083_krx-tick-size-utility.md) | -| REQ-084 | 구조화 로깅 기반 — ISO8601(KST)·레벨·Loki JSON·correlation id | Non-Functional | Implemented | High | project maintainers | [req-084](./Non-Functional/req-084_structured-logging-foundation.md) | -| REQ-085 | 주문 결과 로깅 — submit/체결/거부 일관 tailable 로그 | Integration | Implemented | High | project maintainers | [req-085](./Integration/req-085_order-result-logging.md) | -| REQ-086 | 청산 reason+actor 동반 노출 — 안전망/전략 구분 가시화 | Interface | Implemented | Medium | project maintainers | [req-086](./Interface/req-086_liquidation-reason-actor-exposure.md) | -| REQ-087 | 주문 거부 ERROR 가시성 — 조용한 롤백 제거 | Non-Functional | Implemented | High | project maintainers | [req-087](./Non-Functional/req-087_order-rejection-error-visibility.md) | -| REQ-088 | 운영 조회 CLI — 보유·오늘 체결·실현 PnL·대기 주문 | Interface | Implemented | High | project maintainers | [req-088](./Interface/req-088_operator-pnl-query-cli.md) | -| REQ-089 | 운영자 런북 — docs/operations.md 신설 | Documentation | Implemented | Medium | project maintainers | [req-089](./Documentation/req-089_operator-runbook.md) | -| REQ-090 | 관측성 baseline 문서 정합 — 로깅/관측성·PnL 조회 흐름 | Documentation | Implemented | Medium | project maintainers | [req-090](./Documentation/req-090_observability-baseline-alignment.md) | -| REQ-091 | 이벤트 발행 재시도 + 강제 재연결 (idle 후 첫 발행 무유실) | Exception | Implemented | High | project maintainers | [req-091](./Exception/req-091_publish-retry-and-force-reconnect.md) | -| REQ-092 | 발행 전용 연결 idle keepalive (서버 측 heartbeat 사망 예방) | Exception | Implemented | High | project maintainers | [req-092](./Exception/req-092_publish-idle-keepalive.md) | -| REQ-093 | DLQ 가시성/경보 + emit 실패 경보 (trex.events.dlq 관측성) | Exception | Implemented | High | project maintainers | [req-093](./Exception/req-093_dlq-visibility-and-emit-error-alert.md) | -| REQ-094 | 이벤트 발행 신뢰성 baseline 문서 정합 (runtime-flows / project-structure / operations 갱신) | Documentation | Implemented | Medium | project maintainers | [req-094](./Documentation/req-094_event-publisher-reliability-baseline-alignment.md) | -| REQ-095 | 체결가 0/None 지정가 백필 가드 (슬롯 평단 + 원장 price) | Exception | Implemented | High | trex-core-dev | [req-095](./Exception/req-095_fill-price-zero-backfill-guard.md) | -| REQ-096 | 체결가 백필·0가격 통과 WARN 로깅 | Exception | Implemented | High | trex-core-dev | [req-096](./Exception/req-096_fill-price-backfill-warn-logging.md) | -| REQ-097 | 알람(alert) 능력 검토 스파이크 산출물 | Documentation | Implemented | Medium | trex-core-dev | [req-097](./Documentation/req-097_alert-capability-spike.md) | -| REQ-098 | 알람 envelope wire 계약 정의 | Interface | Implemented | High | trex-core-dev | [req-098](./Interface/req-098_alarm-envelope-wire-contract.md) | -| REQ-099 | trex.alerts AMQP exchange 토폴로지 + 가산·격리 불변식 | Integration | Implemented | High | trex-core-dev | [req-099](./Integration/req-099_alarm-exchange-topology.md) | -| REQ-100 | trex-core 알람 emitter — CRITICAL code 발신 배선 | Integration | Implemented | High | trex-core-dev | [req-100](./Integration/req-100_core-alarm-emitter.md) | -| REQ-101 | 알람 발신 흐름 baseline 문서 정합 | Documentation | Implemented | Medium | trex-core-dev | [req-101](./Documentation/req-101_alarm-baseline-alignment.md) | -| REQ-102 | reconcile-warning durable store — slot_id/pid 키 별도 테이블 | Data | Implemented | High | trex-core-dev | [req-102](./Data/req-102_reconcile-warning-durable-store.md) | -| REQ-103 | 차단형 reconcile — B/C 불일치 슬롯 reconcile-warning 전이 | Integration | Implemented | High | trex-core-dev | [req-103](./Integration/req-103_blocking-reconcile-b-c-cases.md) | -| REQ-104 | 전략 차단 게이트 — pause ∨ reconcile-warning OR 게이트 (안전망 예외) | Interface | Implemented | High | trex-core-dev | [req-104](./Interface/req-104_reconcile-warning-strategy-gate.md) | -| REQ-105 | trex-core sync 명령 — 슬롯 수량을 브로커에 맞춰 갱신 + warning 해제 (INACTIVE→HOLDING OQ-A) | Interface | Implemented | High | trex-core-dev | [req-105](./Interface/req-105_sync-cli-command.md) | -| REQ-106 | trex-core verify 명령 — C 불일치 슬롯 재대조 + warning 해제 (C 전용 OQ-C) | Interface | Implemented | High | trex-core-dev | [req-106](./Interface/req-106_verify-cli-command.md) | -| REQ-107 | 차단/해제 전이 식별 로깅 + 고영향 불일치 알람 | Exception | Implemented | High | trex-core-dev | [req-107](./Exception/req-107_blocking-reconcile-logging-and-alarm.md) | -| REQ-108 | 차단형 reconcile 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습 | Non-Functional | Implemented | High | trex-core-dev | [req-108](./Non-Functional/req-108_reconcile-blocking-regression-and-nfr.md) | -| REQ-109 | portfolio-hub /pnl/realized read 포트 메서드 + HTTP 어댑터 | Integration | Implemented | High | trex-core-dev | [req-109](./Integration/req-109_portfolio-hub-realized-pnl-read-adapter.md) | -| REQ-110 | 운영 리포트 WAC 실현손익 통합 — 전 인스턴스 공통 | Interface | Implemented | High | trex-core-dev | [req-110](./Interface/req-110_operator-report-wac-realized-pnl.md) | -| REQ-111 | 운영 리포트 — portfolio-hub 미가용 시 실현손익 섹션 에러 degrade | Exception | Implemented | High | trex-core-dev | [req-111](./Exception/req-111_operator-report-realized-pnl-degrade.md) | -| REQ-112 | portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터 | Integration | Implemented | High | trex-core-dev | [req-112](./Integration/req-112_portfolio-hub-broker-credential-read-adapter.md) | -| REQ-113 | KISConfig 소스 우선순위 — portfolio-hub fetch 1차 + env break-glass + opt-in | Configuration | Approved | High | trex-core-dev | [req-113](./Configuration/req-113_kisconfig-fetch-source-priority-and-break-glass.md) | -| REQ-114 | 그날-시작 로드 게이트 — fail-closed 신규 진입 차단 + 알람 | Integration | Implemented | High | trex-core-dev | [req-114](./Integration/req-114_daystart-load-gate-fail-closed-entry.md) | -| REQ-115 | 그날-시작 장중 무중단 보장 — 그날 고정 인증정보 캐시 + portfolio-hub 장애 비중단 | Non-Functional | Approved | High | trex-core-dev | [req-115](./Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md) | -| REQ-116 | `trex.slots.req` 전략 필드 노출 + `strategy` 요청 필터 (additive) | Interface | Implemented | High | trex-core-dev | [req-116](./Interface/req-116_slots-req-strategy-field-and-filter.md) | -| REQ-117 | OrderRequest venue additive 필드 (KRX/NXT 거래소구분 포트 DTO) | Integration | Implemented | High | trex-core-dev | [req-117](./Integration/req-117_order-request-venue-field.md) | -| REQ-118 | KIS 브로커 어댑터 venue 파라미터화 — EXCG_ID_DVSN_CD 전송 + NXT 지원 | Integration | Implemented | High | trex-core-dev | [req-118](./Integration/req-118_kis-broker-venue-parameterization.md) | -| REQ-119 | fetched credential 공통 broker provider와 operator CLI truth 정합 | Integration | Implemented | High | trex-core-dev | [req-119](./Integration/req-119_fetched-credential-broker-provider-cli-parity.md) | -| REQ-120 | KIS balance 인증 실패 fail-loud 및 broker/token lifecycle 정책 | Exception | Implemented | High | trex-core-dev | [req-120](./Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md) | -| REQ-121 | managed non-holding divergence 분류와 slot 존재 조회 계약 | Data | Implemented | High | trex-core-dev | [req-121](./Data/req-121_managed-non-holding-divergence-classification-query.md) | -| REQ-122 | operator broker truth hotfix baseline 문서 정합 | Documentation | Implemented | High | trex-core-dev | [req-122](./Documentation/req-122_operator-broker-truth-hotfix-baseline-alignment.md) | -| REQ-123 | cost-basis canonical parse 및 WAC unavailable 안전 sync seed | Integration | Approved | High | trex-core-dev | [req-123](./Integration/req-123_cost-basis-canonical-parse-and-safe-sync-seeding.md) | -| REQ-124 | intent venue persistence and execution | Integration | Implemented | High | trex-core-dev | [req-124](./Integration/req-124_intent-venue-persistence-and-execution.md) | -| REQ-127 | 공통 계좌 자본배분 순수 정책 | Interface | Approved | High | trex-core-dev | [req-127](./Interface/req-127_shared-account-capital-allocation-policy.md) | - -## Recent Change Log Summary - -- 2026-07-29 :: REQ-118 CHG-20260729-02 **Approved → Implemented**: - BAT-063 release-candidate, KIS broker 59 passed/1 skipped, Core 전체 - 922 passed/51 skipped, 공용 가상 주문 인증 1~7단계 PASS. 미체결·포지션· - 중복 주문 0, 원장 일치. -- 2026-07-29 :: REQ-127 **승인(Approved, confirm-req)**: 사용자 승인 범위, 순수 Core 정책·어댑터 입출력·기존 주문/원장 불변 경계, 10개 측정 가능한 수락 기준과 비운영 조건을 확인해 단일 배치 인계를 승인. -- 2026-07-29 :: REQ-127 **신규 생성(Proposed, draft-req, dcy-042)**: 메가·탑50 공통 계좌 자본배분 순수 정책, 고가 후보 유동 제외, 최악 활성 슬롯 현금 불변식, Core·어댑터 책임 경계와 완전 일치·가상 원장 검증을 요구사항으로 고정. 실전 배포·주문 게이트·원장 변경 제외. -- 2026-07-27 :: REQ-124 Implemented: strategy intent venue를 pending slot과 broker submit/cancel까지 additive 전달. 전체 회귀 837 passed, 47 skipped. 배포·실주문 제외. -- 2026-07-16 :: REQ-123 **confirm-req 승인(Approved, lead confirm)**: dcy-038 OQ-1~3 완료 기준으로 제약(scope 제외 항목: API/schema 변경/credential/config 변경/live sync/배포), OQ-2/OQ-3 결정 반영, fail-closed WAC unavail no-write 정책, canonical parsing 우선순위/legacy fallback 규칙 및 dry-run diagnostics가 일치함을 확인해 `REQ-123`을 Approved 전환.