From 36f2aa76b3e3cbdd2ce9391a6328e5c2fc5c96c1 Mon Sep 17 00:00:00 2001 From: serversdown Date: Sun, 28 Jun 2026 23:59:14 +0000 Subject: [PATCH] feat: poker operation contract + tool-spec conformance test Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01G796GsLCvJQKVN7hwV2cDx --- lyra/poker_contract.py | 19 +++++++++++++++++++ tests/test_poker_contract.py | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 lyra/poker_contract.py create mode 100644 tests/test_poker_contract.py diff --git a/lyra/poker_contract.py b/lyra/poker_contract.py new file mode 100644 index 0000000..eb938d1 --- /dev/null +++ b/lyra/poker_contract.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +# Single source of truth for poker logging operations. The REST API, Lyra's LLM +# tool specs, the human UI, and (later) an MCP wrapper all derive from this. +# `required` MUST match the `required` list in the matching tools.py spec. +# `rest` PATH MUST match the FastAPI route template verbatim. +CONTRACT_VERSION = 1 + +OPERATIONS: dict[str, dict] = { + "start_session": {"required": (), "llm_tool": "start_session", "rest": ("POST", "/session")}, + "update_session": {"required": (), "llm_tool": "update_session", "rest": ("PATCH", "/session/{session_id}")}, + "end_session": {"required": ("cash_out",), "llm_tool": "end_session", "rest": None}, + "log_stack": {"required": ("amount",), "llm_tool": "log_stack", "rest": ("POST", "/session/stack")}, + "add_buyin": {"required": ("amount",), "llm_tool": "add_buyin", "rest": ("POST", "/session/buyin")}, + "log_hand": {"required": (), "llm_tool": "log_hand", "rest": ("POST", "/session/hand")}, + "update_hand": {"required": ("id",), "llm_tool": None, "rest": ("PATCH", "/hand/{hand_id}")}, + "add_read": {"required": ("note",), "llm_tool": "add_read", "rest": ("POST", "/session/read")}, + "update_player": {"required": ("id",), "llm_tool": None, "rest": ("PATCH", "/player/{player_id}")}, +} diff --git a/tests/test_poker_contract.py b/tests/test_poker_contract.py new file mode 100644 index 0000000..f0521c7 --- /dev/null +++ b/tests/test_poker_contract.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from lyra import tools +from lyra.poker_contract import OPERATIONS + + +def test_llm_tool_required_args_match_contract(): + for op, decl in OPERATIONS.items(): + name = decl["llm_tool"] + if not name: + continue + spec = tools.TOOLS[name]["spec"] + required = set(spec["function"]["parameters"]["required"]) + assert required == set(decl["required"]), ( + f"{op}: tools spec required {required} != contract {set(decl['required'])}" + )