{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cheyuwu.com/ai/lab/schema.json",
  "title": "Che-Yu Wu Lab case-wall data",
  "description": "Shapes of the public Lab exports: the year index (/ai/lab/index.json), one year (/ai/lab/{year}.json), every year in one file (/ai/lab/all.json) and the record object they all carry. The vocabularies below are closed: the build rejects a record whose kind, status, datePrecision or dateBasis is not listed here, so a new value is a change to this file first. generated_at is the build time and changes on every build; revision is the stable identity of the content and only changes when the data does.",
  "oneOf": [
    { "$ref": "#/$defs/index" },
    { "$ref": "#/$defs/year" },
    { "$ref": "#/$defs/all" }
  ],
  "$defs": {
    "bilingual": {
      "type": "object",
      "required": ["zh", "en"],
      "properties": {
        "zh": { "type": "string", "minLength": 1 },
        "en": { "type": "string", "minLength": 1 }
      },
      "additionalProperties": false
    },
    "period": {
      "type": "object",
      "required": ["start", "end"],
      "properties": {
        "start": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
        "end": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }
      },
      "additionalProperties": false
    },
    "year_label": { "type": "string", "pattern": "^[0-9]{4}$" },
    "record_id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$",
      "description": "Stable record id, unique within its year. Also the ?id= value of panel_url and the #lab-{id} fragment of record_url."
    },
    "revision": {
      "type": "string",
      "pattern": "^[a-f0-9]{16}$",
      "description": "First 16 hex digits of sha256 over the file content with generated_at and revision removed. Identical data yields an identical revision across builds; this is the identity to compare, not generated_at."
    },
    "kind": {
      "type": "string",
      "enum": [
        "prototype",
        "publication",
        "open-source",
        "exhibition",
        "talk",
        "press",
        "course",
        "music",
        "podcast",
        "nft",
        "workshop",
        "performance",
        "interview",
        "award",
        "ongoing-practice"
      ],
      "description": "What the record is. The catalog's own fifteen categories, unchanged: prototype (a sketch or working piece), publication (a written column or article by the artist), open-source (a public repository), exhibition, talk, press (coverage by others), course, music (a release or track), podcast, nft, workshop, performance, interview, award, ongoing-practice (a practice that has no single date)."
    },
    "status": {
      "type": "string",
      "enum": [
        "published",
        "past-event-listed",
        "artist-recorded",
        "public-ongoing",
        "public-prototype"
      ],
      "description": "Publication or event state, closed set of five. published: a public artefact exists at url. past-event-listed: an event that has taken place and is listed by its organiser or venue (the former completed-public-record and performed-with-a-public-listing cases fold in here). artist-recorded: the only public trace is the artist's own record, such as a repository or post (the former performed value with an artist-recorded-event basis folds in here). public-ongoing: publicly announced and still in progress, including progressive releases, open fundraising and announced exhibitions that have not closed (the former ongoing, artist-announced-ongoing, public-progressive-release and public-fundraising-upcoming values fold in here). public-prototype: a working prototype shown publicly before any release."
    },
    "datePrecision": {
      "type": "string",
      "enum": ["day", "month", "year", "unknown"],
      "description": "How much of date the source supports: day (YYYY-MM-DD), month (YYYY-MM), year (YYYY) or unknown (date is null)."
    },
    "dateBasis": {
      "type": "string",
      "enum": [
        "publication-date",
        "public-event-date",
        "first-public-date-unverified",
        "repository-created-at",
        "artist-recorded-event",
        "release-date",
        "artist-origin-month",
        "fundraising-start",
        "artist-site-date",
        "original-file-date"
      ],
      "description": "Which kind of source date the record carries. publication-date: the date the source page or article was published. public-event-date: the date the organiser or venue lists for the event. first-public-date-unverified: the earliest public appearance the artist could find, not confirmed by a dated source. repository-created-at: the creation timestamp of a public repository (the former repository-creation-date spelling folds in here). artist-recorded-event: the date the artist recorded for an event in a public post or repository (the former artist-record spelling folds in here). release-date: the platform's release date of a track or album. artist-origin-month: the month the artist gives for the origin of a piece. fundraising-start: the day a public fundraising campaign opened. artist-site-date: the date on the artist's own website. original-file-date: the date carried by one of the artist's own original files (a date written inside it, or its file timestamp), published by the artist under /archive/ on this site because no other public record of the piece exists."
    },
    "date": {
      "type": ["string", "null"],
      "pattern": "^[0-9]{4}(-[0-9]{2}(-[0-9]{2})?)?$",
      "description": "The date as the source gives it, trimmed to datePrecision; null when datePrecision is unknown."
    },
    "keyword": { "$ref": "#/$defs/bilingual" },
    "source": {
      "type": "object",
      "required": ["url", "label"],
      "properties": {
        "url": { "type": "string", "pattern": "^https?://" },
        "label": { "type": "string", "minLength": 1 }
      },
      "additionalProperties": false
    },
    "related": {
      "type": "object",
      "required": ["id", "label", "type"],
      "properties": {
        "id": { "$ref": "#/$defs/record_id" },
        "label": { "$ref": "#/$defs/bilingual" },
        "type": { "type": "string", "enum": ["theme", "source"] },
        "sourceUrl": { "type": "string", "pattern": "^https?://" },
        "year": {
          "$ref": "#/$defs/year_label",
          "description": "Set when the related record hangs on another year's wall (/ai/lab/{year}.json); absent means the same wall. Walls are calendar years since 2026-09-24, so a curated tie can cross a boundary."
        }
      },
      "additionalProperties": false
    },
    "image": {
      "type": "object",
      "required": ["src", "alt"],
      "properties": {
        "src": { "type": "string", "minLength": 1 },
        "alt": { "$ref": "#/$defs/bilingual" },
        "credit": { "type": "string" }
      },
      "additionalProperties": false
    },
    "record": {
      "type": "object",
      "description": "One case-wall record, copied verbatim from the published catalog. notes (editorial provenance) is never exported. year is present in all.json and absent in the year file, where the file itself names the year.",
      "required": [
        "id",
        "title",
        "kind",
        "date",
        "datePrecision",
        "dateBasis",
        "status",
        "summary",
        "keywords",
        "url",
        "sources",
        "related",
        "page_zh",
        "page_en",
        "record_url",
        "panel_url"
      ],
      "properties": {
        "year": { "$ref": "#/$defs/year_label" },
        "id": { "$ref": "#/$defs/record_id" },
        "title": { "$ref": "#/$defs/bilingual" },
        "kind": { "$ref": "#/$defs/kind" },
        "date": { "$ref": "#/$defs/date" },
        "datePrecision": { "$ref": "#/$defs/datePrecision" },
        "dateBasis": { "$ref": "#/$defs/dateBasis" },
        "status": { "$ref": "#/$defs/status" },
        "summary": { "$ref": "#/$defs/bilingual" },
        "keywords": { "type": "array", "items": { "$ref": "#/$defs/keyword" } },
        "url": { "type": "string", "minLength": 1, "description": "Primary public link for the record." },
        "sources": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/source" } },
        "related": { "type": "array", "items": { "$ref": "#/$defs/related" } },
        "dateNote": { "$ref": "#/$defs/bilingual" },
        "image": { "$ref": "#/$defs/image" },
        "quote": {
          "type": "array",
          "items": { "type": "string", "enum": ["title", "summary"] },
          "description": "Prose-gate exemption: the named fields are quotations and are not rewritten to house style."
        },
        "page_zh": { "type": "string", "pattern": "^https://cheyuwu\\.com/zh/strata/[0-9]{4}/\\?id=[a-z0-9][a-z0-9-]*$", "description": "Chinese year page with this record opened in the detail panel (client-side)." },
        "page_en": { "type": "string", "pattern": "^https://cheyuwu\\.com/strata/[0-9]{4}/\\?id=[a-z0-9][a-z0-9-]*$", "description": "English year page with this record opened in the detail panel (client-side)." },
        "record_url": { "type": "string", "pattern": "^https://cheyuwu\\.com/zh/strata/[0-9]{4}/#lab-[a-z0-9][a-z0-9-]*$", "description": "Citable URL: the Chinese year page with the fragment of this record's <article id=\"lab-{id}\">, which resolves in the prerendered HTML without JavaScript." },
        "panel_url": { "type": "string", "pattern": "^https://cheyuwu\\.com/zh/strata/[0-9]{4}/\\?id=[a-z0-9][a-z0-9-]*$", "description": "Same as page_zh: opens the detail panel; needs JavaScript." }
      },
      "additionalProperties": false
    },
    "index_entry": {
      "type": "object",
      "required": [
        "year",
        "period",
        "label",
        "records",
        "url_zh",
        "url_en",
        "json",
        "markdown",
        "revision",
        "schema_url",
        "all_json",
        "all_markdown",
        "generated_at",
        "contract"
      ],
      "properties": {
        "year": { "$ref": "#/$defs/year_label" },
        "period": { "$ref": "#/$defs/period" },
        "label": { "$ref": "#/$defs/bilingual" },
        "records": { "type": "integer", "minimum": 1 },
        "url_zh": { "type": "string", "pattern": "^https://cheyuwu\\.com/zh/strata/[0-9]{4}/$" },
        "url_en": { "type": "string", "pattern": "^https://cheyuwu\\.com/strata/[0-9]{4}/$" },
        "json": { "type": "string", "pattern": "^https://cheyuwu\\.com/ai/lab/[0-9]{4}\\.json$" },
        "markdown": { "type": "string", "pattern": "^https://cheyuwu\\.com/ai/lab/[0-9]{4}\\.md$" },
        "revision": { "$ref": "#/$defs/revision", "description": "The revision of that year's file." },
        "schema_url": { "const": "https://cheyuwu.com/ai/lab/schema.json" },
        "all_json": { "const": "https://cheyuwu.com/ai/lab/all.json" },
        "all_markdown": { "const": "https://cheyuwu.com/ai/lab/all.md" },
        "generated_at": { "type": "string" },
        "contract": { "const": "https://cheyuwu.com/ai/contract.md" }
      },
      "additionalProperties": false
    },
    "index": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/$defs/index_entry" },
      "description": "/ai/lab/index.json: one entry per published year, newest first. Every entry also names all.json, all.md and this schema."
    },
    "year": {
      "type": "object",
      "description": "/ai/lab/{year}.json",
      "required": [
        "schemaVersion",
        "generated_at",
        "revision",
        "schema_url",
        "contract",
        "index",
        "year",
        "label",
        "records",
        "url_zh",
        "url_en",
        "json",
        "markdown",
        "period",
        "items"
      ],
      "properties": {
        "schemaVersion": { "const": 1 },
        "generated_at": { "type": "string" },
        "revision": { "$ref": "#/$defs/revision" },
        "schema_url": { "const": "https://cheyuwu.com/ai/lab/schema.json" },
        "contract": { "const": "https://cheyuwu.com/ai/contract.md" },
        "index": { "const": "https://cheyuwu.com/ai/lab/index.json" },
        "year": { "$ref": "#/$defs/year_label" },
        "label": { "$ref": "#/$defs/bilingual" },
        "records": { "type": "integer", "minimum": 1 },
        "url_zh": { "type": "string", "pattern": "^https://cheyuwu\\.com/zh/strata/[0-9]{4}/$" },
        "url_en": { "type": "string", "pattern": "^https://cheyuwu\\.com/strata/[0-9]{4}/$" },
        "json": { "type": "string", "pattern": "^https://cheyuwu\\.com/ai/lab/[0-9]{4}\\.json$" },
        "markdown": { "type": "string", "pattern": "^https://cheyuwu\\.com/ai/lab/[0-9]{4}\\.md$" },
        "period": { "$ref": "#/$defs/period" },
        "items": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/record" } }
      },
      "additionalProperties": false
    },
    "all": {
      "type": "object",
      "description": "/ai/lab/all.json: every record of every year, newest year first, then in the year file's own order. Each item carries its year.",
      "required": [
        "schemaVersion",
        "generated_at",
        "revision",
        "schema_url",
        "contract",
        "index",
        "years",
        "records",
        "json",
        "markdown",
        "items"
      ],
      "properties": {
        "schemaVersion": { "const": 1 },
        "generated_at": { "type": "string" },
        "revision": { "$ref": "#/$defs/revision" },
        "schema_url": { "const": "https://cheyuwu.com/ai/lab/schema.json" },
        "contract": { "const": "https://cheyuwu.com/ai/contract.md" },
        "index": { "const": "https://cheyuwu.com/ai/lab/index.json" },
        "years": { "type": "integer", "minimum": 1 },
        "records": { "type": "integer", "minimum": 1 },
        "json": { "const": "https://cheyuwu.com/ai/lab/all.json" },
        "markdown": { "const": "https://cheyuwu.com/ai/lab/all.md" },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "allOf": [
              { "$ref": "#/$defs/record" },
              { "required": ["year"] }
            ]
          }
        }
      },
      "additionalProperties": false
    }
  }
}
