Skip to content

fix(wix-manage): three Catalog V3 frictions in find-products and update-product - #927

Open
danshw wants to merge 6 commits into
mainfrom
fix/stores-find-products-price-filter
Open

fix(wix-manage): three Catalog V3 frictions in find-products and update-product#927
danshw wants to merge 6 commits into
mainfrom
fix/stores-find-products-price-filter

Conversation

@danshw

@danshw danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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 Products cannot 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, $gte and 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:

the first API execution failed to locate the product because it incorrectly parsed the
search response using Catalog V1/V2 structure (productResults.results) instead of V3
(products), requiring a second corrected API execution

STEP 1 now shows the envelope. Prose competes with a strong prior weakly; an example does not compete at all.

Also corrects the fields enum MIN_VARIANT_PRICE_INFOMIN_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 its id plus the field it is changing. Everything else goes with it:

400 product is invalid: variantsInfo is invalid: variants [at index 0] is invalid:
    price must not be empty
    (REQUIRED_FIELD on product.variantsInfo.variants[0].price)

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 returns optionChoiceIds only, and populates names just when the request's fields includes "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-v3
  • stores/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 required on 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.

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.
@danshw

danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

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.

danshw and others added 5 commits August 8, 2026 21:27
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)
@danshw danshw changed the title fix(wix-manage): route price filtering to Search Products in Catalog V3 fix(wix-manage): three Catalog V3 frictions in find-products and update-product Aug 8, 2026
@danshw

danshw commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Eval comparison

Both scenarios added by this PR reach a clean path against production.

Scenario PR production
stores/update-product-sku-full-variant steps 4 8
correctness 10/10 10/10
path judge 10/10 — clean path 2/10
verdict required (PR wins, high confidence)
stores/find-products-under-price-catalog-v3 steps 3 6
correctness 10/10 10/10
path judge 10/10 — clean path 2/10
verdict required (PR wins, high confidence)

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 (priceData.price) resulting in empty matches."

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 price must not be empty rejection, then variant choice-name resolution, then parsing the search response with the Catalog V1 shape. Each fix was real, and none of them could show a clean path while the next one was still outstanding. With all three applied the path collapses to four steps and scores 10.

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 not-required — two ties and one where both arms failed on unrelated infrastructure. That is expected for scenarios this change does not target.

Single run per arm. Both verdicts rest on decision assertions rather than efficiency ratios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant