id: https://sniff.world/schema/taxon-scope
name: sniff_taxon_scope
title: "Sniff TaxonScope: every identifier field declares the species it is scoped to"
version: 0.1.0
license: https://creativecommons.org/licenses/by/4.0/

description: >-
  LinkML root for the species membrane over the data planes. A knowledge graph
  that spans dog, human, mouse, cat and horse moves identifiers across species
  at every join, and an identifier that crosses without its taxon becomes a
  claim about the wrong animal. Three Drosophila annotation ids once sat in the
  gene field of canine disease rows and rendered on breed pages as the gene
  dogs carry a change in; no check refused them because no field had ever said
  which species it belonged to.


  This schema makes that declaration explicit. A TaxonScopeManifest names every
  species-scoped field in the shipped data planes, the taxon it is scoped to,
  the kind of identifier it holds, and optionally the catalog the value must
  resolve in. IdentifierNamespace rows give each (kind, taxon) pair the shape
  its identifiers take, so a value that matches another species' namespace is
  refused as a leak, which is a louder failure than an unknown value.


  The build reads the manifest (web/scripts/validate-taxon-scope.cjs); this
  file is the contract the manifest and the gate obey. Same habit as the
  CoverageFrame family: the schema is what the build reads, never documentation
  about it.

prefixes:
  linkml: https://w3id.org/linkml/
  sniff: https://sniff.world/schema/
  NCBITaxon: http://purl.obolibrary.org/obo/NCBITaxon_
  dcterms: http://purl.org/dc/terms/
default_prefix: sniff
default_range: string

imports:
  - linkml:types

see_also:
  - https://sniff.world/schema/coverage-frame

keywords:
  - species scope
  - identifier namespace
  - cross-species join
  - membrane

classes:

  TaxonScopeManifest:
    description: >-
      The whole membrane: the namespaces identifiers may take, the catalogs a
      value may be required to resolve in, and every species-scoped field in
      the shipped data planes. An empty manifest is a defect, not a pass.
    tree_root: true
    slots:
      - namespaces
      - catalogs
      - fields

  IdentifierNamespace:
    description: >-
      The shape an identifier of one kind takes for one taxon. Used two ways:
      a value in a field of this kind and taxon must match it, and a value in
      a field of any other taxon that matches it is a cross-species leak.
    slots:
      - kind
      - taxon
      - pattern
      - ignore_case
      - example
    slot_usage:
      pattern:
        required: true

  Catalog:
    description: >-
      A place a value may be required to resolve in: an artifact and the path
      within it whose selected values form the allowed set. Declared only where
      the catalog is complete for its taxon and kind; a partial catalog would
      refuse true values, and a field with no complete catalog declares none
      and relies on the namespace arms alone.
    slots:
      - name
      - artifact
      - path
      - taxon
      - kind

  TaxonScopedField:
    description: >-
      One field in one shipped artifact whose values are identifiers scoped to
      a species. The taxon is either a constant or read per row from a sibling
      field named by taxon_from, for artifacts that carry several species.
    slots:
      - artifact
      - path
      - kind
      - taxon
      - taxon_from
      - split
      - resolves_in
      - note
    rules:
      - preconditions:
          slot_conditions:
            taxon_from:
              value_presence: ABSENT
        postconditions:
          slot_conditions:
            taxon:
              required: true
        description: A field with no sibling taxon column declares its taxon as a constant.

slots:

  namespaces:
    description: The identifier namespaces known to the membrane.
    range: IdentifierNamespace
    multivalued: true
    inlined_as_list: true
    required: true

  catalogs:
    description: The catalogs a field may be required to resolve in.
    range: Catalog
    multivalued: true
    inlined_as_list: true

  fields:
    description: Every species-scoped field in the shipped data planes.
    range: TaxonScopedField
    multivalued: true
    inlined_as_list: true
    required: true

  kind:
    description: What sort of identifier the values are.
    range: IdentifierKindEnum
    required: true

  taxon:
    description: The NCBI Taxonomy CURIE of the species the values belong to.
    range: uriorcurie
    pattern: "^NCBITaxon:[0-9]+$"

  taxon_from:
    description: >-
      Name of a sibling field on the same row that carries the row's taxon
      CURIE, for artifacts holding more than one species.

  pattern:
    description: >-
      A regular expression every identifier of this kind and taxon matches.
      The token {taxid} is replaced with the numeric taxon id at check time,
      so one row can describe a taxon-suffixed namespace.

  ignore_case:
    description: Whether the pattern is matched case-insensitively.
    range: boolean

  example:
    description: One real identifier from this namespace, for the reader.

  name:
    description: A short handle a field can name in resolves_in.
    identifier: true

  artifact:
    description: Path of the shipped JSON artifact, relative to web/.
    required: true

  path:
    description: >-
      Where the values sit inside the artifact. Dotted segments; a bare * selects
      every value of an object, [] selects every element of an array, and @key
      selects an object's keys instead of its values.
    required: true

  split:
    description: >-
      A separator on which a single string value is split into several
      identifiers before checking, for fields that store a joined list.

  resolves_in:
    description: >-
      Catalog names whose union every value must resolve in, case-insensitively.
      Absent means the namespace arms are the only check, and the manifest says
      why in the note.
    range: Catalog
    multivalued: true

  note:
    description: Why this field is declared as it is, in one or two sentences.

enums:

  IdentifierKindEnum:
    description: The kinds of species-scoped identifier the data planes carry.
    permissible_values:
      gene_symbol:
        description: A gene symbol as the field's species names it (HGNC-style for human, Ensembl or NCBI for dog).
      ensembl_gene:
        description: A stable Ensembl gene identifier, whose prefix encodes the species.
      omia_phene:
        description: An OMIA phene identifier, whose suffix after the hyphen is the taxon id.
      annotation_gene:
        description: A species-specific annotation id that is neither a symbol nor an Ensembl id, such as a FlyBase-style gene id.
