{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://zaentrum.github.io/schemas/library/v1/defs.schema.json",
  "title": "zaentrum library — shared definitions",
  "description": "Types shared by manifest.json and metadata/metadata.json. A library on storage is the source of truth; databases are caches built by reading it.",
  "$defs": {
    "uuid": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
      "description": "Lower-case RFC 4122 UUID. Identity never changes, so nothing keyed by it is ever renamed."
    },
    "sha256": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$"
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$",
      "description": "RFC 3339 with an upper-case T and Z, as version 2 readers parse it."
    },
    "date": {
      "type": "string",
      "pattern": "^[0-9]{4}(-[0-9]{2}(-[0-9]{2})?)?$",
      "description": "Full date, or year-month, or year when that is all that is known."
    },
    "language": {
      "type": "string",
      "pattern": "^([a-z]{2,3}(-[A-Za-z0-9]{2,8})*|und|zxx|mul)$",
      "description": "BCP 47, or the ISO 639-2 code a media file carried (the playback fields keep the file's code, e.g. 'eng')."
    },
    "relPath": {
      "type": "string",
      "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$)).+$",
      "description": "A relative path, never absolute and never escaping upward. Which folder it is relative to is stated by the field that uses it."
    },
    "rev": {
      "type": "integer",
      "minimum": 1,
      "description": "Per-document revision, incremented by one on every write. Never a timestamp."
    },
    "audit": {
      "type": "object",
      "required": ["rev", "createdAt", "updatedAt"],
      "properties": {
        "rev": { "$ref": "#/$defs/rev" },
        "createdAt": {
          "$ref": "#/$defs/timestamp",
          "description": "When the item entered the library. It orders 'recently added', so it must survive every migration unchanged."
        },
        "createdBy": { "type": "string" },
        "updatedAt": { "$ref": "#/$defs/timestamp" },
        "updatedBy": { "type": "string" }
      }
    },
    "evidence": {
      "type": "object",
      "description": "One observation supporting a derived conclusion, kept so the conclusion can be re-examined without reading the file again.",
      "required": ["signal", "value"],
      "additionalProperties": false,
      "properties": {
        "signal": {
          "enum": ["runtime", "container-title", "stream-title", "folder-name", "filename", "commentary-track", "content-analysis", "encoder-tag", "codec-vs-title", "tmdb", "catalog", "manual"]
        },
        "value": { "type": ["string", "number", "boolean", "object", "array", "null"] },
        "weight": { "type": "number", "minimum": 0, "maximum": 1 },
        "note": { "type": "string" }
      }
    },
    "decision": {
      "type": "object",
      "description": "A conclusion with its confidence and who reached it. A human decision always outranks an inferred one and is never overwritten by automation.",
      "required": ["decidedBy"],
      "properties": {
        "decidedBy": { "enum": ["inferred", "human", "legacy-catalog", "tmdb"] },
        "decidedAt": { "$ref": "#/$defs/timestamp" },
        "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } },
        "review": {
          "type": ["string", "null"],
          "description": "Set when automation could not decide and a person should. Says what to look at."
        }
      }
    },
    "externalIds": {
      "type": "object",
      "description": "Explicit, unambiguous keys into reference databases. Enough to fetch every text and image in metadata/ again.",
      "additionalProperties": false,
      "properties": {
        "tmdbMovie": { "type": "string", "pattern": "^[0-9]+$" },
        "tmdbTv": { "type": "string", "pattern": "^[0-9]+$", "description": "On a series its own TV id; on an episode the parent series' TV id." },
        "tmdbSeason": { "type": "string", "pattern": "^[0-9]+$" },
        "tmdbEpisode": { "type": "string", "pattern": "^[0-9]+$" },
        "tmdbCollection": { "type": "string", "pattern": "^[0-9]+$" },
        "imdb": { "type": "string", "pattern": "^tt[0-9]+$" },
        "tvdb": { "type": "string", "pattern": "^[0-9]+$" }
      }
    },
    "fixity": {
      "type": "object",
      "description": "Proves a file is the same bytes after a move. qh1 is cheap and always present; sha256 of the whole file is filled in by a background job.",
      "required": ["qh1"],
      "additionalProperties": false,
      "properties": {
        "qh1": { "$ref": "#/$defs/sha256", "description": "sha256(first 64 KiB || last 64 KiB || uint64be(size)). Not a content hash." },
        "sha256": { "$ref": "#/$defs/sha256" },
        "sha256At": { "$ref": "#/$defs/timestamp" }
      }
    },
    "essence": {
      "type": "object",
      "description": "What a file or package carries, reduced to the properties that cannot be recovered once lost. Comparing a package's essence with its original's is the check before the original is deleted.",
      "additionalProperties": false,
      "properties": {
        "maxAudioChannels": { "type": "integer", "minimum": 0 },
        "surround": { "type": "boolean", "description": "Any audio track with more than two channels." },
        "losslessAudio": { "type": "boolean" },
        "objectAudio": { "type": "boolean", "description": "Atmos or DTS:X." },
        "maxVideoHeight": { "type": ["integer", "null"] },
        "videoBitDepth": { "type": ["integer", "null"] },
        "hdr10Metadata": { "type": "boolean" },
        "dolbyVision": { "type": "boolean" },
        "stereo3d": { "type": "boolean" },
        "interlaced": { "type": "boolean" },
        "audioLanguages": { "type": "array", "items": { "$ref": "#/$defs/language" }, "uniqueItems": true },
        "subtitleLanguages": { "type": "array", "items": { "$ref": "#/$defs/language" }, "uniqueItems": true },
        "subtitleTracks": { "type": "integer", "minimum": 0 },
        "imageSubtitles": { "type": "boolean" },
        "styledSubtitles": { "type": "boolean" },
        "fonts": { "type": "boolean" },
        "chapters": { "type": "boolean", "description": "The file itself carries chapter marks. A package never does; the version keeps the marks." },
        "commentaryTracks": { "type": "integer", "minimum": 0, "description": "Audio commentaries." },
        "commentarySubtitles": { "type": "integer", "minimum": 0 },
        "audioDescriptionTracks": { "type": "integer", "minimum": 0 },
        "sdhSubtitleLanguages": { "type": "array", "items": { "$ref": "#/$defs/language" }, "uniqueItems": true },
        "forcedSubtitleLanguages": { "type": "array", "items": { "$ref": "#/$defs/language" }, "uniqueItems": true, "description": "Includes signs-and-songs tracks." },
        "closedCaptions": { "type": "boolean" }
      }
    },
    "ownership": {
      "type": "object",
      "description": "Filesystem ownership and permissions as found. The owning uid also records how a file arrived, so it is provenance as much as access control.",
      "additionalProperties": false,
      "properties": {
        "uid": { "type": "integer" },
        "gid": { "type": "integer" },
        "mode": { "type": "string", "pattern": "^0?[0-7]{3,4}$" },
        "acl": { "type": ["string", "null"] },
        "selinux": { "type": ["string", "null"] }
      }
    }
  }
}
