feat: register placements with engine and persist publisher_id - #23
feat: register placements with engine and persist publisher_id#23trial-gravity wants to merge 5 commits into
Conversation
Add publisher_id to the CREATE TABLE, migration SQL, and INSERT statement. Switch ON CONFLICT from DO NOTHING to DO UPDATE so repeat calls backfill publisher_id and refresh placement type. Made-with: Cursor
Call POST /api/v1/placements on the engine before writing to the local DB so the returned publisher_id is captured. Falls back to empty string if the engine is unreachable — backfilled on next call via the ON CONFLICT DO UPDATE in the prior commit. Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Upsert overwrites valid publisher_id with empty string
- The upsert now preserves the existing placements.publisher_id unless the incoming EXCLUDED.publisher_id is non-empty, preventing valid IDs from being overwritten by empty strings.
Or push these changes by commenting:
@cursor push a53a332a4a
Preview (a53a332a4a)
diff --git a/mcp-server/data/db.py b/mcp-server/data/db.py
--- a/mcp-server/data/db.py
+++ b/mcp-server/data/db.py
@@ -35,7 +35,10 @@
INSERT INTO placements (placement_id, placement, style, type, framework, platform, performance, publisher_key_hash, publisher_id, created_at)
VALUES (%(placement_id)s, %(placement)s, %(style)s, %(type)s, %(framework)s, %(platform)s, %(performance)s, %(publisher_key_hash)s, %(publisher_id)s, %(created_at)s)
ON CONFLICT (placement_id) DO UPDATE
-SET publisher_id = EXCLUDED.publisher_id,
+SET publisher_id = CASE
+ WHEN EXCLUDED.publisher_id <> '' THEN EXCLUDED.publisher_id
+ ELSE placements.publisher_id
+ END,
placement = EXCLUDED.placement
"""When the engine is unreachable or no api_key is provided, publisher_id resolves to "". The unconditional SET overwrote previously stored values. Use a CASE expression to preserve the existing publisher_id when the incoming value is empty. Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Tests make unintended real HTTP calls to production
- I updated the autouse test fixture to patch
tools.generate_code._register_placementso tests with an API key no longer perform real outbound HTTP requests.
- I updated the autouse test fixture to patch
- ✅ Fixed: Strict 200 check silently drops 201 Created responses
- I changed
_register_placementto useresp.is_successso successful 2xx responses such as 201 now preserve and returnpublisher_id.
- I changed
Or push these changes by commenting:
@cursor push 3e13a537b1
Preview (3e13a537b1)
diff --git a/mcp-server/tests/test_tools.py b/mcp-server/tests/test_tools.py
--- a/mcp-server/tests/test_tools.py
+++ b/mcp-server/tests/test_tools.py
@@ -16,8 +16,10 @@
@pytest.fixture(autouse=True)
def _mock_db():
- """Prevent real DB writes during unit tests."""
- with patch("tools.generate_code.write_placement"):
+ """Prevent real DB writes and network registration during unit tests."""
+ with patch("tools.generate_code.write_placement"), patch(
+ "tools.generate_code._register_placement", return_value=""
+ ):
yield
diff --git a/mcp-server/tools/generate_code.py b/mcp-server/tools/generate_code.py
--- a/mcp-server/tools/generate_code.py
+++ b/mcp-server/tools/generate_code.py
@@ -27,7 +27,7 @@
json={"placement_id": placement_id, "placement": placement},
timeout=5.0,
)
- if resp.status_code == 200:
+ if resp.is_success:
return resp.json().get("publisher_id", "")
except Exception:
logger.debug("Engine placement registration failed", exc_info=True)| timeout=5.0, | ||
| ) | ||
| if resp.status_code == 200: | ||
| return resp.json().get("publisher_id", "") |
There was a problem hiding this comment.
Strict 200 check silently drops 201 Created responses
Low Severity
_register_placement only extracts publisher_id when resp.status_code == 200, but this is a POST endpoint that registers a new resource. The HTTP-standard response for resource creation is 201 Created. If the engine returns 201, the publisher_id is silently discarded and stored as "". This contrasts with validate_api_key in data/auth.py, which checks for known failure codes instead, making it resilient to any successful status code.
psycopg3's execute() only runs the first statement when given multi-statement SQL. Split _MIGRATE_SQL into individual ALTER TABLE calls so the publisher_id column migration actually runs. Made-with: Cursor
The autouse _mock_db fixture only patched write_placement, so any test passing a truthy api_key triggered a real httpx.post to the engine with a 5-second timeout — making the suite slow and flaky in CI/offline. Made-with: Cursor



Summary
generate_codeis called with a valid API key, the MCP server now POSTs to the engine'sPOST /api/v1/placementsendpoint to register the placement, then stores the returnedpublisher_idin the local PostgreSQL alongside the existing placement data.publisher_idcolumn to the localplacementstable with anALTER TABLEmigration for existing databases.ON CONFLICT DO NOTHINGtoON CONFLICT DO UPDATEso repeat calls backfillpublisher_idand refreshplacementtype.Details
mcp-server/data/db.py: Schema change -- addspublisher_id TEXT DEFAULT ''to CREATE TABLE, migration SQL, and INSERT. Upsert now updatespublisher_idandplacementon conflict.mcp-server/tools/generate_code.py: Adds_register_placement()helper that calls the engine and extractspublisher_idfrom the response. Gracefully returns""on any failure (timeout, network error, non-200). Wired intogenerate_code()beforewrite_placement()so the publisher_id flows into the local DB row.Test plan
publisher_idvia direct curlwrite_placementcaptures correctpublisher_idpublisher_idis""and no error surfaces to the callerNote
Medium Risk
Adds an outbound HTTP call during
generate_codeand changes persistence semantics to an upsert with schema migration, which could affect runtime behavior on failures and existing DB rows.Overview
When
generate_codeis called with anapi_key, it now POSTs to the Gravity engine (/api/v1/placements) to register the placement and captures the returnedpublisher_id(falling back to""on any error/timeout).Local persistence is extended to store
publisher_id: theplacementstable gains a new column with anALTER TABLEmigration, and the write path switches from insert-only to an upsert that backfillspublisher_id(only overwriting when non-empty) and refreshesplacementon conflicts. Unit tests are updated to mock the new outbound registration call.Written by Cursor Bugbot for commit 5556182. This will update automatically on new commits. Configure here.