From 82963c8af75fd214c38af26603dc16393954b1e4 Mon Sep 17 00:00:00 2001 From: evgen Date: Sun, 28 Jun 2026 15:12:38 +0200 Subject: [PATCH 1/3] feat(signals,media-buy): cache-scope isolation for wholesale-feed conditional fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #5739. The wholesale-feed token is documented as scope-keyed — a caller caches (cache_scope, wholesale_feed_version) pairs — but nothing exercised that a token minted under one cache_scope cannot short-circuit (unchanged: true) a request the agent resolves to another. The reference training agent advertises wholesale_feed_versioning.cache_scope_account: true yet keys conditional fetch on a scope-independent token, so it would silently answer unchanged across scopes. - schemas: add a cross-scope MUST-NOT to get-signals-response / get-products-response `unchanged`, scoped to the conditional-fetch comparator. - storyboards: new universal wholesale-feed-{signals,products}-scope-isolation, gated on cache_scope_account so public-only agents grade not_applicable. - reference agent: scope-key the wholesale feed/pricing tokens. Co-Authored-By: Claude Opus 4.8 --- .../wholesale-feed-cache-scope-isolation.md | 13 ++ server/src/training-agent/task-handlers.ts | 20 ++- ...olesale-feed-products-scope-isolation.yaml | 127 ++++++++++++++++++ ...holesale-feed-signals-scope-isolation.yaml | 127 ++++++++++++++++++ .../media-buy/get-products-response.json | 2 +- .../source/signals/get-signals-response.json | 2 +- 6 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 .changeset/wholesale-feed-cache-scope-isolation.md create mode 100644 static/compliance/source/universal/wholesale-feed-products-scope-isolation.yaml create mode 100644 static/compliance/source/universal/wholesale-feed-signals-scope-isolation.yaml diff --git a/.changeset/wholesale-feed-cache-scope-isolation.md b/.changeset/wholesale-feed-cache-scope-isolation.md new file mode 100644 index 0000000000..e63761040b --- /dev/null +++ b/.changeset/wholesale-feed-cache-scope-isolation.md @@ -0,0 +1,13 @@ +--- +"adcontextprotocol": minor +--- + +signals + media-buy: enforce cache-scope isolation for wholesale-feed conditional fetch + +The schemas already state the wholesale-feed token is keyed by `(cache_scope, wholesale_feed_version)`, but nothing exercised that a token minted under one `cache_scope` cannot short-circuit (`unchanged: true`) a request the agent resolves to another. The reference training agent advertises `wholesale_feed_versioning.cache_scope_account: true` yet keys conditional fetch on a scope-independent token, so it would silently answer `unchanged` across scopes — exactly the gap reported in #5739. + +- **Schemas** — `signals/get-signals-response.json` and `media-buy/get-products-response.json`: add a normative cross-scope MUST-NOT to the `unchanged` description, scoped to the conditional-fetch comparator (it MUST key on `(cache_scope, wholesale_feed_version)`, not the token alone). +- **Storyboards** — new universal `wholesale-feed-signals-scope-isolation` and `wholesale-feed-products-scope-isolation`, gated on `wholesale_feed_versioning.cache_scope_account: true`; agents without per-account overlays grade `not_applicable`. +- **Reference agent** — scope-key the wholesale feed/pricing tokens so the comparator rejects cross-scope tokens (and the existing same-scope `unchanged` path still matches). + +Closes #5739. diff --git a/server/src/training-agent/task-handlers.ts b/server/src/training-agent/task-handlers.ts index c98d84c5f1..507adcfe5b 100644 --- a/server/src/training-agent/task-handlers.ts +++ b/server/src/training-agent/task-handlers.ts @@ -2248,18 +2248,26 @@ function stableMapDigest(map: Map>): string { function productWholesaleFeedMeta(req: WholesaleFeedRequest, session: SessionState): WholesaleFeedMeta { const seededProductsRevision = stableMapDigest(session.complyExtensions.seededProducts); const seededPricingRevision = stableMapDigest(session.complyExtensions.seededPricingOptions); + const cacheScope = cacheScopeForWholesaleRequest(req); + // Tokens are scope-keyed: the same feed state yields a distinct token per + // cache_scope so a token minted under one scope never short-circuits a probe + // the seller resolves to another. See media-buy/get-products-response.json#unchanged. return { - wholesale_feed_version: `${PRODUCT_WHOLESALE_FEED_VERSION}.${seededProductsRevision}`, - pricing_version: `${PRODUCT_WHOLESALE_PRICING_VERSION}.${seededPricingRevision}`, - cache_scope: cacheScopeForWholesaleRequest(req), + wholesale_feed_version: `${PRODUCT_WHOLESALE_FEED_VERSION}.${cacheScope}.${seededProductsRevision}`, + pricing_version: `${PRODUCT_WHOLESALE_PRICING_VERSION}.${cacheScope}.${seededPricingRevision}`, + cache_scope: cacheScope, }; } function signalWholesaleFeedMeta(req: WholesaleFeedRequest): WholesaleFeedMeta { + const cacheScope = cacheScopeForWholesaleRequest(req); + // Tokens are scope-keyed: the same feed state yields a distinct token per + // cache_scope so a token minted under one scope never short-circuits a probe + // the agent resolves to another. See signals/get-signals-response.json#unchanged. return { - wholesale_feed_version: SIGNAL_WHOLESALE_FEED_VERSION, - pricing_version: SIGNAL_WHOLESALE_PRICING_VERSION, - cache_scope: cacheScopeForWholesaleRequest(req), + wholesale_feed_version: `${SIGNAL_WHOLESALE_FEED_VERSION}.${cacheScope}`, + pricing_version: `${SIGNAL_WHOLESALE_PRICING_VERSION}.${cacheScope}`, + cache_scope: cacheScope, }; } diff --git a/static/compliance/source/universal/wholesale-feed-products-scope-isolation.yaml b/static/compliance/source/universal/wholesale-feed-products-scope-isolation.yaml new file mode 100644 index 0000000000..6bd05e0d31 --- /dev/null +++ b/static/compliance/source/universal/wholesale-feed-products-scope-isolation.yaml @@ -0,0 +1,127 @@ +id: wholesale_feed_products_scope_isolation +version: "1.0.0" +title: "Wholesale product feed cache-scope isolation" +category: marketplace_catalog +summary: "Validates that get_products conditional-fetch is keyed by cache_scope: a wholesale_feed_version minted under one cache_scope must not short-circuit (unchanged: true) a request the seller resolves to a different cache_scope." +track: media_buy +introduced_in: "3.2" + +requires_capability: + path: wholesale_feed_versioning.cache_scope_account + equals: true + +required_tools: + - get_products + +narrative: | + Wholesale feed tokens are scope-keyed: a buyer caches + `(cache_scope, wholesale_feed_version)` pairs, not a global seller version. A + seller that publishes per-account overlays (`cache_scope_account: true`) + therefore keeps distinct token spaces for the public rate card and for + account overlays. + + This storyboard mints a token while the seller serves `cache_scope: public`, + then echoes that token on a request the seller resolves to `cache_scope: + account`. A scope-keyed comparator MUST reject the cross-scope token and + return the full feed for the resolved scope; a seller that keys conditional + fetch on a global token would wrongly answer `unchanged: true` and the buyer + would silently miss the account overlay's pricing. + +agent: + interaction_model: media_buy_seller + capabilities: + - wholesale_feed_versioning + examples: + - "Sales agents that publish account-specific overlays" + +caller: + role: buyer_agent + example: "Compliance test harness" + +prerequisites: + description: | + The agent advertises `wholesale_feed_versioning.cache_scope_account: true` + and exposes `get_products`. The harness uses the reserved account-overlay + identity (`account-overlay.example`) to elicit a `cache_scope: account` + response and an ordinary account for the public layer. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: false + +phases: + - id: scope_isolation + title: "Cross-scope token must not short-circuit" + steps: + - id: bootstrap_public + title: "Bootstrap public product feed" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/tasks/get_products" + stateful: true + context_outputs: + - name: public_product_feed_version + path: "wholesale_feed_version" + expected: | + Return public-scope product rows with the public feed token. + sample_request: + buying_mode: "wholesale" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "wholesale_feed_products_scope--bootstrap_public" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_value + path: "cache_scope" + value: "public" + description: "Ordinary account resolves to the public cache scope" + - check: field_present + path: "wholesale_feed_version" + description: "Public response carries the public feed token" + - check: field_present + path: "products[0].product_id" + description: "Bootstrap returns product rows" + - check: field_value + path: "context.correlation_id" + value: "wholesale_feed_products_scope--bootstrap_public" + description: "Context correlation_id returned unchanged" + + - id: cross_scope_probe_not_unchanged + title: "Account-scope probe must not honor a public token" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/tasks/get_products" + stateful: true + expected: | + An account-scope request that echoes the public token MUST NOT return + unchanged; it MUST return the full account feed for its own scope. + sample_request: + buying_mode: "wholesale" + account: + brand: + domain: "account-overlay.example" + operator: "pinnacle-agency.example" + if_wholesale_feed_version: "$context.public_product_feed_version" + context: + correlation_id: "wholesale_feed_products_scope--cross_probe" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_value + path: "cache_scope" + value: "account" + description: "Reserved overlay account resolves to the account cache scope" + - check: field_absent + path: "unchanged" + description: "A public-scope token MUST NOT short-circuit an account-scope read to unchanged: true" + - check: field_present + path: "products[0].product_id" + description: "The account feed is returned in full for its own scope" + - check: field_value + path: "context.correlation_id" + value: "wholesale_feed_products_scope--cross_probe" + description: "Context correlation_id returned unchanged" diff --git a/static/compliance/source/universal/wholesale-feed-signals-scope-isolation.yaml b/static/compliance/source/universal/wholesale-feed-signals-scope-isolation.yaml new file mode 100644 index 0000000000..f24364e3b6 --- /dev/null +++ b/static/compliance/source/universal/wholesale-feed-signals-scope-isolation.yaml @@ -0,0 +1,127 @@ +id: wholesale_feed_signals_scope_isolation +version: "1.0.0" +title: "Wholesale signals feed cache-scope isolation" +category: marketplace_catalog +summary: "Validates that get_signals conditional-fetch is keyed by cache_scope: a wholesale_feed_version minted under one cache_scope must not short-circuit (unchanged: true) a request the agent resolves to a different cache_scope." +track: signals +introduced_in: "3.2" + +requires_capability: + path: wholesale_feed_versioning.cache_scope_account + equals: true + +required_tools: + - get_signals + +narrative: | + Wholesale feed tokens are scope-keyed: a caller caches + `(cache_scope, wholesale_feed_version)` pairs, not a global agent version. An + agent that publishes per-account overlays (`cache_scope_account: true`) + therefore keeps distinct token spaces for the public rate card and for + account overlays. + + This storyboard mints a token while the agent serves `cache_scope: public`, + then echoes that token on a request the agent resolves to `cache_scope: + account`. A scope-keyed comparator MUST reject the cross-scope token and + return the full feed for the resolved scope; an agent that keys conditional + fetch on a global token would wrongly answer `unchanged: true` and the caller + would silently miss the account overlay. + +agent: + interaction_model: signals_agent + capabilities: + - wholesale_feed_versioning + examples: + - "Signals agents that publish account-specific overlays" + +caller: + role: buyer_agent + example: "Compliance test harness" + +prerequisites: + description: | + The agent advertises `wholesale_feed_versioning.cache_scope_account: true` + and exposes `get_signals`. The harness uses the reserved account-overlay + identity (`account-overlay.example`) to elicit a `cache_scope: account` + response and an ordinary account for the public layer. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: false + +phases: + - id: scope_isolation + title: "Cross-scope token must not short-circuit" + steps: + - id: bootstrap_public + title: "Bootstrap public signals feed" + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + stateful: true + context_outputs: + - name: public_signal_feed_version + path: "wholesale_feed_version" + expected: | + Return public-scope signal rows with the public feed token. + sample_request: + discovery_mode: "wholesale" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "wholesale_feed_signals_scope--bootstrap_public" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_value + path: "cache_scope" + value: "public" + description: "Ordinary account resolves to the public cache scope" + - check: field_present + path: "wholesale_feed_version" + description: "Public response carries the public feed token" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "Bootstrap returns signal rows" + - check: field_value + path: "context.correlation_id" + value: "wholesale_feed_signals_scope--bootstrap_public" + description: "Context correlation_id returned unchanged" + + - id: cross_scope_probe_not_unchanged + title: "Account-scope probe must not honor a public token" + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + stateful: true + expected: | + An account-scope request that echoes the public token MUST NOT return + unchanged; it MUST return the full account feed for its own scope. + sample_request: + discovery_mode: "wholesale" + account: + brand: + domain: "account-overlay.example" + operator: "pinnacle-agency.example" + if_wholesale_feed_version: "$context.public_signal_feed_version" + context: + correlation_id: "wholesale_feed_signals_scope--cross_probe" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_value + path: "cache_scope" + value: "account" + description: "Reserved overlay account resolves to the account cache scope" + - check: field_absent + path: "unchanged" + description: "A public-scope token MUST NOT short-circuit an account-scope read to unchanged: true" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "The account feed is returned in full for its own scope" + - check: field_value + path: "context.correlation_id" + value: "wholesale_feed_signals_scope--cross_probe" + description: "Context correlation_id returned unchanged" diff --git a/static/schemas/source/media-buy/get-products-response.json b/static/schemas/source/media-buy/get-products-response.json index 83cfaacbf7..0984e56c03 100644 --- a/static/schemas/source/media-buy/get-products-response.json +++ b/static/schemas/source/media-buy/get-products-response.json @@ -331,7 +331,7 @@ "unchanged": { "type": "boolean", "const": true, - "description": "Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry." + "description": "Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. A seller MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the buyer echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the seller's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." }, "sandbox": { "type": "boolean", diff --git a/static/schemas/source/signals/get-signals-response.json b/static/schemas/source/signals/get-signals-response.json index 38826cec56..e6b583f74c 100644 --- a/static/schemas/source/signals/get-signals-response.json +++ b/static/schemas/source/signals/get-signals-response.json @@ -200,7 +200,7 @@ "unchanged": { "type": "boolean", "const": true, - "description": "Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals." + "description": "Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals. **Cross-scope isolation:** the comparator that decides `unchanged` MUST be keyed on `(cache_scope, wholesale_feed_version)`, not on the token value alone. An agent MUST NOT emit `unchanged: true` when it resolves the request to a different `cache_scope` than the one whose token the caller echoed in `if_wholesale_feed_version` (and/or `if_pricing_version`): because the token is scope-keyed, a value minted for `cache_scope: 'public'` cannot match the agent's current token for `cache_scope: 'account'` (or vice-versa), so such a request MUST return the full feed for the resolved scope with that scope's own token." }, "pagination": { "$ref": "/schemas/core/pagination-response.json" From c14f7dacbcd66fc8ffb2715eae792bc566ce965f Mon Sep 17 00:00:00 2001 From: evgen Date: Sun, 28 Jun 2026 18:53:44 +0200 Subject: [PATCH 2/3] docs(verification): list cache-scope-isolation storyboards in universal parity tables The universal-storyboard doc-parity lint (scripts/lint-universal-storyboard-doc-parity.cjs, run inside build:compliance) requires every graded universal storyboard to have a row in both the conformance index and the compliance catalog. Back-fill rows for the two new wholesale-feed-{signals,products}-scope-isolation storyboards. Co-Authored-By: Claude Opus 4.8 --- docs/building/verification/compliance-catalog.mdx | 2 ++ docs/building/verification/conformance.mdx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/building/verification/compliance-catalog.mdx b/docs/building/verification/compliance-catalog.mdx index a7f5940ed4..797a2258d9 100644 --- a/docs/building/verification/compliance-catalog.mdx +++ b/docs/building/verification/compliance-catalog.mdx @@ -41,6 +41,8 @@ Every agent runs every storyboard in `/compliance/{version}/universal/` regardle | `notification-config-rejections` | Semantic `notification_configs[]` request rejection path for duplicate `subscriber_id` values. | | `wholesale-feed-products` | Product wholesale feed versioning — bootstrap responses carry `wholesale_feed_version`/`cache_scope`, and matching `if_wholesale_feed_version` probes return `unchanged` without product rows. | | `wholesale-feed-signals` | Signals wholesale feed versioning — bootstrap responses carry `wholesale_feed_version`/`cache_scope`, and matching `if_wholesale_feed_version` probes return `unchanged` without signal rows. | +| `wholesale-feed-products-scope-isolation` | Product wholesale feed cache-scope isolation — a `wholesale_feed_version` minted under one `cache_scope` must not short-circuit (`unchanged: true`) a request the seller resolves to a different `cache_scope`. | +| `wholesale-feed-signals-scope-isolation` | Signals wholesale feed cache-scope isolation — a `wholesale_feed_version` minted under one `cache_scope` must not short-circuit (`unchanged: true`) a request the agent resolves to a different `cache_scope`. | | `wholesale-feed-product-webhooks` | Account-level `notification_configs[]` registration for agents that advertise product wholesale feed webhook events. | | `wholesale-feed-signal-webhooks` | Account-level `notification_configs[]` registration for agents that advertise signal wholesale feed webhook events. | | `wholesale-feed-bulk-webhooks` | Account-level `notification_configs[]` registration for agents that advertise `wholesale_feed.bulk_change`. | diff --git a/docs/building/verification/conformance.mdx b/docs/building/verification/conformance.mdx index 12134d6485..6eba2ff9a1 100644 --- a/docs/building/verification/conformance.mdx +++ b/docs/building/verification/conformance.mdx @@ -114,6 +114,8 @@ Every agent MUST pass every storyboard below. | [`notification_config_rejections`](https://adcontextprotocol.org/compliance/latest/universal/notification-config-rejections) | Semantic `notification_configs[]` request rejection path for duplicate `subscriber_id` values | | [`wholesale_feed_products`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-products) | Product wholesale feed versioning: bootstrap responses carry `wholesale_feed_version`/`cache_scope`, and matching `if_wholesale_feed_version` probes return `unchanged` without product rows | | [`wholesale_feed_signals`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-signals) | Signals wholesale feed versioning: bootstrap responses carry `wholesale_feed_version`/`cache_scope`, and matching `if_wholesale_feed_version` probes return `unchanged` without signal rows | +| [`wholesale_feed_products_scope_isolation`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-products-scope-isolation) | Product wholesale feed cache-scope isolation: a `wholesale_feed_version` minted under one `cache_scope` must not short-circuit (`unchanged: true`) a request the seller resolves to a different `cache_scope` | +| [`wholesale_feed_signals_scope_isolation`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-signals-scope-isolation) | Signals wholesale feed cache-scope isolation: a `wholesale_feed_version` minted under one `cache_scope` must not short-circuit (`unchanged: true`) a request the agent resolves to a different `cache_scope` | | [`wholesale_feed_product_webhooks`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-product-webhooks) | Account-level `notification_configs[]` registration for agents that advertise product wholesale feed webhook events | | [`wholesale_feed_signal_webhooks`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-signal-webhooks) | Account-level `notification_configs[]` registration for agents that advertise signal wholesale feed webhook events | | [`wholesale_feed_bulk_webhooks`](https://adcontextprotocol.org/compliance/latest/universal/wholesale-feed-bulk-webhooks) | Account-level `notification_configs[]` registration for agents that advertise `wholesale_feed.bulk_change` | From 4898b49406b117685a7b6d221d705a2e3a256a5a Mon Sep 17 00:00:00 2001 From: evgen Date: Sun, 28 Jun 2026 19:12:19 +0200 Subject: [PATCH 3/3] test(training-agent): expect scope-keyed wholesale feed/pricing tokens The reference agent now folds cache_scope into the wholesale feed/pricing token (training-*-feed-v1 -> training-*-feed-v1.public[.]). Update the literal expectations in the public-scope wholesale tests accordingly. The standalone-pricing-token rejection cases keep their arbitrary literals (the reject path is value-independent). Co-Authored-By: Claude Opus 4.8 --- server/tests/unit/training-agent.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/tests/unit/training-agent.test.ts b/server/tests/unit/training-agent.test.ts index e669018674..cbd3861714 100644 --- a/server/tests/unit/training-agent.test.ts +++ b/server/tests/unit/training-agent.test.ts @@ -1221,8 +1221,8 @@ describe('get_products handler', () => { buying_mode: 'wholesale', }); - expect(first.wholesale_feed_version).toBe('training-products-feed-v1.base'); - expect(first.pricing_version).toBe('training-products-pricing-v1.base'); + expect(first.wholesale_feed_version).toBe('training-products-feed-v1.public.base'); + expect(first.pricing_version).toBe('training-products-pricing-v1.public.base'); expect(first.cache_scope).toBe('public'); const { result: unchanged } = await simulateCallTool(server, 'get_products', { @@ -1242,14 +1242,14 @@ describe('get_products handler', () => { const server = createTrainingAgentServer(DEFAULT_CTX); const { result } = await simulateCallTool(server, 'get_products', { buying_mode: 'wholesale', - if_wholesale_feed_version: 'training-products-feed-v1.base', + if_wholesale_feed_version: 'training-products-feed-v1.public.base', if_pricing_version: 'stale-pricing-token', }); expect(result.unchanged).toBeUndefined(); expect((result.products as unknown[]).length).toBeGreaterThan(0); - expect(result.wholesale_feed_version).toBe('training-products-feed-v1.base'); - expect(result.pricing_version).toBe('training-products-pricing-v1.base'); + expect(result.wholesale_feed_version).toBe('training-products-feed-v1.public.base'); + expect(result.pricing_version).toBe('training-products-pricing-v1.public.base'); }); it('changes product wholesale version tokens when controller-seeded catalog state changes', async () => { @@ -7239,8 +7239,8 @@ describe('get_signals handler', () => { }); expect((first.signals as unknown[]).length).toBeGreaterThan(0); - expect(first.wholesale_feed_version).toBe('training-signals-feed-v1'); - expect(first.pricing_version).toBe('training-signals-pricing-v1'); + expect(first.wholesale_feed_version).toBe('training-signals-feed-v1.public'); + expect(first.pricing_version).toBe('training-signals-pricing-v1.public'); expect(first.cache_scope).toBe('public'); const { result: unchanged } = await simulateCallTool(server, 'get_signals', { @@ -7262,14 +7262,14 @@ describe('get_signals handler', () => { const { result } = await simulateCallTool(server, 'get_signals', { account, discovery_mode: 'wholesale', - if_wholesale_feed_version: 'training-signals-feed-v1', + if_wholesale_feed_version: 'training-signals-feed-v1.public', if_pricing_version: 'stale-pricing-token', }); expect(result.unchanged).toBeUndefined(); expect((result.signals as unknown[]).length).toBeGreaterThan(0); - expect(result.wholesale_feed_version).toBe('training-signals-feed-v1'); - expect(result.pricing_version).toBe('training-signals-pricing-v1'); + expect(result.wholesale_feed_version).toBe('training-signals-feed-v1.public'); + expect(result.pricing_version).toBe('training-signals-pricing-v1.public'); }); it('supports signal_refs exact lookup in brief mode', async () => { @@ -7330,8 +7330,8 @@ describe('get_signals handler', () => { signal_spec: 'E2E fallback signal discovery', }); - expect(result.wholesale_feed_version).toBe('training-signals-feed-v1'); - expect(result.pricing_version).toBe('training-signals-pricing-v1'); + expect(result.wholesale_feed_version).toBe('training-signals-feed-v1.public'); + expect(result.pricing_version).toBe('training-signals-pricing-v1.public'); expect((result.signals as unknown[]).length).toBeGreaterThan(0); });