fix strategy state persistence corrections

This commit is contained in:
Hermes Agent
2026-08-29 16:11:02 +09:00
parent 4699de61cc
commit 3854aabd0d
11 changed files with 190 additions and 85 deletions
+34 -34
View File
@@ -716,53 +716,53 @@ class PgStrategyStateStore:
data: str,
slot_active: bool,
) -> SetResult:
if not slot_active:
return SetResult(ok=False, reason="slot_inactive")
if len(data.encode("utf-8")) > _STATE_CAP_BYTES:
return SetResult(ok=False, reason="state_too_large")
now = datetime.now(tz=timezone.utc)
with self._psycopg.connect(self._dsn, autocommit=False) as conn:
try:
with conn.cursor() as cur:
# Lock the lifecycle row before changing state. A close UPDATE
# and this write therefore have one serial order: a writer that
# follows a close observes INACTIVE and cannot recreate state.
cur.execute(
"INSERT INTO slot_strategy_state (slot_id, data) "
"SELECT %s, jsonb_build_object(%s, jsonb_build_object("
"'version', 1, 'data', %s, 'updated_at', %s)) WHERE %s = 0 "
"ON CONFLICT (slot_id) DO UPDATE SET data = jsonb_set("
"slot_strategy_state.data, ARRAY[%s], jsonb_build_object("
"'version', %s + 1, 'data', %s, 'updated_at', %s), true) "
"WHERE COALESCE((slot_strategy_state.data -> %s ->> 'version')::bigint, 0) = %s "
"RETURNING data -> %s ->> 'version'",
(
slot_id,
strategy_instance_id,
data,
now,
expected_version,
strategy_instance_id,
expected_version,
data,
now,
strategy_instance_id,
expected_version,
strategy_instance_id,
),
"SELECT status FROM daily_slot WHERE slot_id = %s::uuid FOR UPDATE",
(slot_id,),
)
row = cur.fetchone()
if row is None:
cur.execute(
"SELECT COALESCE((data -> %s ->> 'version')::bigint, 0) "
"FROM slot_strategy_state WHERE slot_id = %s",
(strategy_instance_id, slot_id),
)
conflict = cur.fetchone()
slot = cur.fetchone()
if slot is None or slot[0] not in {"pending_buy", "holding", "pending_sell"}:
return SetResult(ok=False, reason="slot_inactive")
cur.execute(
"SELECT data FROM slot_strategy_state WHERE slot_id = %s::uuid",
(slot_id,),
)
state_row = cur.fetchone()
state_map = state_row[0] if state_row is not None else {}
entry = state_map.get(strategy_instance_id)
version = int(entry["version"]) if entry is not None else 0
if expected_version != version:
return SetResult(
ok=False,
reason="version_conflict",
version=int(conflict[0]) if conflict is not None else 0,
version=version,
)
entry_json = "jsonb_build_object('version', %s::bigint, " \
"'data', %s::text, 'updated_at', %s::timestamptz)"
if state_row is None:
cur.execute(
"INSERT INTO slot_strategy_state (slot_id, data) VALUES ("
"%s::uuid, jsonb_build_object(%s::text, " + entry_json + "))",
(slot_id, strategy_instance_id, version + 1, data, now),
)
else:
cur.execute(
"UPDATE slot_strategy_state SET data = jsonb_set(data, "
"ARRAY[%s::text]::text[], " + entry_json + ", true) "
"WHERE slot_id = %s::uuid",
(strategy_instance_id, version + 1, data, now, slot_id),
)
conn.commit()
return SetResult(ok=True, version=int(row[0]))
return SetResult(ok=True, version=version + 1)
except BaseException:
conn.rollback()
raise
@@ -12,7 +12,7 @@ BEGIN
DROP TABLE slot_strategy_state;
END IF;
END $$;
CREATE TABLE slot_strategy_state (
CREATE TABLE IF NOT EXISTS slot_strategy_state (
slot_id UUID PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}'::jsonb
);