{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.modernedi.com/docs/scenarios/schemas/scenario-binding-v1.schema.json",
  "title": "ModernEDI ScenarioBinding v1",
  "description": "A tenant-scoped authoring binding that connects every actor and step in one immutable ScenarioDefinition to current workspace partners and executable targets. It references tenant artifacts by identity and contains no copied runtime configuration, credentials, private keys, or other secrets.",
  "type": "object",
  "additionalProperties": false,
  "required": ["apiVersion", "kind", "metadata", "spec"],
  "properties": {
    "$schema": {
      "const": "https://www.modernedi.com/docs/scenarios/schemas/scenario-binding-v1.schema.json",
      "description": "Optional portable tooling hint. It does not participate in the binding's canonical content hash."
    },
    "apiVersion": {
      "const": "modernedi.com/scenario-binding-authoring/v1",
      "description": "Wire-contract version for tenant-authored scenario bindings."
    },
    "kind": {
      "const": "ScenarioBinding",
      "description": "Discriminator for a scenario binding document."
    },
    "metadata": {
      "$ref": "#/$defs/metadata"
    },
    "spec": {
      "$ref": "#/$defs/spec"
    }
  },
  "$defs": {
    "token": {
      "type": "string",
      "pattern": "^[A-Za-z][A-Za-z0-9_-]{0,127}$",
      "description": "A stable identifier beginning with a letter and containing at most 128 letters, digits, underscores, or hyphens."
    },
    "namespace": {
      "type": "string",
      "maxLength": 253,
      "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$",
      "description": "Lowercase namespace of the published ScenarioDefinition."
    },
    "artifactVersion": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "pattern": "^(?!\\s)(?!.*\\s$)[^\\u0000-\\u001F\\u007F]+$",
      "description": "Exact published definition version."
    },
    "metadata": {
      "title": "Binding metadata",
      "description": "Editable identity and display name for this binding draft. Apply creates an immutable binding revision.",
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "name"],
      "properties": {
        "id": {
          "$ref": "#/$defs/token",
          "description": "Stable binding ID within the workspace."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 160,
          "pattern": "\\S",
          "description": "Human-readable binding name. The server trims leading and trailing whitespace when canonicalizing."
        }
      }
    },
    "spec": {
      "title": "Binding specification",
      "description": "References one exact published definition and binds all of its actors and steps. Apply resolves and freezes authoritative artifacts for runtime use.",
      "type": "object",
      "additionalProperties": false,
      "required": ["definition", "environment", "actors", "steps"],
      "properties": {
        "definition": {
          "$ref": "#/$defs/definitionReference"
        },
        "environment": {
          "const": "production",
          "description": "ModernEDI's tenant runtime environment selector. v1 runs only against the tenant production runtime; ordinary product access, workspace permissions, and applied-binding readiness govern use."
        },
        "actors": {
          "type": "array",
          "description": "Exactly one binding for every definition actor. IDs must be unique; exactly one endpoint must be this workspace and at least one must be a current external partner.",
          "minItems": 1,
          "maxItems": 100,
          "items": {
            "$ref": "#/$defs/actorBinding"
          }
        },
        "steps": {
          "type": "array",
          "description": "Exactly one binding for every definition step. IDs must be unique.",
          "minItems": 1,
          "maxItems": 500,
          "items": {
            "$ref": "#/$defs/stepBinding"
          }
        }
      }
    },
    "definitionReference": {
      "title": "Published definition reference",
      "description": "Exact identity and canonical content hash returned by Publish or the scenario catalog. Apply fails closed if any part differs.",
      "type": "object",
      "additionalProperties": false,
      "required": ["namespace", "key", "version", "contentSha256"],
      "properties": {
        "namespace": {
          "$ref": "#/$defs/namespace"
        },
        "key": {
          "$ref": "#/$defs/token"
        },
        "version": {
          "$ref": "#/$defs/artifactVersion"
        },
        "contentSha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$",
          "description": "Lowercase SHA-256 digest of the canonical published definition."
        }
      }
    },
    "actorBinding": {
      "title": "Actor binding",
      "description": "Connects one definition actor to this workspace or a current trading partner.",
      "type": "object",
      "additionalProperties": false,
      "required": ["actorId", "endpoint"],
      "properties": {
        "actorId": {
          "$ref": "#/$defs/token",
          "description": "Actor ID from the referenced ScenarioDefinition."
        },
        "endpoint": {
          "$ref": "#/$defs/endpoint"
        }
      }
    },
    "endpoint": {
      "title": "Actor endpoint",
      "description": "OneOf discriminator: workspace accepts only kind; partner also requires a positive tenant partner ID.",
      "oneOf": [
        {
          "title": "Workspace endpoint",
          "type": "object",
          "additionalProperties": false,
          "required": ["kind"],
          "properties": {
            "kind": {
              "const": "workspace",
              "description": "The current tenant workspace."
            }
          }
        },
        {
          "title": "Partner endpoint",
          "type": "object",
          "additionalProperties": false,
          "required": ["kind", "partnerId"],
          "properties": {
            "kind": {
              "const": "partner"
            },
            "partnerId": {
              "type": "integer",
              "minimum": 1,
              "description": "Current tenant partner ID. The server rejects missing, deleted, or cross-tenant partners."
            }
          }
        }
      ]
    },
    "stepBinding": {
      "title": "Step binding",
      "description": "Connects one definition step to an executable or observation target and binds every declared semantic fact to an implementation-guide source. syntaxTree is optional while authoring; Apply resolves an exact authoritative syntax-tree artifact before runtime execution.",
      "type": "object",
      "additionalProperties": false,
      "required": ["stepId", "target"],
      "properties": {
        "stepId": {
          "$ref": "#/$defs/token",
          "description": "Step ID from the referenced ScenarioDefinition."
        },
        "syntaxTree": {
          "$ref": "#/$defs/syntaxTreeReference",
          "description": "Optional explicit implementation-guide identity. Omit it to let Apply select the exact current syntax tree from the authoritative mapping or adapter; an applied runtime binding is always resolved and immutable."
        },
        "target": {
          "$ref": "#/$defs/target"
        },
        "factSources": {
          "type": "array",
          "maxItems": 200,
          "description": "One source for every fact declared by this ScenarioDefinition step. Omission is a deliberate authoring shorthand only when the step declares no facts; canonical Apply materializes an empty array. Server validation otherwise requires exact fact-name coverage. Collection shape, element type, cardinality, normalization, and sensitivity come only from the declaration and are intentionally not duplicated here.",
          "items": {
            "$ref": "#/$defs/factSourceBinding"
          }
        }
      }
    },
    "factSourceBinding": {
      "title": "Fact source binding",
      "description": "Binds one named fact declaration to one validated X12 Mapper expression. Fact names must be unique within a step.",
      "type": "object",
      "additionalProperties": false,
      "required": ["fact", "source"],
      "properties": {
        "fact": {
          "$ref": "#/$defs/token",
          "description": "Fact name declared by this exact ScenarioDefinition step."
        },
        "source": {
          "$ref": "#/$defs/factSource"
        }
      }
    },
    "factSource": {
      "title": "Fact source",
      "description": "One ordinary X12 Mapper expression fact source. It uses the same language and execution engine as an incoming map's primaryKeyExtractor; v1 does not define a second scenario-specific selector language. The expression must read the bound X12 document so its value has concrete provenance.",
      "oneOf": [
        {
          "$ref": "#/$defs/x12MapperExpressionSource"
        }
      ]
    },
    "x12MapperExpressionSource": {
      "title": "X12 Mapper expression fact source",
      "description": "Evaluates one ordinary X12 Mapper expression against the step transaction. Validate and Apply use the same parser, semantic validator, function registry, budget mechanism, and syntax-tree-aware engine as runtime mappings and primaryKeyExtractor, with scenario-specific bounded limits. Validate and Apply reject an expression that is statically incapable of reading an X12 segment or element, and runtime fails closed when the executed path produces no concrete X12 read. Scenario evidence uses that engine's element-read tracing facility; this does not imply that existing primary-key persistence emits the same trace. The ScenarioDefinition remains authoritative for value type, cardinality, normalization, and sensitivity.",
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "expression"],
      "properties": {
        "kind": {
          "const": "x12-mapper-expression",
          "description": "Uses ModernEDI's existing X12 Mapper expression language and engine, shared with primaryKeyExtractor."
        },
        "expression": {
          "type": "string",
          "minLength": 1,
          "maxLength": 16384,
          "pattern": "\\S",
          "examples": ["ST->BEG(03)", "forEach(line in ST->PO1+ => line(02))"],
          "description": "One value-returning X12 Mapper expression that reads at least one X12 segment or element. A constant-only expression cannot provide authoritative document evidence. Scalars, lists, and sets are interpreted according to the matching fact declaration; secrets and credentials must never appear here."
        }
      }
    },
    "syntaxTreeReference": {
      "title": "Syntax-tree reference",
      "description": "Optional authoring-time pin to an exact X12 release and transaction set. Both fields are required when this object is present.",
      "type": "object",
      "additionalProperties": false,
      "required": ["x12Version", "transactionSet"],
      "properties": {
        "x12Version": {
          "type": "string",
          "pattern": "^[Vv]?(?:00)?[1-9][0-9]{3}$",
          "examples": ["004010", "005010"],
          "description": "X12 release token. Apply canonicalizes accepted forms to a six-digit release such as 004010."
        },
        "transactionSet": {
          "type": "string",
          "pattern": "^[0-9]{3}$",
          "description": "Known three-digit X12 transaction set. The code must match the referenced definition step."
        }
      }
    },
    "target": {
      "title": "Step target",
      "description": "OneOf discriminator selecting an evidence-constrained runtime mapping, a registered active executor adapter, or observation-only evidence.",
      "oneOf": [
        {
          "title": "Runtime mapping target",
          "type": "object",
          "additionalProperties": false,
          "required": ["kind", "runtimeMappingId"],
          "properties": {
            "kind": {
              "const": "runtime-mapping"
            },
            "runtimeMappingId": {
              "type": "integer",
              "minimum": 1,
              "description": "Current tenant runtime mapping ID. Apply freezes its partner, direction, transaction set, and syntax tree. This target never initiates an exchange: the ordinary ModernEDI transaction pipeline must already have run this exact mapping, and the run advances only after that persisted transaction is attached as authoritative evidence."
            }
          }
        },
        {
          "title": "Adapter target",
          "type": "object",
          "additionalProperties": false,
          "required": ["kind", "adapterId"],
          "properties": {
            "kind": {
              "const": "adapter"
            },
            "adapterId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "pattern": "\\S",
              "description": "Registered active executor adapter ID. Apply verifies it implements this definition and step."
            },
            "runtimeMappingId": {
              "type": "integer",
              "minimum": 1,
              "description": "Optional current tenant runtime mapping whose exact mapping execution must be proven by authoritative evidence while the registered adapter remains the active executor. Apply freezes and verifies its partner, direction, transaction set, and syntax tree."
            }
          }
        },
        {
          "title": "Observation-only target",
          "type": "object",
          "additionalProperties": false,
          "required": ["kind"],
          "properties": {
            "kind": {
              "const": "observation-only",
              "description": "ModernEDI observes and evaluates this step but does not initiate it."
            }
          }
        }
      ]
    }
  },
  "x-modernedi-docs": {
    "filePattern": "*.binding.json",
    "exampleFile": "../examples/order-invoice.binding.json",
    "authoringWorkflow": [
      "Publish or select the exact ScenarioDefinition first; copy its namespace, key, version, and contentSha256 into spec.definition.",
      "Bind every actor exactly once. Use one workspace endpoint and one or more current partner endpoints.",
      "Bind every step exactly once to an evidence-constrained runtime mapping, active executable adapter, or observation-only target. Runtime-mapping and observation-only targets do not initiate exchanges.",
      "For every semantic fact declared by a tenant-authored definition step, add one factSources entry. Its expression uses the same X12 Mapper language and functions as an incoming map's primaryKeyExtractor.",
      "Omit syntaxTree for the easiest authoring experience when the chosen target can resolve it. Specify it only when you deliberately need an implementation-guide pin.",
      "Run Validate, then Apply. Apply resolves current artifacts, fails closed on drift or ambiguity, and stores an immutable runtime revision."
    ],
    "serverSemanticChecks": [
      "The definition identity and content hash resolve to exactly one published or managed definition available to this tenant.",
      "Actor and step IDs are unique and cover the referenced definition exactly.",
      "Exactly one actor endpoint is the workspace and at least one is a current tenant partner.",
      "Every runnable step must cross the tenant workspace boundary: exactly one endpoint is the workspace and the other is a bound partner. Apply rejects partner-to-partner and workspace-to-workspace steps because ModernEDI can neither operate nor authoritatively observe them.",
      "Runtime mappings match the step direction, partner, transaction set, and resolved syntax tree. They constrain authoritative evidence from the ordinary transaction pipeline; they do not dispatch scenario documents.",
      "Tenant-authored steps bind every declared fact exactly once. Mapper expressions are parsed and semantically validated against the resolved syntax tree before Apply, must be capable of an X12 segment or element read, and must produce concrete read provenance at runtime; value type, collection behavior, normalization, and sensitivity are taken from the definition.",
      "Adapters are registered and cover the referenced definition and step. An adapter target may additionally pin a runtime mapping when the adapter executes the step but attached evidence must prove that exact mapping.",
      "Apply records an immutable, fully resolved execution binding; an optional authoring syntaxTree never makes the runtime artifact ambiguous."
    ],
    "toolingIntegration": [
      "factSources[].source.expression is an ordinary value-returning X12 Mapper expression. It is parsed and executed by the same language engine as a mapping's primaryKeyExtractor, not by a scenario-only selector dialect.",
      "The binding editor opens each fact source in a focused Mapper expression editor. It provides syntax-tree-aware completion, hover information, live diagnostics, and the existing searchable function reference using an explicit draft pin, a selected current runtime mapping, or the Mapper catalog's current default as an authoring preview. Apply independently resolves and freezes the exact authoritative tree.",
      "POST /x12mapper/workspace/expression/diagnostics is the public stateless diagnostics operation for external authoring tools. It accepts one expression plus optional X12 release and transaction-set context; it never reads tenant data.",
      "Draft diagnostics are fast authoring feedback. Scenario Validate and Apply remain authoritative for the exact tree, definition fact typing and cardinality, tenant artifacts, target authority, and bounded runtime evaluation."
    ],
    "factValueDurability": {
      "source": "ScenarioTypedValueLimits",
      "collectionMaximumItems": 1000,
      "scalarTextMaximumBytes": 4096,
      "encodedTypedValueMaximumBytes": 32768,
      "observationFactsMaximumBytes": 262144,
      "factEvidenceReceiptMaximumBytes": 4096,
      "evidenceDocumentMaximumBytes": 8192,
      "nonFactEvidenceMaximumDocuments": 8,
      "observationEvidenceMaximumBytes": 524288,
      "stableDiagnostic": "fact_value_limit_exceeded",
      "observationCapacityStableDiagnostic": "transaction_observation_capacity_exceeded",
      "factEvidenceCapacityStableDiagnostic": "transaction_fact_evidence_capacity_exceeded",
      "evidenceDocumentCapacityStableDiagnostic": "transaction_evidence_capacity_exceeded",
      "description": "X12 Mapper expressions retain their ordinary language semantics, while the typed fact values and evidence persisted for one observation are deliberately bounded. Collections may contain at most 1,000 scalar values, each string value may contain at most 4,096 UTF-8 bytes, each encoded typed value may contain at most 32,768 bytes, and the combined durable fact projection may contain at most 262,144 bytes. Fact provenance receipts, individual evidence documents, non-fact evidence count, and aggregate evidence JSON have their own documented limits. Every bound is checked before SQL persistence and returns a stable fact or observation capacity diagnostic."
    },
    "relatedLinks": [
      {
        "label": "Mapper language tutorial",
        "href": "https://app.modernedi.com/mapper/tutorial"
      },
      {
        "label": "Mapper function reference",
        "href": "https://app.modernedi.com/mapper?reference=functions"
      },
      {
        "label": "Mapper expression diagnostics API",
        "href": "https://www.modernedi.com/docs/scenarios/reference#workspace-api-post-x12mapper-workspace-expression-diagnostics"
      }
    ]
  }
}
