{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.kapiworks.com/schemas/package-2.json",
  "title": "Kapi Works package (kapi-works.json)",
  "description": "kapi-works.json: what the package is, its language, how it plays, and the roles, tests, context, links, media and sources it declares. Every text is a text ID from the words file.",
  "type": "object",
  "required": [
    "schema",
    "sdk",
    "id",
    "kind",
    "title",
    "languages",
    "view"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "description": "This file's schema, https://docs.kapiworks.com/schemas/package-2.json, so editors check the file as you type. Kapi ignores it."
    },
    "schema": {
      "const": "kapi.works.package/2",
      "description": "The format, kapi.works.package/2."
    },
    "sdk": {
      "type": "string",
      "pattern": "^2\\.(\\d{1,3})$",
      "anyOf": [
        {
          "pattern": "\\.0*0$"
        },
        {
          "pattern": "\\.0*1$"
        }
      ],
      "description": "The SDK the package needs: 2 and a minor, such as 2.0. Kapi serves up to 2.1."
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
      "description": "The package's ID, the same in every version: lower-case letters, digits and hyphens, up to 64 characters."
    },
    "kind": {
      "enum": [
        "course"
      ],
      "description": "The kind of package: course."
    },
    "courseType": {
      "enum": [
        "training-course",
        "scenario-course",
        "quick-refresher",
        "certificate-course",
        "walkthrough",
        "what-changed",
        "video"
      ],
      "description": "What the course is for, which sets the type checks Kapi runs. Left out, the course is a training-course. Only a course has one."
    },
    "title": {
      "type": "string",
      "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
      "description": "The text ID of the package's title, such as course.title."
    },
    "summary": {
      "type": "string",
      "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
      "description": "The text ID of a one-sentence summary, such as course.summary."
    },
    "label": {
      "type": "string",
      "maxLength": 40,
      "pattern": "^[^\\x00-\\x1f\\x7f]*[^\\s\\x00-\\x1f\\x7f][^\\x00-\\x1f\\x7f]*$",
      "description": "The author's name for this version: one line, up to 40 characters."
    },
    "languages": {
      "description": "The package's language and its words file. A package has one language for now.",
      "type": "object",
      "required": [
        "default",
        "files"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "default": {
          "type": "string",
          "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\\d{3}))?$",
          "description": "The language, as a BCP 47 tag such as en-GB."
        },
        "files": {
          "type": "object",
          "minProperties": 1,
          "maxProperties": 1,
          "propertyNames": {
            "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\\d{3}))?$"
          },
          "additionalProperties": {
            "type": "string",
            "pattern": "^words/(?:[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\\d{3}))?)\\.json$",
            "description": "The path words/<language>.json, for the same language."
          },
          "description": "The words file for the default language, and only that one: { \"en-GB\": \"words/en-GB.json\" }."
        }
      }
    },
    "view": {
      "description": "How the package plays: on Kapi's player for its kind.",
      "type": "object",
      "required": [
        "player",
        "line"
      ],
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "description": "A schema address for editors. Kapi ignores it."
        },
        "player": {
          "enum": [
            "course"
          ],
          "description": "The player, the same as kind."
        },
        "line": {
          "enum": [
            "1.0"
          ],
          "description": "The player's line: 1.0."
        }
      }
    },
    "state": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9/._-]{0,63}$",
      "description": "The version of what the package saves for each person, such as new-hire-welcome/v2."
    },
    "roles": {
      "type": "array",
      "minItems": 1,
      "maxItems": 32,
      "items": {
        "description": "A role, such as manager.",
        "type": "object",
        "required": [
          "id",
          "title"
        ],
        "additionalProperties": false,
        "properties": {
          "$schema": {
            "description": "A schema address for editors. Kapi ignores it."
          },
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "The role's ID, which lessons name in requiredFor."
          },
          "title": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of the role's name."
          },
          "default": {
            "const": true,
            "description": "true on the role for people whose job role isn't mapped to one of these."
          }
        }
      },
      "description": "The roles lessons can be required for, up to 32, with IDs all different. Mark one as the default."
    },
    "assessments": {
      "type": "array",
      "minItems": 1,
      "maxItems": 8,
      "items": {
        "description": "A test, drawn from a pool of held questions.",
        "type": "object",
        "required": [
          "id",
          "title",
          "pool"
        ],
        "additionalProperties": false,
        "properties": {
          "$schema": {
            "description": "A schema address for editors. Kapi ignores it."
          },
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "The test's ID."
          },
          "title": {
            "type": "string",
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of the test's name."
          },
          "pool": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
              "description": "A question ID from questions.json."
            },
            "description": "The IDs of the questions the test draws from. These held questions sit on no page."
          },
          "suggest": {
            "description": "The test settings Kapi suggests. The company decides.",
            "type": "object",
            "required": [
              "draw",
              "passMark"
            ],
            "additionalProperties": false,
            "properties": {
              "$schema": {
                "description": "A schema address for editors. Kapi ignores it."
              },
              "draw": {
                "type": "integer",
                "minimum": 1,
                "maximum": 50,
                "description": "How many questions each attempt draws, up to 50."
              },
              "passMark": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100,
                "description": "The pass mark, as a percentage."
              },
              "attempts": {
                "type": "integer",
                "minimum": 1,
                "maximum": 10,
                "description": "How many attempts a person gets, up to 10."
              }
            }
          }
        }
      },
      "description": "The tests Kapi gives, up to 8, with IDs all different."
    },
    "context": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "company.name": {
          "enum": [
            "required",
            "optional"
          ],
          "description": "The company's name, for {company.name}."
        },
        "door": {
          "enum": [
            "required",
            "optional"
          ],
          "description": "Which door the person learns through: employee, partner or customer."
        },
        "role": {
          "enum": [
            "required",
            "optional"
          ],
          "description": "The person's role, one of roles."
        },
        "learner.firstName": {
          "enum": [
            "required",
            "optional"
          ],
          "description": "The person's first name, for {learner.firstName}, where the company switches it on."
        }
      },
      "patternProperties": {
        "^contacts\\.[a-z0-9][a-z0-9-]{0,63}$": {
          "enum": [
            "required",
            "optional"
          ],
          "description": "A contact the company fills in, such as contacts.it-help, for {contacts.it-help.name}, .email, .phone and .url."
        }
      },
      "description": "What Kapi fills in when the package opens: required if the package needs it, optional if it works without it."
    },
    "links": {
      "type": "array",
      "maxItems": 50,
      "items": {
        "type": "string",
        "pattern": "^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$",
        "description": "A host name, such as www.hse.gov.uk."
      },
      "description": "The hosts the words may link to, up to 50. Every link in the words uses https and one of these hosts."
    },
    "media": {
      "type": "array",
      "items": {
        "description": "A media file.",
        "type": "object",
        "required": [
          "id",
          "file",
          "kind"
        ],
        "additionalProperties": false,
        "properties": {
          "$schema": {
            "description": "A schema address for editors. Kapi ignores it."
          },
          "id": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "The media ID blocks use, such as laptop-setup."
          },
          "file": {
            "type": "string",
            "maxLength": 240,
            "pattern": "^media(?:/[^/\\\\:*?\"<>|\\x00-\\x1f\\x7f.][^/\\\\:*?\"<>|\\x00-\\x1f\\x7f]*)+$",
            "description": "The file's path under media/, such as media/laptop-setup.png, up to 240 characters."
          },
          "kind": {
            "enum": [
              "image",
              "audio",
              "video",
              "font"
            ],
            "description": "What the file is. Kapi reads its first bytes, not its name."
          },
          "sha256": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "The SHA-256 of a picture the company added in Works Studio, as sha256:<64 hex digits>. Kapi keeps that picture, so its file can stay out of the package. A file in the package must have this SHA-256."
          },
          "alt": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^(?=.{1,128}$)[a-z0-9][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*$",
            "description": "The text ID of what the picture shows, in a sentence. A picture needs it unless it's decorative."
          },
          "decorative": {
            "const": true,
            "description": "true for a picture that adds nothing to the words, so screen readers skip it."
          },
          "speech": {
            "type": "boolean",
            "description": "true when the sound or video has speech, which needs captions."
          },
          "captions": {
            "type": "object",
            "propertyNames": {
              "pattern": "^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\\d{3}))?$"
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 240,
              "pattern": "^media(?:/[^/\\\\:*?\"<>|\\x00-\\x1f\\x7f.][^/\\\\:*?\"<>|\\x00-\\x1f\\x7f]*)+$",
              "description": "A WebVTT file under media/."
            },
            "description": "A WebVTT captions file under media/ for each language, such as { \"en-GB\": \"media/welcome.vtt\" }."
          }
        }
      },
      "description": "The package's pictures, sound, video and fonts, with IDs all different. Blocks show them by ID."
    },
    "sources": {
      "type": "array",
      "items": {
        "description": "A document.",
        "type": "object",
        "required": [
          "title"
        ],
        "additionalProperties": false,
        "properties": {
          "$schema": {
            "description": "A schema address for editors. Kapi ignores it."
          },
          "title": {
            "type": "string",
            "maxLength": 200,
            "pattern": "\\S",
            "description": "The document's title, up to 200 characters."
          },
          "sha256": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "The SHA-256 of the document's text, as sha256:<64 hex digits>."
          }
        }
      },
      "description": "The documents the package was made from."
    }
  },
  "allOf": [
    {
      "if": {
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "not": {
              "const": "course"
            }
          }
        }
      },
      "then": {
        "not": {
          "required": [
            "courseType"
          ]
        }
      }
    },
    {
      "if": {
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "const": "course"
          }
        }
      },
      "then": {
        "properties": {
          "view": {
            "type": "object",
            "properties": {
              "player": {
                "const": "course"
              },
              "line": {
                "enum": [
                  "1.0"
                ]
              }
            }
          }
        }
      }
    }
  ]
}
