diff --git a/src/provably/handoff/_bootstrap.py b/src/provably/handoff/_bootstrap.py index a4daa44..007d7f1 100644 --- a/src/provably/handoff/_bootstrap.py +++ b/src/provably/handoff/_bootstrap.py @@ -94,7 +94,7 @@ def _onboard_database(current_org: str, middleware_id: str, postgres_url: str) - # padding step ran above to ensure provably_intercepts exists, and the backend's # schema introspection will pick it up so discover_intercepts_table() can find it. _log.info("database_already_exists_reusing") - database_id = resolve_existing_database_id(current_org, middleware_id, db_name) + database_id = resolve_existing_database_id(current_org, middleware_id, db_name, body["uri"]) if not database_id: log_failed_response(resp) raise RuntimeError("Database exists but could not resolve existing database_id") diff --git a/src/provably/handoff/_discovery.py b/src/provably/handoff/_discovery.py index c5df118..e76a72d 100644 --- a/src/provably/handoff/_discovery.py +++ b/src/provably/handoff/_discovery.py @@ -27,7 +27,8 @@ def discover_intercepts_table(org_id: str, middleware_id: str, database_id: str) return _node_to_bundle(node) -def resolve_existing_database_id(org_id: str, middleware_id: str, db_name: str) -> str: +def resolve_existing_database_id(org_id: str, middleware_id: str, db_name: str, host: str = "") -> str: + candidates: list[str] = [] for path in ( f"/api/v1/organizations/{org_id}/middlewares/{middleware_id}/databases", f"/api/v1/organizations/{org_id}/databases", @@ -43,8 +44,18 @@ def resolve_existing_database_id(org_id: str, middleware_id: str, db_name: str) db_id = extract_id(item, ["id", "database_id"]) except ValueError: continue - if db_id: - return db_id + if db_id and db_id not in candidates: + candidates.append(db_id) + # Same name can map to several registrations on different hosts; pick the one whose + # stored host matches `host` so we don't bind to a different physical DB. + if host: + matched = [db_id for db_id in candidates if _database_host(org_id, middleware_id, db_id) == host] + if matched: + return matched[0] + if len(candidates) > 1: + return "" # ambiguous and none matched the host — refuse to guess + if candidates: + return candidates[0] try: data = get_json(f"/api/v1/organizations/{org_id}/data") return find_first_id(data, ("database_id",)) @@ -52,6 +63,15 @@ def resolve_existing_database_id(org_id: str, middleware_id: str, db_name: str) return "" +def _database_host(org_id: str, middleware_id: str, database_id: str) -> str: + """Stored connection host for a database (the list endpoint omits it; the detail one has it).""" + try: + detail = get_json(f"/api/v1/organizations/{org_id}/middlewares/{middleware_id}/databases/{database_id}") + except Exception: # noqa: BLE001 + return "" + return str(detail.get("uri") or "").strip() + + def resolve_existing_collection_id(org_id: str, middleware_id: str, database_id: str, table_id: str) -> str: try: payload = get_json(f"/api/v1/organizations/{org_id}/collections")