id: stale_response_advisory
version: "1.0.0"
title: "STALE_RESPONSE advisory-success wire placement"
category: stale_response_advisory
summary: "Validates that STALE_RESPONSE rides in errors[] on a populated success response with transport success preserved, and that STALE_RESPONSE is absent on healthy upstream responses."
track: error_handling
required_tools:
  - get_products

narrative: |
  STALE_RESPONSE is a non-fatal advisory emitted when the seller served cached data past its
  freshness target because an upstream dependency failed. It rides in errors[] on a populated
  success response — transport stays success (MCP isError: false, HTTP 200, A2A succeeded),
  the response payload is non-empty, and the advisory is never promoted to adcp_error on the
  envelope.

  This is easy for implementations to get wrong: returning SERVICE_UNAVAILABLE instead of a
  stale-cache success, flipping transport failure, or emitting an empty payload all fail the
  spec. STALE_RESPONSE is distinct from SERVICE_UNAVAILABLE: STALE_RESPONSE means the seller's
  service is fine but one upstream is temporarily unreachable and the seller chose to honor the
  request from cache. SERVICE_UNAVAILABLE means no usable response is available at all.

  This storyboard verifies three properties:
  1. Wire placement: STALE_RESPONSE appears in errors[] on a success response with a populated
     payload — not as adcp_error, not with transport failure.
  2. Details shape: error.details conforms to error-details/stale-response.json —
     served_from_cache (required, always true), cache_age_seconds (required).
  3. Non-emission guard: STALE_RESPONSE is NOT emitted when the upstream is healthy (normal
     get_products call against a working upstream).

  The stale-cache forcing phases require comply_test_controller with the
  force_upstream_unavailable scenario. Steps requiring the controller grade not_applicable for
  agents that do not advertise it. The non-emission guard (Phase 3) runs for all agents.

agent:
  interaction_model: media_buy_seller
  capabilities:
    - sells_media
  examples:
    - "Any AdCP seller agent that caches upstream responses"

caller:
  role: buyer_agent
  example: "Compliance test harness"

prerequisites:
  description: |
    Phase 2 (stale-cache forcing) requires comply_test_controller with
    force_upstream_unavailable support. Phase 3 (non-emission guard) runs for all agents.
  test_kit: "test-kits/acme-outdoor.yaml"

