{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "CanonicalChangeEventEnvelope",
    "description": "The universal wrapper for all SuiteX <-> NetSuite synchronization events. Enforces the Anti-Corruption Layer and circular update prevention.",
    "type": "object",
    "required": [
        "schemaVersion",
        "eventId",
        "accountId",
        "recordType",
        "recordId",
        "operation",
        "source",
        "timestamp",
        "orderingKey",
        "changes",
        "sourceSystem",
        "writeId",
        "actorId"
    ],
    "properties": {
        "schemaVersion": {
            "type": "string",
            "description": "Enables zero-downtime schema evolution (e.g., 'v1', 'v2').",
            "enum": [
                "v1"
            ]
        },
        "eventId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for this specific event instance."
        },
        "accountId": {
            "type": "string",
            "description": "The tenant identifier, used to route to the correct database and fetch tenant-specific field_metadata."
        },
        "recordType": {
            "type": "string",
            "description": "The standard NetSuite record type (e.g., 'project', 'projecttask', 'customer')."
        },
        "recordId": {
            "type": "string",
            "description": "The NetSuite internalId or the SuiteX primary key."
        },
        "operation": {
            "type": "string",
            "enum": [
                "create",
                "update",
                "delete"
            ],
            "description": "The type of change operation represented by this event."
        },
        "source": {
            "type": "string",
            "enum": [
                "reconciliation",
                "netsuite-ue",
                "netsuite-poll",
                "suitex"
            ],
            "description": "The physical emission pathway that produced this event. Ordered by conflict resolution priority (highest to lowest): reconciliation > netsuite-ue > netsuite-poll > suitex. Distinct from sourceSystem: 'source' describes HOW the event entered the backbone (which system component emitted it), while 'sourceSystem' describes WHO made the logical business change (the authoritative domain actor). Example: a NetSuite User Event script detecting a change made by a SuiteX workflow would have source='netsuite-ue' and sourceSystem='workflow'."
        },
        "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "ISO8601 UTC timestamp of when the change occurred."
        },
        "orderingKey": {
            "type": "string",
            "pattern": "^[a-z_]+:[a-zA-Z0-9_-]+$",
            "description": "Pub/Sub ordering key in format 'recordType:recordId'. Ensures all events for the same record are processed sequentially within a Pub/Sub partition. Example: 'customer:12345'."
        },
        "baseVersion": {
            "type": [
                "integer",
                "null"
            ],
            "minimum": 0,
            "description": "Monotonic integer projection version the event was derived from (used for Three-Way Merge). Null if the version is unavailable at emission time (e.g., first-time UE emit without a REST call). Must be 0 or greater when present."
        },
        "changes": {
            "type": "object",
            "description": "Minimal field-level deltas containing normalized values. Schema details are delegated to record-specific schemas."
        },
        "fullSnapshotRef": {
            "type": [
                "string",
                "null"
            ],
            "pattern": "^gs://",
            "description": "Optional GCS URI pointing to a full state backup in Google Cloud Storage, used when deltas cannot be reliably generated. Must use the gs:// scheme. Example: 'gs://suitex-snapshots/customer/12345/20260101T000000Z.json'."
        },
        "sourceSystem": {
            "type": "string",
            "enum": [
                "suitex",
                "netsuite",
                "workflow",
                "user"
            ],
            "description": "The logical business actor that originated the change. Strictly required to prevent infinite circular update loops. Distinct from source: 'sourceSystem' describes WHO made the logical change, while 'source' describes the physical emission pathway. Example: a SuiteX-originated update emitted via NetSuite UE would have sourceSystem='suitex' and source='netsuite-ue'."
        },
        "writeId": {
            "type": "string",
            "format": "uuid",
            "description": "The idempotency key representing the atomic write across systems."
        },
        "actorId": {
            "type": "string",
            "minLength": 1,
            "description": "SuiteX user ID or 'system' for automated processes. The identity of the actor who initiated the change. Either a SuiteX User ID (e.g., '8832') or the reserved string 'system' for automated background processes."
        },
        "transactionGroupId": {
            "type": [
                "string",
                "null"
            ],
            "format": "uuid",
            "description": "Used to group multiple events for batched RESTlet execution."
        }
    },
    "additionalProperties": false
}
