{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://uiaf.dtv.dev/schema/3.1.1/uiaf-payload.schema.json",
  "title": "UIAF v3 Data-Plane Event Payload",
  "description": "Discriminated common-envelope contract for the three UIAF data-plane events (session, conversion, identify). Producer-strict: additionalProperties false throughout. UTF-8 / RFC 8785 byte ceilings and prohibited-content rules that JSON Schema cannot express are enforced by the shared reference validator (schema-tools/uiaf-reference-validator.mjs); producers reject before body freeze and endpoints reject before processing. The immutable wire/storage body is the UTF-8 RFC 8785 serialization selected and frozen once; its byte length is at most 32768.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "event",
    "event_id",
    "timestamp",
    "identity",
    "consent",
    "attribution",
    "page",
    "client",
    "event_data",
    "_meta"
  ],
  "properties": {
    "event": {
      "enum": [
        "session",
        "conversion",
        "identify"
      ]
    },
    "event_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
      "description": "UUID v4, minted once per logical event by its delivery owner; immutable across retries and transports. Same event_id with a different canonical body is a protocol violation."
    },
    "timestamp": {
      "type": "string",
      "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\\.[0-9]{3}Z$",
      "description": "ISO 8601 UTC with validated millisecond precision and Z offset. Digit ranges schema-checked by the pattern; real-calendar validity (month lengths, leap years) is schema-checked via format: date-time (full mode) and independently re-checked by the shared reference validator. Leap seconds (:60) are NOT representable — the pipeline is Unix-epoch based, and schema and reference validator reject them in aligned fashion. Every other timestamp is Unix epoch seconds (integer).",
      "format": "date-time"
    },
    "identity": {
      "$ref": "#/$defs/identity"
    },
    "consent": {
      "$ref": "#/$defs/consent"
    },
    "attribution": {
      "oneOf": [
        {
          "$ref": "#/$defs/attribution"
        },
        {
          "type": "null"
        }
      ]
    },
    "page": {
      "oneOf": [
        {
          "$ref": "#/$defs/page"
        },
        {
          "type": "null"
        }
      ]
    },
    "client": {
      "oneOf": [
        {
          "$ref": "#/$defs/client"
        },
        {
          "type": "null"
        }
      ]
    },
    "event_data": {
      "type": "object"
    },
    "_meta": {
      "$ref": "#/$defs/meta"
    }
  },
  "allOf": [
    {
      "$comment": "Discriminator: session event_data shape (frozen: reasons 0-5 unique + optional extensions).",
      "if": {
        "properties": {
          "event": {
            "const": "session"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "event_data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "reasons"
            ],
            "properties": {
              "reasons": {
                "type": "array",
                "minItems": 0,
                "maxItems": 5,
                "uniqueItems": true,
                "items": {
                  "enum": [
                    "session_open",
                    "identity_created",
                    "identity_recovered",
                    "attribution_touch",
                    "consent_change"
                  ]
                }
              },
              "extensions": {
                "$ref": "#/$defs/extensions"
              }
            }
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "Discriminator: conversion event_data shape (CR9-07 ratified; CR9-07a: a pattern-valid identifier-shaped conversion_id must additionally fail reference-validator prohibited-content checks).",
      "if": {
        "properties": {
          "event": {
            "const": "conversion"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "event_data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "conversion_name",
              "conversion_id"
            ],
            "properties": {
              "conversion_name": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{0,63}$"
              },
              "conversion_id": {
                "oneOf": [
                  {
                    "type": "string",
                    "pattern": "^[\\x21-\\x7e]{1,128}$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "extensions": {
                "$ref": "#/$defs/extensions"
              }
            }
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "Discriminator: identify event_data shape (bounded verified-evidence array; canonical array <= 8192 UTF-8 bytes enforced by reference validator).",
      "if": {
        "properties": {
          "event": {
            "const": "identify"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "event_data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "identifiers"
            ],
            "properties": {
              "identifiers": {
                "type": "array",
                "minItems": 1,
                "maxItems": 8,
                "items": {
                  "$ref": "#/$defs/evidence"
                }
              },
              "extensions": {
                "$ref": "#/$defs/extensions"
              }
            }
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "Size-profile null paths: for session and conversion, client is the only permitted whole-group null (compact profile); attribution and page must be objects.",
      "if": {
        "properties": {
          "event": {
            "enum": [
              "session",
              "conversion"
            ]
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "attribution": {
            "$ref": "#/$defs/attribution"
          },
          "page": {
            "$ref": "#/$defs/page"
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "IR32 core identify profile: the v3 core schema carries no registered deployment-profile discriminator, so identify REQUIRES attribution, page, and client to each be JSON null. Any future registered non-null profile needs its own explicit versioned schema/policy mechanism; no profile name or prose can open this shape in core.",
      "if": {
        "properties": {
          "event": {
            "const": "identify"
          }
        },
        "type": "object"
      },
      "then": {
        "type": "object",
        "properties": {
          "attribution": {
            "type": "null"
          },
          "page": {
            "type": "null"
          },
          "client": {
            "type": "null"
          }
        }
      }
    },
    {
      "$comment": "is_new is always false when uid is null.",
      "if": {
        "properties": {
          "identity": {
            "properties": {
              "uid": {
                "type": "null"
              }
            },
            "required": [
              "uid"
            ],
            "type": "object"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "identity": {
            "properties": {
              "is_new": {
                "const": false
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "session_seq is required (integer) iff session_id is non-null; null session_id carries null session_seq.",
      "if": {
        "properties": {
          "identity": {
            "properties": {
              "session_id": {
                "type": "string"
              }
            },
            "required": [
              "session_id"
            ],
            "type": "object"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "identity": {
            "properties": {
              "session_seq": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "else": {
        "properties": {
          "identity": {
            "properties": {
              "session_seq": {
                "type": "null"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "CR10-05 lifecycle/source coupling: status not_applicable iff source site_policy, and then all four observed signals are not_applicable.",
      "if": {
        "properties": {
          "consent": {
            "properties": {
              "status": {
                "const": "not_applicable"
              }
            },
            "required": [
              "status"
            ],
            "type": "object"
          }
        },
        "type": "object"
      },
      "then": {
        "properties": {
          "consent": {
            "properties": {
              "source": {
                "const": "site_policy"
              },
              "signals": {
                "properties": {
                  "analytics_storage": {
                    "const": "not_applicable"
                  },
                  "ad_storage": {
                    "const": "not_applicable"
                  },
                  "ad_user_data": {
                    "const": "not_applicable"
                  },
                  "ad_personalization": {
                    "const": "not_applicable"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "else": {
        "properties": {
          "consent": {
            "properties": {
              "source": {
                "enum": [
                  "cmp_cookiebot",
                  "cmp_onetrust",
                  "cmp_didomi",
                  "cmp_custom",
                  "gcm"
                ]
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "IR22 GPC overlay: the overlay applies exactly when gpc.detected is true AND gpc.applicable is true, forcing the three ad purposes to denied while analytics_storage is untouched. detected-only, applicable-only, and neither do not invoke the overlay; effective permissions then follow configured policy alone.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "gpc": {
                "type": "object",
                "properties": {
                  "detected": {
                    "const": true
                  },
                  "applicable": {
                    "const": true
                  }
                },
                "required": [
                  "detected",
                  "applicable"
                ]
              }
            },
            "required": [
              "gpc"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "properties": {
          "consent": {
            "properties": {
              "effective": {
                "properties": {
                  "ad_storage": {
                    "const": "denied"
                  },
                  "ad_user_data": {
                    "const": "denied"
                  },
                  "ad_personalization": {
                    "const": "denied"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      }
    },
    {
      "$comment": "IR5: session_start is true exactly when the allocated session_seq is 0.",
      "if": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "object",
            "properties": {
              "session_seq": {
                "const": 0
              }
            },
            "required": [
              "session_seq"
            ]
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "object",
            "properties": {
              "session_start": {
                "const": true
              }
            }
          }
        }
      },
      "else": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "object",
            "properties": {
              "session_start": {
                "const": false
              }
            }
          }
        }
      }
    },
    {
      "$comment": "IR33 compact step-2 coupling: last_touch.custom null is legal only in the compact session/conversion shape with last_touch.term null, last_touch.content null, and client null (step 1 client-null precedes step 2). first_touch.custom stays object-valued via $defs.touchpoint.",
      "if": {
        "type": "object",
        "properties": {
          "attribution": {
            "type": "object",
            "properties": {
              "last_touch": {
                "type": "object",
                "properties": {
                  "custom": {
                    "type": "null"
                  }
                },
                "required": [
                  "custom"
                ]
              }
            },
            "required": [
              "last_touch"
            ]
          }
        },
        "required": [
          "attribution"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "client": {
            "type": "null"
          },
          "attribution": {
            "type": "object",
            "properties": {
              "last_touch": {
                "type": "object",
                "properties": {
                  "term": {
                    "type": "null"
                  },
                  "content": {
                    "type": "null"
                  }
                }
              }
            }
          }
        }
      }
    },
    {
      "$comment": "Ruling E: effective.analytics_storage denied cannot coexist with a non-null persistent identity.uid; independently authorized restricted events run with uid null.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "denied"
                  }
                },
                "required": [
                  "analytics_storage"
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "identity": {
            "type": "object",
            "properties": {
              "uid": {
                "type": "null"
              }
            }
          }
        }
      }
    },
    {
      "$comment": "Ruling E: with effective.ad_storage denied, no click-ID member may appear anywhere in the immutable payload; both touch click_ids maps must be empty (the reference validator additionally screens every other surface).",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "ad_storage": {
                    "const": "denied"
                  }
                },
                "required": [
                  "ad_storage"
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "attribution": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "first_touch": {
                    "anyOf": [
                      {
                        "type": "null"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "click_ids": {
                            "type": "object",
                            "maxProperties": 0
                          }
                        }
                      }
                    ]
                  },
                  "last_touch": {
                    "anyOf": [
                      {
                        "type": "null"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "click_ids": {
                            "type": "object",
                            "maxProperties": 0
                          }
                        }
                      }
                    ]
                  }
                }
              }
            ]
          }
        }
      }
    },
    {
      "$comment": "IR64a: a session event requires effective.analytics_storage=allowed (capability-driven authorization, never tier-driven).",
      "if": {
        "type": "object",
        "properties": {
          "event": {
            "const": "session"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "allowed"
                  }
                }
              }
            }
          }
        }
      }
    },
    {
      "$comment": "IR64b: an all-four-denied effective permission vector is a dormant state — no data-plane payload may exist.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "denied"
                  },
                  "ad_storage": {
                    "const": "denied"
                  },
                  "ad_user_data": {
                    "const": "denied"
                  },
                  "ad_personalization": {
                    "const": "denied"
                  }
                },
                "required": [
                  "analytics_storage",
                  "ad_storage",
                  "ad_user_data",
                  "ad_personalization"
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": false
    },
    {
      "$comment": "IR65a: data_quality=full iff all four effective permissions allowed.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "allowed"
                  },
                  "ad_storage": {
                    "const": "allowed"
                  },
                  "ad_user_data": {
                    "const": "allowed"
                  },
                  "ad_personalization": {
                    "const": "allowed"
                  }
                },
                "required": [
                  "analytics_storage",
                  "ad_storage",
                  "ad_user_data",
                  "ad_personalization"
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "data_quality": {
                "const": "full"
              }
            }
          }
        }
      },
      "else": {
        "type": "object",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "data_quality": {
                "not": {
                  "const": "full"
                }
              }
            }
          }
        }
      }
    },
    {
      "$comment": "IR65a: analytics allowed with at least one ad purpose denied => data_quality=stripped.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "allowed"
                  }
                },
                "required": [
                  "analytics_storage"
                ],
                "anyOf": [
                  {
                    "properties": {
                      "ad_storage": {
                        "const": "denied"
                      }
                    },
                    "required": [
                      "ad_storage"
                    ]
                  },
                  {
                    "properties": {
                      "ad_user_data": {
                        "const": "denied"
                      }
                    },
                    "required": [
                      "ad_user_data"
                    ]
                  },
                  {
                    "properties": {
                      "ad_personalization": {
                        "const": "denied"
                      }
                    },
                    "required": [
                      "ad_personalization"
                    ]
                  }
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "data_quality": {
                "const": "stripped"
              }
            }
          }
        }
      }
    },
    {
      "$comment": "IR65a: analytics denied (with the all-denied dormant case already invalid) => data_quality=restricted.",
      "if": {
        "type": "object",
        "properties": {
          "consent": {
            "type": "object",
            "properties": {
              "effective": {
                "type": "object",
                "properties": {
                  "analytics_storage": {
                    "const": "denied"
                  }
                },
                "required": [
                  "analytics_storage"
                ]
              }
            },
            "required": [
              "effective"
            ]
          }
        },
        "required": [
          "consent"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "data_quality": {
                "const": "restricted"
              }
            }
          }
        }
      }
    },
    {
      "$comment": "IR87: count===0 iff first_touch null AND last_touch null AND is_new_touch false; a positive count requires both touches object-valued.",
      "if": {
        "type": "object",
        "properties": {
          "attribution": {
            "type": "object",
            "properties": {
              "count": {
                "const": 0
              }
            },
            "required": [
              "count"
            ]
          }
        },
        "required": [
          "attribution"
        ]
      },
      "then": {
        "type": "object",
        "properties": {
          "attribution": {
            "type": "object",
            "properties": {
              "first_touch": {
                "type": "null"
              },
              "last_touch": {
                "type": "null"
              },
              "is_new_touch": {
                "const": false
              }
            }
          }
        }
      },
      "else": {
        "type": "object",
        "properties": {
          "attribution": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "first_touch": {
                    "type": "object"
                  },
                  "last_touch": {
                    "type": "object"
                  }
                }
              }
            ]
          }
        }
      }
    }
  ],
  "$defs": {
    "uid": {
      "type": "string",
      "maxLength": 64,
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\\.[0-9]{9,11}$",
      "$comment": "The embedded issuance-timestamp bound of 9-11 digits (vs the payload-wide 253402300799 / 12-digit epoch ceiling used by every other timestamp field) is DELIBERATE: a 12-digit segment would lengthen the 64-byte UID bound analysis, and the 9-digit floor still admits issuance times back to 2001. Confirmed intentional (F-ARCH-015); do not 'align' it with unixSeconds.",
      "description": "Canonical UID definition: {uuid_v4}.{unix_seconds}. Server-minted only; there is no client-generated persistent-UID fallback."
    },
    "uuid4": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
    },
    "unixSeconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 253402300799
    },
    "identity": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "uid",
        "session_id",
        "session_seq",
        "session_start",
        "is_new",
        "resolution_method",
        "confidence"
      ],
      "properties": {
        "uid": {
          "oneOf": [
            {
              "$ref": "#/$defs/uid"
            },
            {
              "type": "null"
            }
          ]
        },
        "session_id": {
          "oneOf": [
            {
              "$ref": "#/$defs/uuid4"
            },
            {
              "type": "null"
            }
          ]
        },
        "session_seq": {
          "oneOf": [
            {
              "type": "integer",
              "minimum": 0,
              "maximum": 4294967295
            },
            {
              "type": "null"
            }
          ],
          "description": "Allocated from uiaf_session_state.next_seq after authorization, immediately before body freeze, with the increment synchronously persisted. First event of a session carries 0 with session_start true. Strictly increasing for accepted events; gaps allowed; never reused."
        },
        "session_start": {
          "type": "boolean",
          "description": "True exactly when the allocated session_seq value is 0."
        },
        "is_new": {
          "type": "boolean",
          "description": "Always false when uid is null."
        },
        "resolution_method": {
          "enum": [
            "cookie",
            "localstorage_recovery",
            "sessionstorage_recovery",
            "new",
            "ephemeral"
          ]
        },
        "confidence": {
          "enum": [
            "high",
            "medium",
            "low"
          ]
        }
      }
    },
    "consentSignal": {
      "enum": [
        "granted",
        "denied",
        "unknown",
        "not_applicable"
      ]
    },
    "effectivePermission": {
      "enum": [
        "allowed",
        "denied"
      ]
    },
    "consent": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "signals",
        "status",
        "effective",
        "gpc",
        "source",
        "state_updated_at",
        "tier"
      ],
      "properties": {
        "signals": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "analytics_storage",
            "ad_storage",
            "ad_user_data",
            "ad_personalization"
          ],
          "properties": {
            "analytics_storage": {
              "$ref": "#/$defs/consentSignal"
            },
            "ad_storage": {
              "$ref": "#/$defs/consentSignal"
            },
            "ad_user_data": {
              "$ref": "#/$defs/consentSignal"
            },
            "ad_personalization": {
              "$ref": "#/$defs/consentSignal"
            }
          }
        },
        "status": {
          "enum": [
            "resolved",
            "not_applicable"
          ],
          "description": "Data-plane lifecycle domain. 'pending' belongs exclusively to the internal/control-plane consent representation: no pending payload exists on the data plane, and pending reason/provenance fields never appear in this envelope."
        },
        "effective": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "analytics_storage",
            "ad_storage",
            "ad_user_data",
            "ad_personalization"
          ],
          "properties": {
            "analytics_storage": {
              "$ref": "#/$defs/effectivePermission"
            },
            "ad_storage": {
              "$ref": "#/$defs/effectivePermission"
            },
            "ad_user_data": {
              "$ref": "#/$defs/effectivePermission"
            },
            "ad_personalization": {
              "$ref": "#/$defs/effectivePermission"
            }
          }
        },
        "gpc": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "detected",
            "applicable"
          ],
          "properties": {
            "detected": {
              "type": "boolean"
            },
            "applicable": {
              "type": "boolean"
            }
          },
          "description": "GPC is an independent policy overlay and never a consent source."
        },
        "source": {
          "enum": [
            "cmp_cookiebot",
            "cmp_onetrust",
            "cmp_didomi",
            "cmp_custom",
            "gcm",
            "site_policy"
          ],
          "description": "Selected observed-signal authority after CMP-over-GCM precedence. cmp_custom covers all unlisted adapters; no vendor enum additions within v3. site_policy appears iff status is not_applicable."
        },
        "state_updated_at": {
          "$ref": "#/$defs/unixSeconds",
          "description": "When the selected source's consent record last materially changed. Stable across unchanged rereads; never event/read/receipt/serialization time. For site_policy: the active T0 policy-record activation time."
        },
        "consent_record_id": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{1,128}$",
          "description": "Optional opaque local reference to a separately retained consent receipt; compared byte-exact with no trim/case/normalization/coercion. Never a raw CMP string, user identifier, secret, credential, or DSR authenticator. Excluded from the dirty projection: rotation alone can never trigger a session."
        },
        "tier": {
          "type": "integer",
          "minimum": 0,
          "maximum": 4,
          "description": "Derived shorthand only. Tier never drives permissions, sends, purges, routing, or tests."
        }
      }
    },
    "clickIdEntry": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "value",
        "captured_at",
        "expires_at"
      ],
      "properties": {
        "value": {
          "type": "string",
          "minLength": 1,
          "maxLength": 512
        },
        "captured_at": {
          "$ref": "#/$defs/unixSeconds"
        },
        "expires_at": {
          "$ref": "#/$defs/unixSeconds"
        }
      },
      "description": "0 <= captured_at < expires_at <= 253402300799 (ordering enforced by the reference validator). Opaque values are never truncated; expired values are removed from live stores and omitted at assembly before body freeze."
    },
    "touchpoint": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "touch_id",
        "source",
        "medium",
        "campaign",
        "term",
        "content",
        "click_ids",
        "referrer",
        "landing_url",
        "timestamp",
        "custom"
      ],
      "properties": {
        "touch_id": {
          "$ref": "#/$defs/uuid4",
          "description": "Required. Minted once per detected external navigation, before dedup. Same touch_id = reload/SPA/enrichment continuation; different touch_id = accepted new touch even with an identical fingerprint."
        },
        "source": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "medium": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "campaign": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "term": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "content": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "click_ids": {
          "type": "object",
          "maxProperties": 16,
          "propertyNames": {
            "enum": [
              "gclid",
              "gbraid",
              "wbraid",
              "dclid",
              "fbclid",
              "msclkid",
              "ttclid",
              "li_fat_id",
              "epik",
              "twclid",
              "ScCid"
            ]
          },
          "additionalProperties": {
            "$ref": "#/$defs/clickIdEntry"
          },
          "description": "Registered canonical allowlist spellings only (case-sensitive, exact-match, no global key lowercasing) — propertyNames enum kept in CI-asserted parity with uiaf-enums.json click_id_params. Canonical click-ID object <= 4096 UTF-8 bytes; whole touchpoint <= 6144 UTF-8 bytes (reference validator)."
        },
        "referrer": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            {
              "type": "null"
            }
          ]
        },
        "landing_url": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            },
            {
              "type": "null"
            }
          ]
        },
        "timestamp": {
          "$ref": "#/$defs/unixSeconds"
        },
        "custom": {
          "type": "object",
          "maxProperties": 10,
          "propertyNames": {
            "allOf": [
              {
                "pattern": "^[a-z0-9_]{1,32}$",
                "type": "string"
              },
              {
                "not": {
                  "enum": [
                    "ScCid",
                    "dclid",
                    "epik",
                    "fbclid",
                    "gbraid",
                    "gclid",
                    "li_fat_id",
                    "msclkid",
                    "sccid",
                    "ttclid",
                    "twclid",
                    "utm_campaign",
                    "utm_content",
                    "utm_medium",
                    "utm_source",
                    "utm_term",
                    "wbraid"
                  ]
                }
              },
              {
                "not": {
                  "pattern": "^_?uiaf_",
                  "type": "string"
                }
              }
            ]
          },
          "additionalProperties": {
            "type": "string",
            "maxLength": 256,
            "pattern": "^[^\\u0000-\\u001f]*$"
          },
          "description": "Allowlisted custom attribution parameters. One application/x-www-form-urlencoded decode (first decoded occurrence wins, strict UTF-8) then NFC; <= 256 UTF-8 bytes per value; <= 2048 UTF-8 bytes canonical object (reference validator). Reserved names — every UTM parameter, every registered click-ID spelling and its lowercase fold, uiaf_* and _uiaf_* — are rejected here (schema) and by the reference validator (registry-parity CI-asserted)."
        }
      }
    },
    "attribution": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "first_touch",
        "last_touch",
        "count",
        "is_new_touch"
      ],
      "properties": {
        "first_touch": {
          "oneOf": [
            {
              "$ref": "#/$defs/touchpoint"
            },
            {
              "type": "null"
            }
          ]
        },
        "last_touch": {
          "oneOf": [
            {
              "$ref": "#/$defs/touchpointLast"
            },
            {
              "type": "null"
            }
          ]
        },
        "count": {
          "type": "integer",
          "minimum": 0,
          "maximum": 4294967295,
          "description": "Accepted touch records under the configured deduplication algorithm, not page loads or exact human visits."
        },
        "is_new_touch": {
          "type": "boolean"
        }
      }
    },
    "page": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "url",
        "path",
        "referrer",
        "title"
      ],
      "properties": {
        "url": {
          "type": "string",
          "minLength": 1,
          "maxLength": 512,
          "description": "Canonical absolute HTTP(S) origin + WHATWG-serialized path only; no userinfo, query string, or fragment (exact canonicality and path consistency are reference-validator checks).",
          "pattern": "^https?://"
        },
        "path": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "pattern": "^/(?!/)[^?#\\\\]*$",
          "description": "Exact serialized pathname of page.url: single leading slash, no authority-form double slash, no query/fragment/backslash."
        },
        "referrer": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 512,
              "pattern": "^https?://"
            },
            {
              "type": "null"
            }
          ],
          "description": "Origin + path only."
        },
        "title": {
          "type": "string",
          "maxLength": 512
        }
      }
    },
    "client": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "user_agent",
        "language",
        "viewport",
        "screen"
      ],
      "properties": {
        "user_agent": {
          "type": "string",
          "maxLength": 512
        },
        "language": {
          "type": "string",
          "maxLength": 35
        },
        "viewport": {
          "oneOf": [
            {
              "type": "string",
              "pattern": "^[0-9]{1,5}x[0-9]{1,5}$"
            },
            {
              "type": "null"
            }
          ]
        },
        "screen": {
          "oneOf": [
            {
              "type": "string",
              "pattern": "^[0-9]{1,5}x[0-9]{1,5}$"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "evidence": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type",
        "digest",
        "algorithm",
        "normalization_profile",
        "verification",
        "provenance",
        "tenant_binding"
      ],
      "properties": {
        "type": {
          "enum": [
            "email",
            "phone",
            "customer"
          ]
        },
        "digest": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description": "Lowercase 64-hex. The SHA-256 empty-string digest is a validation error (reference validator). Raw identifiers never enter the payload."
        },
        "algorithm": {
          "enum": [
            "sha256",
            "hmac_sha256"
          ]
        },
        "key_version": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_-]{0,31}$",
          "description": "Required iff algorithm is hmac_sha256; prohibited for sha256. Customer identifiers are represented only as tenant-separated server-produced keyed digests; the browser never holds the key."
        },
        "normalization_profile": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]{1,46}_v[0-9]{1,3}$",
          "maxLength": 52,
          "description": "Open pattern-bounded versioned registry identifier (version is part of the identifier; no second version field). Receivers with an older registry snapshot accept the payload structurally, mark unknown-profile entries unsupported, and never reject the whole event for a compatible new profile."
        },
        "verification": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "state",
            "method",
            "time"
          ],
          "properties": {
            "state": {
              "enum": [
                "authenticated",
                "verified"
              ]
            },
            "method": {
              "enum": [
                "authenticated_session",
                "verified_link",
                "verified_otp",
                "payment"
              ]
            },
            "time": {
              "$ref": "#/$defs/unixSeconds"
            }
          }
        },
        "provenance": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Required evidence field (1-128 UTF-8 bytes). Distinct from the prohibited internal consent pending-provenance: this field is valid and required here."
        },
        "tenant_binding": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      },
      "allOf": [
        {
          "$comment": "key_version is required iff algorithm is hmac_sha256 and prohibited for sha256 (strict-mode-safe structure).",
          "if": {
            "properties": {
              "algorithm": {
                "const": "hmac_sha256"
              }
            },
            "required": [
              "algorithm"
            ],
            "type": "object"
          },
          "then": {
            "type": "object",
            "properties": {
              "key_version": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_-]{0,31}$"
              }
            },
            "required": [
              "key_version"
            ]
          },
          "else": {
            "properties": {
              "key_version": false
            },
            "type": "object"
          }
        },
        {
          "$comment": "Ruling G / IR52: customer evidence is server-side keyed only — algorithm hmac_sha256 (key_version required via the hmac conditional). The profile name is NOT frozen to a version: it follows the open versioned pattern; known cross-family registry bindings are rejected by the shared reference validator.",
          "if": {
            "properties": {
              "type": {
                "const": "customer"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          },
          "then": {
            "type": "object",
            "properties": {
              "algorithm": {
                "const": "hmac_sha256"
              }
            }
          }
        }
      ]
    },
    "extensionScalar": {
      "oneOf": [
        {
          "type": "string",
          "maxLength": 256
        },
        {
          "type": "number"
        },
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ]
    },
    "extensions": {
      "type": "object",
      "maxProperties": 16,
      "propertyNames": {
        "allOf": [
          {
            "pattern": "^[a-z][a-z0-9_]{1,31}\\.[a-z][a-z0-9_]{1,31}$",
            "type": "string"
          },
          {
            "not": {
              "pattern": "^uiaf\\.",
              "type": "string"
            }
          },
          {
            "not": {
              "pattern": "\\.(dclid|epik|fbclid|gbraid|gclid|li_fat_id|msclkid|sccid|ttclid|twclid|wbraid)$"
            }
          }
        ]
      },
      "additionalProperties": {
        "oneOf": [
          {
            "$ref": "#/$defs/extensionScalar"
          },
          {
            "type": "object",
            "maxProperties": 16,
            "propertyNames": {
              "pattern": "^[a-z][a-z0-9_]{0,31}$",
              "type": "string"
            },
            "additionalProperties": {
              "$ref": "#/$defs/extensionScalar"
            }
          },
          {
            "type": "array",
            "maxItems": 16,
            "items": {
              "$ref": "#/$defs/extensionScalar"
            }
          }
        ]
      },
      "description": "Sole generic extension point (event_data.extensions). The uiaf. namespace is reserved and prohibited in v3. Max nesting depth 2; whole RFC 8785 canonical object <= 4096 UTF-8 bytes (reference validator). May not carry raw identifiers, credentials, tokens, consent strings, or special-category/free-form sensitive data. IR99: extension local segments matching a registry click key (namespaced local suffix, lowercase incl case-fold of ScCid) are prohibited click carriers; the exclusion pattern is derived from uiaf-enums.click_id_params (CI parity-asserted)."
    },
    "meta": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "uiaf_version",
        "data_quality",
        "attribution_completeness",
        "emitter"
      ],
      "properties": {
        "uiaf_version": {
          "const": "3.1.1"
        },
        "data_quality": {
          "enum": [
            "full",
            "stripped",
            "restricted"
          ],
          "description": "Derived from effective capabilities and field omission, never from a tier."
        },
        "attribution_completeness": {
          "enum": [
            "full",
            "utm_only",
            "request_context_only",
            "none"
          ]
        },
        "emitter": {
          "enum": [
            "client",
            "server"
          ],
          "description": "Sole logical-emitter field. _meta.delivery and _meta.identity_method are retired and fail validation via additionalProperties: false. Transport (fetch/beacon) is never in the immutable body."
        }
      }
    },
    "touchpointLast": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "touch_id",
        "source",
        "medium",
        "campaign",
        "term",
        "content",
        "click_ids",
        "referrer",
        "landing_url",
        "timestamp",
        "custom"
      ],
      "properties": {
        "touch_id": {
          "$ref": "#/$defs/uuid4",
          "description": "Required. Minted once per detected external navigation, before dedup. Same touch_id = reload/SPA/enrichment continuation; different touch_id = accepted new touch even with an identical fingerprint."
        },
        "source": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "medium": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "campaign": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "term": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "content": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[^\\u0000-\\u001f]+$"
            },
            {
              "type": "null"
            }
          ]
        },
        "click_ids": {
          "type": "object",
          "maxProperties": 16,
          "propertyNames": {
            "enum": [
              "gclid",
              "gbraid",
              "wbraid",
              "dclid",
              "fbclid",
              "msclkid",
              "ttclid",
              "li_fat_id",
              "epik",
              "twclid",
              "ScCid"
            ]
          },
          "additionalProperties": {
            "$ref": "#/$defs/clickIdEntry"
          },
          "description": "Registered canonical allowlist spellings only (case-sensitive, exact-match, no global key lowercasing) — propertyNames enum kept in CI-asserted parity with uiaf-enums.json click_id_params. Canonical click-ID object <= 4096 UTF-8 bytes; whole touchpoint <= 6144 UTF-8 bytes (reference validator)."
        },
        "referrer": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            {
              "type": "null"
            }
          ]
        },
        "landing_url": {
          "oneOf": [
            {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            },
            {
              "type": "null"
            }
          ]
        },
        "timestamp": {
          "$ref": "#/$defs/unixSeconds"
        },
        "custom": {
          "oneOf": [
            {
              "type": "object",
              "maxProperties": 10,
              "propertyNames": {
                "allOf": [
                  {
                    "pattern": "^[a-z0-9_]{1,32}$",
                    "type": "string"
                  },
                  {
                    "not": {
                      "enum": [
                        "ScCid",
                        "dclid",
                        "epik",
                        "fbclid",
                        "gbraid",
                        "gclid",
                        "li_fat_id",
                        "msclkid",
                        "sccid",
                        "ttclid",
                        "twclid",
                        "utm_campaign",
                        "utm_content",
                        "utm_medium",
                        "utm_source",
                        "utm_term",
                        "wbraid"
                      ]
                    }
                  },
                  {
                    "not": {
                      "pattern": "^_?uiaf_",
                      "type": "string"
                    }
                  }
                ]
              },
              "additionalProperties": {
                "type": "string",
                "maxLength": 256,
                "pattern": "^[^\\u0000-\\u001f]*$"
              },
              "description": "Allowlisted custom attribution parameters. One application/x-www-form-urlencoded decode (first decoded occurrence wins, strict UTF-8) then NFC; <= 256 UTF-8 bytes per value; <= 2048 UTF-8 bytes canonical object (reference validator). Reserved names — every UTM parameter, every registered click-ID spelling and its lowercase fold, uiaf_* and _uiaf_* — are rejected here (schema) and by the reference validator (registry-parity CI-asserted)."
            },
            {
              "type": "null"
            }
          ],
          "description": "Identical to the ordinary touchpoint custom member, except null is admitted here ONLY because attribution.last_touch is the sole legal compact step-2 nulling site (IR33); the envelope-level compact-coupling conditional requires term=null, content=null, and client=null alongside custom=null."
        }
      }
    }
  }
}
