{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://sous.pras.ai/schema/v0/common/identifier.schema.json",
  "title": "Sous identifiers",
  "description": "Local identifiers and external ontology references. Sous constrains the SLOT; external vocabularies supply the VALUES.",
  "$defs": {
    "LocalId": {
      "title": "Local identifier",
      "description": "Unique within its document. Used for materials, tasks and equipment instances.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]{0,63}$",
      "examples": [
        "onion",
        "seared_beef",
        "pot1",
        "t_sear"
      ]
    },
    "Curie": {
      "title": "CURIE",
      "description": "A reference into an external vocabulary, written `prefix:reference`. Sous never mints food identifiers of its own beyond the `sous:` convenience namespace.\n\nThe prefix is deliberately NOT a closed list. A closed list means adopting a new ontology requires a release of this schema, and the vocabulary meets new ones first -- `milk` and `honey` are UBERON terms, and a FoodOn-shaped field kept them unmapped for three releases. A full IRI is accepted too, since `https:` is a prefix like any other.\n\nWhat replaces the closed list is a check rather than a silence: schema/v0/context.jsonld maps the prefixes Sous knows how to resolve, and a reference using any other prefix validates but is reported by SR-023 as an identifier nothing can dereference. An unknown prefix is then a decision someone made, not a typo nobody noticed.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9.+-]*:[A-Za-z0-9_.~:/?#@!$&'()*+,;=%-]+$",
      "examples": [
        "foodon:03310351",
        "uberon:0001913",
        "chebi:15377",
        "sous:onion_peeled",
        "https://purl.obolibrary.org/obo/FOODON_03310351"
      ]
    },
    "SubstanceRef": {
      "title": "Substance reference",
      "description": "What a material IS. At least one of ref or label is required; a bare label without a ref is legal but does not reach the executable profile.",
      "type": "object",
      "properties": {
        "ref": {
          "$ref": "#/$defs/Curie"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "Human-readable name. Required whenever ref is absent, and recommended always."
        },
        "brand": {
          "type": "string",
          "description": "Brand for a specific commercial product. Prefer a gtin: ref."
        },
        "variety": {
          "type": "string",
          "description": "Cultivar, cut or grade: 'blade steak', 'Yukon Gold', 'San Marzano'."
        },
        "unmapped_reason": {
          "type": "string",
          "description": "Why no ref could be supplied. Required by the executable profile when ref is absent."
        }
      },
      "anyOf": [
        {
          "required": [
            "ref"
          ]
        },
        {
          "required": [
            "label"
          ]
        }
      ],
      "additionalProperties": false
    },
    "EquipmentClassRef": {
      "title": "Equipment class reference",
      "description": "A capability class from vocab/equipment-classes.json. Recipes reference classes; kitchens declare instances that provide them.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]*$"
    },
    "OperationRef": {
      "title": "Operation reference",
      "description": "A core operation id from vocab/operations.json, or a namespaced vendor extension.",
      "type": "string",
      "anyOf": [
        {
          "pattern": "^[a-z][a-z0-9_]*$"
        },
        {
          "pattern": "^x-[a-z0-9-]+:[a-z0-9_]+$"
        }
      ]
    }
  }
}
