{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://runninglake.com/schema/v1alpha1/columnmask.json",
  "title": "ColumnMask",
  "description": "A ColumnMask as RunningLake's configuration repository declares it, at policies/columnmasks/<name>.yaml. Generated from the Go types the control plane decodes this file into, so it checks the shape the gate reads — field names and types, and which fields may exist. The gate also applies rules no schema can express — required fields inside spec among them — so a file this schema accepts can still be refused by `rl diff`. Take #/properties/spec for the spec alone.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "apiVersion",
    "kind",
    "metadata"
  ],
  "properties": {
    "apiVersion": {
      "type": "string",
      "const": "runninglake.io/v1alpha1",
      "description": "The config schema's version, which is independent of the HTTP API's (rule unsupported-api-version)."
    },
    "kind": {
      "type": "string",
      "const": "ColumnMask",
      "description": "The kind this file declares. A kind that disagrees with the file's path is refused (rule kind-mismatch)."
    },
    "metadata": {
      "required": [
        "name"
      ],
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^[a-z0-9_-]{1,63}$",
          "description": "The resource's name, which is also its path segment. It must be the name this file's path gives it (rule name-mismatch)."
        },
        "labels": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "annotations": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      }
    },
    "spec": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": false,
      "properties": {
        "on": {
          "description": "On is the table the column is in: a layer, the schema in it when the table sits in one, and the table. A table nested deeper than a schema cannot be named yet.",
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": false,
          "properties": {
            "layer": {
              "type": [
                "string",
                "null"
              ]
            },
            "schema": {
              "type": [
                "string",
                "null"
              ]
            },
            "table": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        },
        "column": {
          "description": "Column is the name of the column the mask is to cover, once something applies it, which nothing does yet, as the table spells it: letters, digits, underscore, hyphen and spaces between words (a dot would name a nested field, which cannot be masked yet). At most one mask covers a column, compared without regard to case. A column the table's declared schema does not have is refused, naming the columns it does have; a table that declares no schema (its columns come from the catalog) is not checked.",
          "type": [
            "string",
            "null"
          ]
        },
        "function": {
          "description": "Function is what a reader who is not exempt is to see in place of the value, once something applies it, which nothing does yet. A closed list, and today it holds one function: nullify, which is meant to read as NULL and needs no key. Others are added by the row that makes the engines apply them, each with its conformance case, which nullify does not have yet.",
          "type": [
            "string",
            "null"
          ]
        },
        "exempt": {
          "description": "Exempt lists the principals the mask does NOT apply to, each user:<email>, role:<one of the five roles> or team:<name>; everyone else is to see the masked value once something applies it, which nothing does yet. A user is an email address and a team a name, in plain characters: no wildcard. A team: principal matches nobody until teams exist and then exempts that team's members. A service or an agent has no form here, so the mask applies to it. Leave it out to mask for everyone.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  }
}
