fix(wix-manage): three Catalog V3 frictions in find-products and update-product - #927
fix(wix-manage): three Catalog V3 frictions in find-products and update-product#927danshw wants to merge 6 commits into
Conversation
Asked "which of my products cost less than $20?", an agent following this recipe pulls the catalog with Query Products and compares amounts in its own code. The recipe frames Search Products as text lookup only, and its Query Products filter table — which is complete — has no price row, so nothing points at the endpoint that can filter on price. Search Products filters on `actualPriceRange.minValue.amount` with `$lt`/`$gt`. Add that row to the endpoint-choice table and say so where the Query Products filter table ends, so the client-side scan is not the only path on offer. Also correct one `fields` enum constant in the same table: the price entry was `MIN_VARIANT_PRICE_INFO`, which the API does not accept. Covered by a new scenario, stores/find-products-under-price-catalog-v3.
|
Superseded — this described an earlier revision of this PR, before the Update Product fixes were folded in from a separate branch. See the eval comparison comment below for the current results. |
A first paired run showed the endpoint-choice row alone was not enough: the agent read the recipe and still paged the catalog and compared amounts in its own code, because the only Search Products example here was a text lookup. Give the example the filter form so the server-side path is copyable. The same example was also missing a level — free text goes in `search.search`, not `search.expression` — and neither endpoint's result envelope was stated anywhere, so agents fall back to a Catalog V1-shaped guess (`productResults.results`, `catalogItems`) and burn a round trip. Show the envelope once.
A prose clause competes weakly with a Catalog V1 prior; a visible example does not compete at all. Verified against the served Search Products and Query Products references, whose response shape is { "products": [...], "pagingMetadata": {...} }.
`variantsInfo.variants` replaces the array *and* each variant object —
it does not merge. The recipe said to pass the entire existing array and
to include each existing variant `id`, which an agent can satisfy while
still rebuilding each variant from only its `id` plus the field being
changed. That drops every field it did not carry, and the update is
rejected on the first required one:
HTTP 400 {"message":"product is invalid:\n`-- variantsInfo is
invalid:\n `-- variants [at index 0] is invalid:\n `--
price must not be empty","details":{"validationError":
{"fieldViolations":[{"field":"product.variantsInfo.variants[0].price",
"description":"must not be empty","violatedRule":"REQUIRED_FIELD"}]}}}
Setting a SKU on an existing product hits this: the agent reads the
product, maps its variants to `{ id, sku }`, and loses the price. It
recovers by retrying with the full variant, so the user still gets the
right result — one rejected write later.
Say that each variant object is replaced whole, and correct the error
table, which attributed `price must not be empty` to new variants only
and so read as not applying to an existing-variant update.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f84c0e0)
The single-variant seed let an agent satisfy the rule by accident: spreading the one variant it read back is the obvious move, so the run often passed without the replace-not-merge behaviour ever being tested. Seed the product with a Size option and two variants at different prices, and ask for the SKU of one of them. Now the agent has to send both variants complete — dropping the untouched variant, or rebuilding either one from just its `id` plus the changed field, is rejected. Judge bands updated to match: correctness checks the other variant survives with its price, and the path judge's 1-3 band covers both the REQUIRED_FIELD rejection and the dropped-variant case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 227ea0a)
The recipe tells you to use `optionChoiceNames` in variants, so an agent reasonably expects to read them back. Get Product does not return them by default — each variant's `choices` carries `optionChoiceIds` only, and `optionChoiceNames` is populated only when the request's `fields` array includes `"VARIANT_OPTION_CHOICE_NAMES"`. Matching a variant on a choice name the response never carried does not raise; it selects nothing. In a recorded run the agent asked for one size of a two-variant product, matched the choice by name, got no variant back, and had to spend two more calls inspecting the product's options before it could resolve the choice GUID and retry. Give both ways out: request the field and match on names, or read the GUID from `options[].choicesSettings.choices[].choiceId` and match `variants[].choices[].optionChoiceIds.choiceId`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit ca6687b)
Eval comparisonBoth scenarios added by this PR reach a clean path against production.
What production did instead. On the SKU task: "Tried wrong catalog version (V1 instead of V3) first resulting in a 428 error, and had to perform an extra API spec lookup to find the SKU field name in the V3 schema." On the price task: "fetched products without a price filter and performed client-side comparison, and guessed the price field location ( Why the changes ship together. The SKU scenario was run four times while these fixes sat in separate branches, and its PR arm was blocked by a different friction each time — the Prose was not enough for either shape defect. The price routing note alone left the agent still scanning the catalog client-side (5/10, then 2/10) after reading the recipe; only the executable filter in the example made it stable. The same held for the response envelope. Both are now shown as examples rather than described. Three further scenarios covering these recipes were pulled in and returned Single run per arm. Both verdicts rest on decision assertions rather than efficiency ratios. |
Three Catalog V3 frictions found in recorded runs, across the two recipes an agent uses together to change something on an existing product. They are in one PR because they compound: an agent answering "set the SKU on the Large size" reads Find Products to locate the product and Update Product to change it, and each defect below costs it a rejected call or a wasted round trip on that single task.
1. Price questions were routed into a full-catalog scan
Query Productscannot filter on price. The recipe listed its filterable fields and said nothing further, so agents paged the whole catalog and compared amounts in their own code.Price is filterable — on Search Products, via
actualPriceRange.minValue.amount, which accepts$lt,$gt,$lte,$gteand the rest. STEP 1 now shows an executable filter, the routing table gains a price row, and STEP 4 says explicitly that price is not in Query Products' set.Worth recording: adding the routing-table row alone did not change behaviour — the agent kept scanning client-side after reading the recipe, twice. Only making the example executable made it stable.
2. The response envelope was never shown
Neither Search nor Query documented where results arrive. Agents fell back to a Catalog V1-shaped guess and lost a round trip:
STEP 1 now shows the envelope. Prose competes with a strong prior weakly; an example does not compete at all.
Also corrects the
fieldsenumMIN_VARIANT_PRICE_INFO→MIN_PRICE_VARIANT. The old name does not exist in the API.3. Variant updates dropped state, twice over
Each variant object is replaced, not merged. The recipe said to pass the entire array and include each existing
id— both of which an agent can satisfy while rebuilding each variant from just itsidplus the field it is changing. Everything else goes with it:The error table made this harder to spot — it attributed the error to "new" variants, so an agent updating an existing one read it as not applying.
Variant choice names are opt-in on read. The recipe tells you to write
optionChoiceNames, so agents expect to read them back. Get Product returnsoptionChoiceIdsonly, and populates names just when the request'sfieldsincludes"VARIANT_OPTION_CHOICE_NAMES". Matching on a name the response never carried raises nothing — it selects no variant. In a recorded run the agent spent two extra calls inspecting the product's options before resolving the choice GUID.Coverage
Two scenarios, three assertions each with the path judge gating at 7, per this repo's eval-scenario guide:
stores/find-products-under-price-catalog-v3stores/update-product-sku-full-variant— seeds a two-variant product so the agent must send both variants complete; a single-variant fixture let a run pass without exercising the rule at all.What is and isn't proven
The price-filter change earned
requiredon a decision assertion, path judge 9/10 against 2/10 on production.The variant changes are supported but not certified. In a paired run where both arms read the recipe, production reproduced the choice-name defect and the PR arm did not — the change doing its job — but the PR arm still fell short of a clean path because of defect 2, which is why these are now in one PR rather than two. Earlier runs also confirmed defect 3's rejection appears without the fix.
These recipes carry more residual friction than any single change removes, so a scenario here can fail its path judge for a reason unrelated to the change under test. Each claim above is tied to a specific recorded run rather than to the verdict.