{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.revenexx.com/schema.schema.json",
  "title": "revenexx App Schema (Baseline)",
  "description": "Declarative database structures for a revenexx App, consumed by Baseline (the Revenue Cloud schema engine). Baseline diffs this against the live PostgreSQL catalog and provisions namespaced tables, RLS policies, column GRANTs, a per-app role and PostgREST exposure. `tenant_id` is injected automatically — never declare it. Foreign keys may only reference entities inside this same app (cross-app FKs are rejected). The canonical grammar lives in baseline/docs/openapi.yaml; this schema mirrors it for editor validation and the App Registry validation gate.",
  "type": "object",
  "required": ["entities"],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Reference to this JSON Schema for editor validation and autocompletion."
    },
    "entities": {
      "type": "object",
      "minProperties": 1,
      "description": "Map of entity (table) name to its definition. Names are snake_case ASCII; Baseline namespaces them to {vendor}__{app}__{entity} in the database.",
      "additionalProperties": { "$ref": "#/$defs/entity" },
      "propertyNames": { "pattern": "^[a-z][a-z0-9_]{0,62}$" }
    }
  },
  "$defs": {
    "entity": {
      "type": "object",
      "required": ["columns"],
      "additionalProperties": false,
      "properties": {
        "columns": {
          "type": "object",
          "minProperties": 1,
          "description": "Map of column name to definition. Do NOT declare 'tenant_id' or 'org_id' — they are reserved/injected by Baseline. At least one column must be marked pk: true.",
          "additionalProperties": { "$ref": "#/$defs/column" },
          "propertyNames": { "pattern": "^[a-z][a-z0-9_]{0,62}$" }
        },
        "indexes": {
          "type": "array",
          "description": "Secondary indexes beyond the primary key.",
          "items": { "$ref": "#/$defs/index" }
        },
        "checks": {
          "type": "array",
          "description": "Raw table-level CHECK constraint expressions.",
          "items": { "type": "string" }
        },
        "scopeable": {
          "description": "Entity Scoping Engine opt-in. false (default) = not scoped; true = scopeable by every registered dimension (open-by-default); array of dimension slugs = scopeable by exactly those. Requires a uuid 'id' column.",
          "oneOf": [
            { "type": "boolean" },
            { "type": "array", "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$" } }
          ]
        },
        "staging": {
          "type": "object",
          "required": ["mode"],
          "additionalProperties": false,
          "description": "Bulk Data Plane staging siblings. 'ab' provisions __shadow/__rowstate/__abrollback (needs a uuid id); 'import' provisions __staging; 'import+delta' adds __rowstate for content-hash delta imports.",
          "properties": {
            "mode": { "type": "string", "enum": ["ab", "import", "import+delta"] }
          }
        }
      }
    },
    "column": {
      "type": "object",
      "required": ["type"],
      "additionalProperties": false,
      "properties": {
        "type": {
          "type": "string",
          "description": "PostgreSQL type written verbatim into DDL. Supported: text, integer, bigint, smallint, uuid, boolean, jsonb, json, timestamptz, timestamp, date, time, numeric (or numeric(p,s)), serial, bigserial, bytea, inet, cidr, macaddr, point, real, double precision, text[], integer[], jsonb[]."
        },
        "pk": { "type": "boolean", "default": false, "description": "Part of the primary key." },
        "notNull": { "type": "boolean", "default": false },
        "unique": { "type": "boolean", "default": false },
        "default": { "type": "string", "description": "Raw SQL default expression, e.g. \"now()\", \"gen_random_uuid()\", \"true\", \"'image'\"." },
        "check": { "type": "string", "description": "Raw column-level CHECK expression, e.g. \"length(sku) > 0\"." },
        "references": { "$ref": "#/$defs/reference" },
        "generator": { "$ref": "#/$defs/generator" }
      }
    },
    "reference": {
      "type": "object",
      "required": ["entity", "column"],
      "additionalProperties": false,
      "description": "Foreign key. Must target an entity inside the SAME app — cross-app FKs are rejected by Baseline.",
      "properties": {
        "entity": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$", "description": "Referenced entity (table) name in this app." },
        "column": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,62}$", "description": "Referenced column (typically 'id')." },
        "app": { "type": "string", "description": "Reserved. Cross-app FKs are not allowed; setting this is rejected at apply time." },
        "onDelete": {
          "type": "string",
          "enum": ["cascade", "restrict", "set null", "no action", "set default"],
          "default": "no action"
        }
      }
    },
    "index": {
      "type": "object",
      "required": ["columns"],
      "additionalProperties": false,
      "properties": {
        "columns": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string" },
          "description": "Indexed columns. 'tenant_id' may be included (it is injected by Baseline)."
        },
        "unique": { "type": "boolean", "default": false },
        "method": { "type": "string", "enum": ["btree", "hash", "gin", "gist", "brin", "spgist"], "default": "btree" },
        "where": { "type": "string", "description": "Partial-index predicate, e.g. \"external_identifier IS NOT NULL\"." }
      }
    },
    "generator": {
      "type": "object",
      "required": ["kind"],
      "additionalProperties": false,
      "description": "Bounded, declarative sample-data hint (no arbitrary SQL).",
      "properties": {
        "kind": { "type": "string", "enum": ["sequence", "pick", "range", "decimal", "uuid", "bool", "timestamp", "date", "lorem"] },
        "prefix": { "type": "string", "description": "For 'sequence'/'lorem'." },
        "values": { "type": "array", "minItems": 1, "description": "For 'pick'." },
        "min": { "type": "number", "description": "For 'range' (integer) / 'decimal' (numeric)." },
        "max": { "type": "number", "description": "For 'range' / 'decimal'." },
        "scale": { "type": "integer", "minimum": 0, "maximum": 8, "description": "For 'decimal' (default 2)." }
      }
    }
  }
}
