{
  "description": "Event payload conformance vectors for events.schema.json. Each test case carries a full event payload and whether it must validate against the schema. Covers the optional propensity fields on ExposureLayerInfo (probability, modelVersion) and the optional top-level configVersion on exposure and decision events. probability is the propensity of the CHOSEN allocation at decision time: floored-softmax probability for linear_contextual, bucket-range share for other adaptive policies (thompson_bernoulli/epsilon_greedy/ucb1), the actually-used weight for per-entity dynamic allocations, and OMITTED for static policies.",
  "schema": "events.schema.json",
  "testCases": [
    {
      "name": "contextual_exposure_with_propensity",
      "valid": true,
      "comment": "linear_contextual exposure mirroring expected_contextual.json low_engagement_desktop: scores [0, 0.6, 0.4], gamma 1.0, floor 0.05 not binding -> softmax probability of chosen treatment_a = e^0.6 / (e^0 + e^0.6 + e^0.4) = 0.422379. modelVersion is the trainingSummary.generatedAt of the coefficients used. configVersion is the bundle version evaluated against.",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-low-engage",
        "timestamp": "2024-06-02T10:00:00.000Z",
        "sdkName": "js-client",
        "sdkVersion": "1.0.0",
        "configVersion": "2024-06-01T00:00:00.000Z",
        "assignments": {
          "ui.heroVariant": "hero_bold"
        },
        "layers": [
          {
            "layerId": "layer_hero",
            "bucket": 893,
            "policyId": "policy_contextual",
            "policyKey": "hero-contextual",
            "allocationName": "treatment_a",
            "allocationKey": "treatment-a",
            "probability": 0.422379,
            "modelVersion": "2024-05-30T12:00:00.000Z"
          }
        ]
      }
    },
    {
      "name": "adaptive_exposure_bucket_share_probability",
      "valid": true,
      "comment": "Context-free adaptive policy (thompson_bernoulli/epsilon_greedy/ucb1) exposure: probability is the chosen allocation's bucket-range share = (bucketRange[1] - bucketRange[0] + 1) / bucketCount = (599 - 0 + 1) / 1000 = 0.6. No modelVersion (linear_contextual only).",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-bandit-42",
        "timestamp": "2024-06-02T10:05:00.000Z",
        "sdkName": "js-client",
        "sdkVersion": "1.0.0",
        "configVersion": "2024-06-01T00:00:00.000Z",
        "assignments": {
          "checkout.buttonColor": "green"
        },
        "layers": [
          {
            "layerId": "layer_checkout",
            "bucket": 493,
            "policyId": "policy_thompson",
            "policyKey": "checkout-bandit",
            "allocationName": "treatment",
            "allocationKey": "treatment",
            "probability": 0.6
          }
        ]
      }
    },
    {
      "name": "static_exposure_without_probability",
      "valid": true,
      "comment": "Static policy exposure: probability and modelVersion are OMITTED entirely. configVersion is still present because it describes the bundle, not the policy.",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-static-7",
        "timestamp": "2024-06-02T10:10:00.000Z",
        "sdkName": "js-client",
        "sdkVersion": "1.0.0",
        "configVersion": "2024-06-01T00:00:00.000Z",
        "assignments": {
          "pricing.tier": "standard"
        },
        "layers": [
          {
            "layerId": "layer_pricing",
            "bucket": 430,
            "policyId": "policy_static",
            "policyKey": "pricing-rollout",
            "allocationName": "control",
            "allocationKey": "control"
          }
        ]
      }
    },
    {
      "name": "decision_with_config_version",
      "valid": true,
      "comment": "Decision event with top-level configVersion. Layer entries reuse ExposureLayerInfo, so propensity fields are valid here too.",
      "event": {
        "type": "decision",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-bandit-42",
        "timestamp": "2024-06-02T10:05:00.000Z",
        "sdkName": "js-client",
        "sdkVersion": "1.0.0",
        "configVersion": "2024-06-01T00:00:00.000Z",
        "requestedParameters": ["checkout.buttonColor"],
        "assignments": {
          "checkout.buttonColor": "green"
        },
        "layers": [
          {
            "layerId": "layer_checkout",
            "bucket": 493,
            "policyId": "policy_thompson",
            "policyKey": "checkout-bandit",
            "allocationName": "treatment",
            "allocationKey": "treatment",
            "probability": 0.6
          }
        ],
        "latencyMs": 2
      }
    },
    {
      "name": "legacy_exposure_without_new_fields",
      "valid": true,
      "comment": "Backward compatibility: pre-0.6.0 exposure payloads without probability, modelVersion, or configVersion must continue to validate.",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-legacy-1",
        "timestamp": "2024-06-02T10:15:00.000Z",
        "sdkName": "js-client",
        "sdkVersion": "0.9.0",
        "assignments": {
          "pricing.tier": "standard"
        },
        "layers": [
          {
            "layerId": "layer_pricing",
            "bucket": 954,
            "policyId": "policy_static",
            "allocationName": "control"
          }
        ]
      }
    },
    {
      "name": "probability_zero_rejected",
      "valid": false,
      "comment": "probability must be in (0, 1]: 0 violates exclusiveMinimum. A chosen allocation always has non-zero propensity; senders that cannot produce one must omit the field.",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-bandit-42",
        "timestamp": "2024-06-02T10:20:00.000Z",
        "assignments": {
          "checkout.buttonColor": "green"
        },
        "layers": [
          {
            "layerId": "layer_checkout",
            "bucket": 493,
            "policyId": "policy_thompson",
            "allocationName": "treatment",
            "probability": 0
          }
        ]
      }
    },
    {
      "name": "probability_above_one_rejected",
      "valid": false,
      "comment": "probability must be in (0, 1]: 1.5 violates maximum.",
      "event": {
        "type": "exposure",
        "orgId": "org_test",
        "projectId": "proj_test",
        "env": "production",
        "unitKey": "user-bandit-42",
        "timestamp": "2024-06-02T10:25:00.000Z",
        "assignments": {
          "checkout.buttonColor": "green"
        },
        "layers": [
          {
            "layerId": "layer_checkout",
            "bucket": 493,
            "policyId": "policy_thompson",
            "allocationName": "treatment",
            "probability": 1.5
          }
        ]
      }
    }
  ]
}
