fix: close broker uncertainty audit gaps

This commit is contained in:
jordan
2026-07-29 17:53:39 +09:00
parent de1e3eb44c
commit 611805dcec
19 changed files with 236 additions and 42 deletions
+14 -13
View File
@@ -43,6 +43,16 @@ _SUBMISSION_LOOKUP_GRACE_SECONDS = 90.0
_SUPPORTED_ORDER_VENUES = frozenset({"AUTO", "KRX", "NXT", "SOR"})
def _balance_http_error_code(status: Optional[int]) -> str:
if status == 401:
return "authentication_failed"
if status == 403:
return "authorization_failed"
if status in {408, 425, 429} or (status is not None and status >= 500):
return "transport_failed"
return "broker_rejected"
def resolve_order_venue(venue: str, at: datetime) -> str:
"""Resolve AUTO to the venue that accepts domestic-stock orders at `at`.
@@ -161,7 +171,8 @@ class KISBroker:
def account_scope_hash(self) -> str:
"""Stable non-secret identity for account-scoped persisted state."""
cano, product = self._account()
return hashlib.sha256(f"{cano}|{product}".encode()).hexdigest()
material = f"{self._base_url}|{cano}|{product}|{self._app_key}"
return hashlib.sha256(material.encode()).hexdigest()
# --- credential hot-swap (dcy-036 / REQ-113 AC-2 / REQ-115 AC-1) ----------
def reinitialize(self, credential) -> None:
@@ -658,12 +669,7 @@ class KISBroker:
payload = resp.json()
except httpx.HTTPStatusError as exc:
status = exc.response.status_code if exc.response is not None else None
code = (
"authentication_failed"
if status == 401
else ("authorization_failed" if status == 403 else "transport_failed")
)
raise BrokerBalanceError(code) from None
raise BrokerBalanceError(_balance_http_error_code(status)) from None
except httpx.HTTPError:
raise BrokerBalanceError("transport_failed") from None
except (ValueError, TypeError):
@@ -721,12 +727,7 @@ class KISBroker:
raise
except httpx.HTTPStatusError as exc:
status = exc.response.status_code if exc.response is not None else None
code = (
"authentication_failed"
if status == 401
else ("authorization_failed" if status == 403 else "transport_failed")
)
raise BrokerBalanceError(code) from None
raise BrokerBalanceError(_balance_http_error_code(status)) from None
except httpx.HTTPError:
raise BrokerBalanceError("transport_failed") from None
except (KeyError, TypeError, ValueError):
+12 -2
View File
@@ -341,6 +341,8 @@ class PostgreSQLSlotRepository:
slot_id: UUID,
broker_order_id: str,
state: str,
*,
only_if_unset: bool = False,
) -> bool:
now = datetime.now(tz=timezone.utc)
with self._psycopg.connect(self._dsn, autocommit=False) as conn:
@@ -349,8 +351,16 @@ class PostgreSQLSlotRepository:
"UPDATE daily_slot SET cancel_state=%s, "
"cancel_requested_at=%s, updated_at=%s "
"WHERE slot_id=%s AND broker_order_id=%s "
"AND status IN ('pending_buy','pending_sell')",
(state, now, now, slot_id, broker_order_id),
"AND status IN ('pending_buy','pending_sell') "
"AND (%s = FALSE OR cancel_state IS NULL)",
(
state,
now,
now,
slot_id,
broker_order_id,
only_if_unset,
),
)
ok = cur.rowcount == 1
conn.commit()
@@ -8,4 +8,6 @@ ALTER TABLE daily_slot
ALTER TABLE daily_slot
ADD CONSTRAINT daily_slot_venue_check
CHECK (venue IN ('KRX', 'NXT', 'SOR'));
-- apply_migrations() replays every file on each boot. Keep this historical
-- migration compatible with AUTO rows introduced by migration 019.
CHECK (venue IN ('AUTO', 'KRX', 'NXT', 'SOR'));
@@ -1,5 +1,5 @@
-- dcy-041 / GitHub #45: account-scoped last healthy KIS snapshot.
-- account_hash is SHA-256(CANO|ACNT_PRDT_CD); no plaintext account identity,
-- account_hash is SHA-256(base_url|CANO|ACNT_PRDT_CD|app_key); no plaintext identity,
-- token, key, order response, or broker payload is persisted.
CREATE TABLE IF NOT EXISTS broker_account_snapshot (
account_hash TEXT PRIMARY KEY,