From cf21beac47f9e45c467e062af14582e16008381d Mon Sep 17 00:00:00 2001 From: Aleksander Sekowski Date: Sat, 25 Jul 2026 22:06:13 -0700 Subject: [PATCH] Derive default-catalog product ids deterministically (issue #34) build_static_product_catalog minted prod-{uuid4} ids per process, so any multi-worker deployment (the shipped Dockerfile runs uvicorn --workers 2) or a restart between GET /products and GET /products/{id} could 404 on an id the server itself had just returned. This is the surviving kernel of issue #34 observation 1; the CSV-mode side was fixed in v2.2.2. Derive ids with uuid5 over the config name instead: same prod-[0-9a-f]{8} shape, unique per product, identical in every process. Adds a regression test that rebuilds the catalog after a cache reset (the single-process proxy for a second worker) and asserts the id set is unchanged. --- src/ad_seller/services/catalog_service.py | 8 ++++++-- tests/unit/test_csv_catalog_coherence.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ad_seller/services/catalog_service.py b/src/ad_seller/services/catalog_service.py index 06a80c3..c6dd60d 100644 --- a/src/ad_seller/services/catalog_service.py +++ b/src/ad_seller/services/catalog_service.py @@ -392,11 +392,15 @@ def build_static_product_catalog() -> dict[str, Any]: """Build a fresh catalog dict from ``DEFAULT_PRODUCT_CONFIGS`` (uncached). Returns ``{"products": {product_id: ProductDefinition}, "inventory_types": [...]}`` - with newly generated product IDs. + with deterministic product IDs derived from each config's name, so every + process (and every uvicorn worker) serves the same ids. Random per-process + ids meant a list-then-get across two workers, or across a restart, could + 404 on an id the server itself had just returned (issue #34). """ products: dict[str, Any] = {} for cfg in DEFAULT_PRODUCT_CONFIGS: - product_def = product_from_config(cfg, f"prod-{uuid.uuid4().hex[:8]}") + stable = uuid.uuid5(uuid.NAMESPACE_URL, f"ad-seller-product:{cfg['name']}") + product_def = product_from_config(cfg, f"prod-{stable.hex[:8]}") products[product_def.product_id] = product_def inventory_types = sorted({p.inventory_type for p in products.values()}) diff --git a/tests/unit/test_csv_catalog_coherence.py b/tests/unit/test_csv_catalog_coherence.py index 3a5aa5b..53fed0c 100644 --- a/tests/unit/test_csv_catalog_coherence.py +++ b/tests/unit/test_csv_catalog_coherence.py @@ -215,6 +215,16 @@ def test_default_mode_ids_stay_uuid_shaped_and_stable(self, default_mode): second = catalog_service.get_static_product_catalog() assert list(first["products"].keys()) == list(second["products"].keys()) + def test_default_mode_ids_survive_cache_reset(self, default_mode): + # Proxy for multi-worker and restart behavior (issue #34): each uvicorn + # worker builds its own catalog cache, so ids must be deterministic + # across independent builds, not merely stable within one cache. + first = catalog_service.get_static_product_catalog() + catalog_service.reset_catalog_cache() + second = catalog_service.get_static_product_catalog() + assert list(first["products"].keys()) == list(second["products"].keys()) + assert len(set(first["products"])) == len(catalog_service.DEFAULT_PRODUCT_CONFIGS) + # ============================================================================= # API surface — CSV mode