Summary
3.1 specifies wholesale_feed_version as scope-keyed:
"A returned wholesale_feed_version is scope-keyed via cache_scope. Callers cache (cache_scope, wholesale_feed_version) pairs alongside the (account, filters, discovery_mode, destinations, countries) tuple used."
The signals/products storyboards exercise the positive conditional-fetch round-trip (echo the token → unchanged: true) but never the scope-isolation negative: a token issued for one cache_scope MUST NOT yield unchanged: true on a request that resolves to a different cache_scope. The requirement is implied but untestable, so a seller that computes a single global feed token — ignoring cache_scope — passes the suite while silently serving stale unchanged: true across scopes.
Evidence (empirical)
Implementing 3.1 wholesale mirroring on a reference signals agent, a deliberately simple global-token build:
- emits
wholesale_feed_version on every wholesale response and honors if_wholesale_feed_version → unchanged: true (happy path ✓);
- passes the 9.2.0 storyboard grader 7 / 0;
- yet live-returns
unchanged: true for an account-scoped request presenting a cache_scope: public token (the token recomputes identically because scope isn't part of the input).
The suite currently certifies a build that violates the scope-keying contract.
Proposed
1. Storyboard wholesale_conditional_fetch_isolation (signals; mirror for products):
get_signals(discovery_mode: "wholesale") → assert wholesale_feed_version and cache_scope present.
- Replay with
if_wholesale_feed_version: <token> → assert unchanged: true, signals omitted, cache_scope present.
- Scope isolation: present the public-scope token on a request that resolves to
cache_scope: account (e.g. account-overlay pricing) → assert NOT unchanged: true. The normative claim is scope-based, not identity-based: the token was issued for cache_scope: public; re-presenting it on a request that yields cache_scope: account MUST NOT short-circuit.
- (Optional) Mutate the catalog, replay the stale token → assert full payload (token rolled).
2. Normative tightening on get-signals-response.json / get-products-response.json (+ the if_wholesale_feed_version request fields):
A seller MUST NOT return unchanged: true when the cache_scope the request resolves to differs from the cache_scope the presented token was issued for.
Design notes (already checked)
- The MUST-NOT lives on the conditional-fetch handler — a scope comparison between the presented token's issuing scope and the scope this request resolves to. It is unrelated to access control; the failure mode is staleness, not authorization.
- Step 4 seeding:
force_get_signals_arm explicitly MUST NOT be consumed on wholesale reads, so it can't drive the mutation step. The storyboard should mutate via a seed_* fixture (or a direct test-controller catalog mutation) instead.
Same shape as the prior merged conformance fixes (#5429 / #5444). Happy to open the storyboard PR if the scenario shape lands.
Summary
3.1 specifies
wholesale_feed_versionas scope-keyed:The signals/products storyboards exercise the positive conditional-fetch round-trip (echo the token →
unchanged: true) but never the scope-isolation negative: a token issued for onecache_scopeMUST NOT yieldunchanged: trueon a request that resolves to a differentcache_scope. The requirement is implied but untestable, so a seller that computes a single global feed token — ignoringcache_scope— passes the suite while silently serving staleunchanged: trueacross scopes.Evidence (empirical)
Implementing 3.1 wholesale mirroring on a reference signals agent, a deliberately simple global-token build:
wholesale_feed_versionon every wholesale response and honorsif_wholesale_feed_version→unchanged: true(happy path ✓);unchanged: truefor anaccount-scoped request presenting acache_scope: publictoken (the token recomputes identically because scope isn't part of the input).The suite currently certifies a build that violates the scope-keying contract.
Proposed
1. Storyboard
wholesale_conditional_fetch_isolation(signals; mirror for products):get_signals(discovery_mode: "wholesale")→ assertwholesale_feed_versionandcache_scopepresent.if_wholesale_feed_version: <token>→ assertunchanged: true,signalsomitted,cache_scopepresent.cache_scope: account(e.g. account-overlay pricing) → assert NOTunchanged: true. The normative claim is scope-based, not identity-based: the token was issued forcache_scope: public; re-presenting it on a request that yieldscache_scope: accountMUST NOT short-circuit.2. Normative tightening on
get-signals-response.json/get-products-response.json(+ theif_wholesale_feed_versionrequest fields):Design notes (already checked)
force_get_signals_armexplicitly MUST NOT be consumed on wholesale reads, so it can't drive the mutation step. The storyboard should mutate via aseed_*fixture (or a direct test-controller catalog mutation) instead.Same shape as the prior merged conformance fixes (#5429 / #5444). Happy to open the storyboard PR if the scenario shape lands.