{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://build.rathvan.com/rathvan.schema.json",
  "title": "rathvan.json",
  "description": "One file describing what to build. Four fields are required; everything else is carried so the file explains itself. A config that drops the reasons leaves the next reader unable to tell a decision from a default.",
  "type": "object",
  "required": [
    "name",
    "slug",
    "package",
    "domain"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Points at this file so an editor gives autocomplete and validation before anything is run."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "Display name, e.g. MedSetu."
    },
    "slug": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]{1,38}[a-z0-9]$",
      "description": "Lowercase and DNS-safe. It becomes a hostname, a bucket name and a database role \u2014 none of which accept what a display name allows."
    },
    "package": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
      "description": "Base Java package, e.g. com.quantaiseed.medsetu."
    },
    "domain": {
      "type": "string",
      "description": "A vocabulary key. Decides every noun in the generated product, so it cannot be defaulted.",
      "examples": [
        "MEDICAL",
        "LEGAL",
        "FINANCE",
        "TAX",
        "EDUCATION",
        "LOGISTICS",
        "PROPERTY",
        "RECRUITMENT",
        "OTHER"
      ]
    },
    "bands": {
      "type": "array",
      "description": "Capability bands to carry over. FOUNDATION is mandatory \u2014 everything else references its tables. Omitting it does not produce a smaller product, it produces a broken one.",
      "items": {
        "enum": [
          "FOUNDATION",
          "SECURITY",
          "INTEGRATION",
          "CORE_PLATFORM",
          "BILLING",
          "PRODUCT"
        ]
      },
      "contains": {
        "const": "FOUNDATION"
      },
      "uniqueItems": true,
      "default": [
        "FOUNDATION",
        "SECURITY",
        "INTEGRATION",
        "CORE_PLATFORM",
        "PRODUCT"
      ]
    },
    "stack": {
      "type": "object",
      "description": "The seven axes. Four are irreversible \u2014 if one is wrong the fix is a rebuild, not a change.",
      "additionalProperties": false,
      "properties": {
        "cloud": {
          "enum": [
            "GCP",
            "AWS",
            "AZURE",
            "SELF_HOSTED"
          ]
        },
        "messaging": {
          "enum": [
            "SCHEDULED",
            "MANAGED_QUEUE",
            "KAFKA"
          ]
        },
        "backend": {
          "enum": [
            "JAVA",
            "PYTHON",
            "NODE",
            "GO"
          ]
        },
        "api": {
          "enum": [
            "REST",
            "GRAPHQL",
            "GRPC"
          ]
        },
        "agent_interface": {
          "enum": [
            "MCP",
            "PLUGIN",
            "BESPOKE"
          ]
        },
        "database": {
          "enum": [
            "POSTGRES",
            "MYSQL",
            "DOCUMENT"
          ]
        },
        "frontend": {
          "enum": [
            "REACT",
            "SERVER_RENDERED",
            "VUE",
            "SVELTE"
          ]
        }
      }
    },
    "capabilities": {
      "type": "array",
      "description": "Capabilities reused rather than rebuilt. Building what the platform already has is the most expensive mistake available here, and it happens silently.",
      "items": {
        "type": "string"
      },
      "uniqueItems": true
    },
    "intake": {
      "type": "object",
      "description": "Every answer given, verbatim. Carried so the file explains itself: chosen and defaulted look identical in a finished repository.",
      "additionalProperties": true
    },
    "obligations": {
      "type": "array",
      "description": "Derived from the answers \u2014 health data forcing a separate data plane, and so on. Carried rather than recomputed, because they are decisions someone accepted.",
      "items": {
        "type": "string"
      }
    },
    "rejected": {
      "type": "array",
      "description": "Suggestions considered and declined. 'Never considered' and 'considered and declined' are different facts, and only one means the customer disagreed.",
      "items": {
        "type": "string"
      }
    },
    "tenant": {
      "type": "object",
      "description": "Whose platform this is. Present so a company can point Rathvan at its own ecosystem rather than this one's \u2014 the capability catalogue, the issue tracker and the source host are all theirs. Absent means the defaults that ship with the platform.",
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "description": "Short identifier, e.g. quantaiseed."
        },
        "name": {
          "type": "string"
        },
        "capabilityCatalogue": {
          "type": "string",
          "description": "URL or path to the tenant's own city map. Their capability names, not the ones that ship here."
        },
        "sourceControl": {
          "enum": [
            "github",
            "gitlab",
            "azure_devops",
            "bitbucket"
          ]
        },
        "issueTracker": {
          "enum": [
            "jira",
            "linear",
            "github_issues",
            "azure_boards",
            "none"
          ]
        },
        "search": {
          "type": "string",
          "description": "Endpoint for the tenant's own search over their corpus."
        },
        "registry": {
          "type": "string",
          "description": "Where the tenant publishes and consumes plugins and MCP servers."
        }
      }
    },
    "vocabulary": {
      "type": "object",
      "description": "Optional. Override the nouns for your domain, or define them for a domain that is not built in. These name tables, classes and routes, so they are applied before anything is generated \u2014 changing them afterwards is a migration.",
      "additionalProperties": false,
      "properties": {
        "professional": {
          "type": "string",
          "minLength": 1,
          "description": "Who does the work, singular. Doctor, Vet, Surveyor."
        },
        "client": {
          "type": "string",
          "minLength": 1,
          "description": "Who it is done for, singular. Patient, Owner, Claimant."
        },
        "practice": {
          "type": "string",
          "minLength": 1,
          "description": "The organisation they work in. Clinic, Firm, Yard."
        },
        "engagement": {
          "type": "string",
          "minLength": 1,
          "description": "One piece of work. Visit, Matter, Survey."
        },
        "credential": {
          "type": "string",
          "minLength": 1,
          "description": "What licenses the professional. GMC, RCVS, Bar Council."
        },
        "authority": {
          "type": "string",
          "minLength": 1,
          "description": "Who issues and polices that credential."
        }
      }
    },
    "taxonomy": {
      "type": "string",
      "description": "Optional. Path to your own vocabulary.yaml, merged over the built-in one by domain key. Use this to add a domain the platform has never heard of. Equivalent to --taxonomy on the command line."
    }
  }
}
