{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cloudoak.dev/spec/manifest.schema.json",
  "title": "Cloudoak app manifest",
  "description": "SPDX-License-Identifier: Apache-2.0. Canonical form is JSON (YAML is authoring sugar). Field rules mirror internal/manifest validation.",
  "type": "object",
  "required": ["manifest", "app", "auth"],
  "additionalProperties": false,
  "properties": {
    "manifest": {"const": 1},
    "app": {
      "type": "object",
      "required": ["id", "name", "version", "publisher"],
      "additionalProperties": false,
      "properties": {
        "id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9.\\-]{1,126}[a-z0-9]$", "allOf": [{"pattern": "\\."}], "description": "Reverse-DNS: at least one dot (com.example.notes)."},
        "name": {"type": "string", "minLength": 1},
        "version": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"},
        "publisher": {"type": "string", "pattern": "^[a-z0-9][a-z0-9\\-]{0,62}$"},
        "homepage": {"type": "string", "pattern": "^https://"},
        "min_node": {"type": "string"},
        "min_client": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"}
      }
    },
    "auth": {
      "type": "object",
      "description": "Where sign-in may return to and which web origins may call the node for this app. Exact matches only.",
      "required": ["redirect_uris"],
      "additionalProperties": false,
      "properties": {
        "redirect_uris": {
          "type": "array",
          "minItems": 1,
          "maxItems": 20,
          "uniqueItems": true,
          "items": {"type": "string", "pattern": "^(https://[^#]+|http://(localhost|127\\.0\\.0\\.1|\\[::1\\])(:[0-9]+)?(/[^#]*)?|[a-z][a-z0-9+\\-]*(\\.[a-z0-9+\\-]+)+:[^#]*)$"}
        },
        "origins": {
          "type": "array",
          "maxItems": 20,
          "uniqueItems": true,
          "items": {"type": "string", "pattern": "^(https://[a-z0-9.\\-]+(:[0-9]+)?|http://(localhost|127\\.0\\.0\\.1|\\[::1\\])(:[0-9]+)?)$"}
        }
      }
    },
    "scopes": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "description"],
        "additionalProperties": false,
        "properties": {
          "id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9.:\\-_]{0,63}$"},
          "description": {"type": "string", "minLength": 1}
        }
      }
    },
    "tables": {
      "type": "object",
      "patternProperties": {
        "^[a-z][a-z0-9_]{0,62}$": {"$ref": "#/$defs/table"}
      },
      "additionalProperties": false
    },
    "collections": {
      "type": "object",
      "patternProperties": {
        "^[a-z][a-z0-9_]{0,62}$": {"$ref": "#/$defs/collection"}
      },
      "additionalProperties": false
    },
    "vectors": {
      "type": "object",
      "patternProperties": {
        "^[a-z][a-z0-9_]{0,62}$": {"$ref": "#/$defs/vector"}
      },
      "additionalProperties": false
    },
    "buckets": {
      "type": "object",
      "patternProperties": {
        "^[a-z][a-z0-9_]{0,62}$": {"$ref": "#/$defs/bucket"}
      },
      "additionalProperties": false
    },
    "migrations": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["from", "to", "steps"],
        "additionalProperties": false,
        "properties": {
          "from": {"type": "string", "minLength": 1},
          "to": {"type": "string", "minLength": 1},
          "steps": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "required": ["op", "table", "to"],
                  "additionalProperties": false,
                  "properties": {
                    "op": {"const": "rename_table"},
                    "table": {"type": "string"},
                    "to": {"type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$"}
                  }
                },
                {
                  "type": "object",
                  "required": ["op", "table", "from", "to"],
                  "additionalProperties": false,
                  "properties": {
                    "op": {"const": "rename_column"},
                    "table": {"type": "string"},
                    "from": {"type": "string"},
                    "to": {"type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$"}
                  }
                },
                {
                  "type": "object",
                  "required": ["op", "table", "column"],
                  "additionalProperties": false,
                  "properties": {
                    "op": {"const": "drop_column"},
                    "table": {"type": "string"},
                    "column": {"type": "string"},
                    "deprecated_in": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"}
                  }
                },
                {
                  "type": "object",
                  "required": ["op", "table"],
                  "additionalProperties": false,
                  "properties": {
                    "op": {"const": "drop_table"},
                    "table": {"type": "string"},
                    "deprecated_in": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"}
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "$defs": {
    "permissions": {
      "type": "object",
      "required": ["read", "create", "update", "delete"],
      "additionalProperties": false,
      "properties": {
        "read": {"$ref": "#/$defs/readRules"},
        "create": {"$ref": "#/$defs/rules"},
        "update": {"$ref": "#/$defs/rules"},
        "delete": {"$ref": "#/$defs/rules"}
      }
    },
    "rules": {
      "type": "array",
      "minItems": 1,
      "items": {"type": "string", "pattern": "^(owner|authenticated|none)$"}
    },
    "readRules": {
      "description": "Who reads: the row's owner, or nobody. \"authenticated\" opens no read.",
      "type": "array",
      "minItems": 1,
      "items": {"type": "string", "pattern": "^(owner|none)$"}
    },
    "column": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {"enum": ["text", "int", "bigint", "float", "bool", "timestamp", "date", "uuid", "json", "enum", "ref"]},
        "required": {"type": "boolean"},
        "max": {"type": "integer", "minimum": 0},
        "unique": {"type": "boolean"},
        "to": {"type": "string"},
        "values": {"type": "array", "items": {"type": "string"}},
        "deprecated_in": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"}
      }
    },
    "table": {
      "type": "object",
      "required": ["columns", "permissions"],
      "additionalProperties": false,
      "properties": {
        "deprecated_in": {"type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.\\-]+)?$"},
        "columns": {
          "type": "object",
          "minProperties": 1,
          "patternProperties": {
            "^[a-z][a-z0-9_]{0,62}$": {"$ref": "#/$defs/column"}
          },
          "additionalProperties": false
        },
        "indexes": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["columns"],
            "additionalProperties": false,
            "properties": {
              "columns": {"type": "array", "minItems": 1, "items": {"type": "string"}}
            }
          }
        },
        "permissions": {"$ref": "#/$defs/permissions"}
      }
    },
    "collection": {
      "type": "object",
      "required": ["permissions"],
      "additionalProperties": false,
      "properties": {
        "indexes": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["path"],
            "additionalProperties": false,
            "properties": {
              "path": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_]{0,62}$"},
              "type": {"enum": ["text", "int", "float", "bool", "timestamp"]}
            }
          }
        },
        "permissions": {"$ref": "#/$defs/permissions"}
      }
    },
    "vector": {
      "type": "object",
      "required": ["dimensions", "metric", "permissions"],
      "additionalProperties": false,
      "properties": {
        "dimensions": {"type": "integer", "minimum": 1, "maximum": 4096},
        "metric": {"enum": ["cosine", "l2", "ip"]},
        "hnsw": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "m": {"type": "integer", "minimum": 0},
            "ef_construction": {"type": "integer", "minimum": 0}
          }
        },
        "permissions": {"$ref": "#/$defs/permissions"}
      }
    },
    "bucket": {
      "type": "object",
      "required": ["max_size", "allowed_types", "permissions"],
      "additionalProperties": false,
      "properties": {
        "max_size": {"type": "string", "minLength": 1},
        "allowed_types": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 3}},
        "permissions": {"$ref": "#/$defs/permissions"}
      }
    }
  }
}
