{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://vibed.host/schema/v1.json",
  "title": "vibed.json (contract v1)",
  "description": "Manifest of a vibed app: a vibed.json file in the project folder, or inline in index.html as <script type=\"application/vibed+json\">…</script> (not both). Unknown fields are errors.",
  "type": "object",
  "additionalProperties": false,
  "required": ["vibed", "name"],
  "properties": {
    "$schema": { "type": "string" },
    "vibed": {
      "const": 1,
      "description": "Major version of the vibed contract. Must match the SDK path /_vibed/v1/."
    },
    "name": {
      "type": "string",
      "pattern": "^(?!.*--)[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$",
      "description": "Subdomain: the app runs at https://<name>.vibed.host. Lowercase letters, digits, single hyphens, 3–32 characters."
    },
    "title": {
      "type": "string",
      "maxLength": 60,
      "description": "Human-readable name shown in the join dialog and dashboard."
    },
    "dir": {
      "type": "string",
      "default": ".",
      "pattern": "^(?!/)(?!.*\\.\\.)[^\\\\]+$",
      "description": "Folder to upload, relative to vibed.json, e.g. \"dist\" after a build."
    },
    "access": {
      "enum": ["public", "invite", "owner"],
      "description": "Who may use the app's data. public: anyone with the link. invite: only with a room code or room link. owner: only the owner. Required as soon as collections, rooms or ai are used."
    },
    "rooms": {
      "enum": ["owner", "anyone"],
      "default": "owner",
      "description": "Who may create rooms. anyone: every guest (they can then invite others)."
    },
    "retention": {
      "type": "string",
      "pattern": "^([1-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-5])d$",
      "default": "30d",
      "description": "Documents created by guests are deleted this long after their last change (1d–365d). Rooms expire after the same time without activity. The owner's own documents never expire."
    },
    "collections": {
      "type": "object",
      "maxProperties": 50,
      "propertyNames": { "pattern": "^[a-z][a-zA-Z0-9_]{0,39}$" },
      "additionalProperties": { "$ref": "#/$defs/collection" },
      "description": "Collections this app uses. Every name used in the code must be declared here."
    },
    "ai": {
      "type": "object",
      "maxProperties": 10,
      "propertyNames": { "pattern": "^[a-z][a-zA-Z0-9_]{0,39}$" },
      "additionalProperties": { "$ref": "#/$defs/assistant" },
      "description": "AI assistants, used with vibed.ai(name). Provider, key and budget are set by the owner in the dashboard."
    },
    "allow": {
      "type": "array",
      "maxItems": 20,
      "uniqueItems": true,
      "items": { "$ref": "#/$defs/origin", "not": { "enum": ["https://fonts.googleapis.com", "https://fonts.gstatic.com"] } },
      "description": "External origins the app may load from or connect to (fonts, images, APIs). Everything else is blocked. Google Fonts cannot be allowed."
    }
  },
  "allOf": [
    {
      "if": {
        "anyOf": [
          { "required": ["collections"] },
          { "required": ["ai"] },
          { "required": ["rooms"] }
        ]
      },
      "then": { "required": ["access"] }
    }
  ],
  "$defs": {
    "origin": {
      "type": "string",
      "pattern": "^https://[a-z0-9.-]+(:[0-9]{1,5})?$",
      "description": "An https origin without path, e.g. https://api.example.org"
    },
    "collection": {
      "type": "object",
      "additionalProperties": false,
      "required": ["preset"],
      "properties": {
        "preset": {
          "enum": ["owner", "private", "inbox", "board", "shared", "published"],
          "description": "owner: only the owner reads and writes (solutions, secret content). private: everyone sees only their own, the owner cannot read them. inbox: everyone sees their own, the owner sees all. board: everyone sees all, changes only their own. shared: everyone sees and changes all. published: everyone reads, only the owner writes."
        },
        "scope": {
          "enum": ["app", "room"],
          "default": "app",
          "description": "room: every room has its own separate data. Use vibed.room(id).collection(name). Works with every preset."
        },
        "onlyMine": {
          "type": "boolean",
          "default": false,
          "description": "true: documents can only be written with mine() / mine(key) — at most one per person and key. Makes votes and likes tamper-proof."
        },
        "schema": {
          "type": "object",
          "description": "Optional JSON Schema subset checked on every write: type, properties, required, enum, minimum, maximum, minLength, maxLength, items, maxItems, additionalProperties."
        },
        "files": {
          "type": "object",
          "additionalProperties": false,
          "description": "Makes this a file collection: use upload(file, data).",
          "properties": {
            "maxSize": {
              "type": "string",
              "pattern": "^([1-9]|[1-4][0-9]|50)MB$|^[1-9][0-9]{0,2}KB$",
              "default": "10MB"
            },
            "accept": {
              "type": "array",
              "items": { "type": "string", "pattern": "^[a-z]+/([a-z0-9.+-]+|\\*)$" },
              "description": "Allowed MIME types, e.g. [\"image/*\", \"application/pdf\"]."
            }
          }
        }
      }
    },
    "assistant": {
      "type": "object",
      "additionalProperties": false,
      "required": ["model", "system"],
      "properties": {
        "model": {
          "enum": ["fast", "smart"],
          "description": "Model class. The owner maps it to a provider and model in the dashboard."
        },
        "system": {
          "type": "string",
          "maxLength": 8000,
          "description": "System prompt. Added by the server to every request; the app cannot change it."
        },
        "maxTokens": { "type": "integer", "minimum": 1, "maximum": 4000, "default": 1000 },
        "perGuestPerDay": {
          "type": "integer",
          "minimum": 0,
          "maximum": 1000,
          "description": "Max requests per guest per day. 0 = only the owner may use this assistant."
        }
      }
    }
  }
}