phases:
  - id: capability_discovery
    title: "Capability discovery"
    narrative: |
      The buyer calls get_adcp_capabilities to confirm the agent supports media buying before
      testing STALE_RESPONSE wire placement.

    steps:
      - id: get_capabilities
        title: "Check agent capabilities"
        narrative: |
          Verify that the agent declares the expected protocol support before
          proceeding with domain-specific operations.
        task: get_adcp_capabilities
        schema_ref: "protocol/get-adcp-capabilities-request.json"
        response_schema_ref: "protocol/get-adcp-capabilities-response.json"
        doc_ref: "/protocol/get_adcp_capabilities"
        comply_scenario: capability_discovery
        stateful: false
        expected: |
          Return capabilities declaring media_buy in supported_protocols.
        sample_request:
          context:
            correlation_id: "stale_response_advisory--get_capabilities"
        validations:
          - check: response_schema
            description: "Response matches get-adcp-capabilities-response.json schema"
          - check: field_present
            path: "supported_protocols"
            description: "Agent declares supported protocols"
          - check: field_present
            path: "context"
            description: "Response echoes back the context object"
          - check: field_value
            path: "context.correlation_id"
            value: "stale_response_advisory--get_capabilities"
            description: "Context correlation_id returned unchanged"

  - id: stale_response_forcing
    title: "Force stale-cache condition and verify advisory-success wire placement"
    narrative: |
      The test controller forces an upstream dependency unavailable while a stale cache entry
      exists. The seller must respond to get_products with a populated payload from cache and
      emit STALE_RESPONSE in errors[], while keeping transport success.

      All steps in this phase require comply_test_controller with force_upstream_unavailable
      support. Steps grade not_applicable for agents that do not advertise the controller.
      Validations in this phase carry severity: advisory — a seller that has the controller
      but whose runner contract does not yet support force_upstream_unavailable will fail
      advisory rather than blocking compliance, consistent with the acceptance criteria in
      issue #4934.

    steps:
      - id: force_upstream_unavailable
        title: "Force upstream dependency unavailable (seed stale-cache condition)"
        narrative: |
          The compliance harness calls comply_test_controller with force_upstream_unavailable
          to signal that the seller's upstream dependency is currently unreachable. The seller
          is expected to have (or synthesize) a stale cache entry for the subsequent
          get_products call. On receipt, the seller MUST mark the named upstream as unavailable
          for the duration of the compliance session (or until explicitly reset), so that the
          next get_products request falls back to the cache entry.
        task: comply_test_controller
        schema_ref: "compliance/comply-test-controller-request.json"
        response_schema_ref: "compliance/comply-test-controller-response.json"
        doc_ref: "/building/by-layer/L3/comply-test-controller"
        comply_scenario: force_upstream_unavailable
        requires_tool: comply_test_controller
        stateful: true
        expected: |
          Controller acknowledges the upstream-unavailable state transition. The seller
          confirms the named upstream is now marked unreachable for subsequent calls.
        sample_request:
          scenario: force_upstream_unavailable
          params:
            tool: "get_products"
            upstream_name: "inventory-service"
            cache_age_seconds: 60
          account:
            sandbox: true
          context:
            correlation_id: "stale_response_advisory--force_upstream_unavailable"
        validations:
          - check: response_schema
            description: "Controller response matches comply-test-controller-response.json"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable being advertised in the seller's comply_test_controller scenarios. Promote to required when force_upstream_unavailable is widely adopted — remove the permanent_advisory block and set severity: required (or remove severity to use the default). See issue #4934."
          - check: field_present
            path: "context"
            description: "Response echoes back the context object"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable being advertised in the seller's comply_test_controller scenarios. Promote to required when force_upstream_unavailable is widely adopted. See issue #4934."
          - check: field_value
            path: "context.correlation_id"
            value: "stale_response_advisory--force_upstream_unavailable"
            description: "Context correlation_id returned unchanged"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable being advertised in the seller's comply_test_controller scenarios. Promote to required when force_upstream_unavailable is widely adopted. See issue #4934."

      - id: stale_response_wire_placement
        title: "STALE_RESPONSE in errors[] on populated success response"
        narrative: |
          With the upstream forced unavailable and a stale cache entry present, the seller
          responds to get_products with a populated payload and STALE_RESPONSE in errors[].
          Transport MUST stay success (isError: false, no adcp_error envelope) — the advisory
          rides in the payload's errors[] and MUST NOT be promoted to a transport-level error.

          The seller MUST include the required details fields: served_from_cache: true and
          cache_age_seconds (integer >= 0). Optionally: freshness_target_seconds, upstream
          (name and/or url of the failed dependency), original_error (code/message of the
          underlying failure).
        task: get_products
        schema_ref: "media-buy/get-products-request.json"
        response_schema_ref: "media-buy/get-products-response.json"
        doc_ref: "/media-buy/task-reference/get_products"
        comply_scenario: stale_response_advisory
        requires_tool: comply_test_controller
        stateful: true
        expected: |
          Return a populated product list from stale cache with:
          - errors[] containing a STALE_RESPONSE entry with details.served_from_cache: true
            and details.cache_age_seconds: 60, as requested by the controller
          - Transport success: no adcp_error on the envelope (isError: false)
          - Response payload non-empty (products[] with at least one entry)

        sample_request:
          idempotency_key: "$generate:uuid_v4#stale_response_advisory_stale_response_forcing_stale_response_wire_placement"
          buying_mode: "brief"
          brief: "Display advertising"
          account:
            brand:
              domain: "acmeoutdoor.example"
            operator: "pinnacle-agency.example"
            sandbox: true
          context:
            correlation_id: "stale_response_advisory--stale_response_wire_placement"
        validations:
          - check: response_schema
            description: "Response matches get-products-response.json schema even under stale cache"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted — remove permanent_advisory and severity fields. See issue #4934."
          - check: field_present
            path: "errors"
            description: "errors[] is present on a stale-cache response"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted. See issue #4934."
          - check: field_contains
            path: "errors[*]"
            value:
              code: "STALE_RESPONSE"
              details:
                served_from_cache: true
                cache_age_seconds: 60
            description: "One STALE_RESPONSE entry carries the correlated deterministic stale-cache details"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted. See issue #4934."
          - check: field_present
            path: "context"
            description: "Response echoes back the context object on stale-cache advisory"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted. See issue #4934."
          - check: field_value
            path: "context.correlation_id"
            value: "stale_response_advisory--stale_response_wire_placement"
            description: "Context correlation_id returned unchanged"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted. See issue #4934."
          - check: field_present
            path: "products[0]"
            description: "Response payload is non-empty (products[] has at least one entry from stale cache)"
            severity: advisory
            permanent_advisory:
              reason: "Gated on force_upstream_unavailable runner support. Promote to required when widely adopted. See issue #4934."

  - id: non_emission_guard
    title: "No STALE_RESPONSE on healthy upstream response"
    narrative: |
      STALE_RESPONSE MUST NOT be emitted when the upstream is healthy. Per the normative
      error-code description, sellers MUST NOT emit STALE_RESPONSE when the cache hit is
      within freshness target or when no cached payload exists — the correct code for a
      completely unavailable upstream with no cache is SERVICE_UNAVAILABLE.

      This phase sends a normal get_products request and verifies no STALE_RESPONSE appears.
      Runs for all agents — no controller required.

    steps:
      - id: no_stale_on_healthy_upstream
        title: "STALE_RESPONSE absent on healthy upstream response"
        narrative: |
          A normal get_products request against a healthy upstream MUST NOT emit
          STALE_RESPONSE. Sellers that unconditionally emit STALE_RESPONSE on every
          get_products response are non-conformant — the code is only valid when a
          specific cache-fallback condition is active.

          NOTE: No machine-enforced absence check for STALE_RESPONSE exists today — the
          `field_absent` primitive rejects errors[] entirely, but other error codes are
          valid. The schema and correlation_id checks below prove conformance for
          non-STALE_RESPONSE flows; a dedicated `error_code_absent` check kind would
          close this gap. TODO: add check primitive when runner contract supports it.
        task: get_products
        schema_ref: "media-buy/get-products-request.json"
        response_schema_ref: "media-buy/get-products-response.json"
        doc_ref: "/media-buy/task-reference/get_products"
        comply_scenario: stale_response_non_emission
        stateful: false
        expected: |
          Return products normally. errors[] may be absent or present with codes other
          than STALE_RESPONSE. STALE_RESPONSE MUST NOT appear unless the seller is
          actively serving from a stale cache entry past its freshness target.

        sample_request:
          idempotency_key: "$generate:uuid_v4#stale_response_advisory_non_emission_guard_no_stale_on_healthy_upstream"
          buying_mode: "brief"
          brief: "Display advertising for outdoor lifestyle campaign"
          account:
            brand:
              domain: "acmeoutdoor.example"
            operator: "pinnacle-agency.example"
          context:
            correlation_id: "stale_response_advisory--no_stale_on_healthy"
        validations:
          - check: response_schema
            description: "Response matches get-products-response.json schema on healthy upstream"
          - check: field_present
            path: "context"
            description: "Response echoes back the context object"
          - check: field_value
            path: "context.correlation_id"
            value: "stale_response_advisory--no_stale_on_healthy"
            description: "Context correlation_id returned unchanged"
