{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://leji.org/schemas/v1.0/context-manifest.schema.json",
  "title": "Leji context manifest (leji.json)",
  "description": "The machine entrypoint of a Leji context layer. Lives at the repository root as leji.json.",
  "type": "object",
  "required": [
    "leji",
    "name",
    "rootPath",
    "bootProfilePath",
    "categories",
    "owners"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "description": "Optional pointer to this schema, for editor validation and tooling.",
      "type": "string"
    },
    "leji": {
      "type": "string",
      "description": "The Leji spec line this context layer targets, e.g. \"1.0\". The self-naming key follows the OpenAPI convention and identifies the file as a Leji manifest.",
      "pattern": "^\\d+\\.\\d+$"
    },
    "name": {
      "description": "A short, stable identifier for this context layer, e.g. \"acme-billing-context\".",
      "type": "string",
      "minLength": 1
    },
    "description": {
      "description": "One-line summary of what this context layer covers.",
      "type": "string"
    },
    "rootPath": {
      "type": "string",
      "description": "Context root, POSIX path relative to the repository root. Declares where the context layer lives; it does not re-base other paths: all paths in all Leji artifacts resolve from the repository root.",
      "pattern": "^(?!/)(?!\\./)(?!.*(^|/)\\.\\.(/|$))(?!.*\\\\).*$"
    },
    "bootProfilePath": {
      "description": "Path to the boot profile, the agent-agnostic entrypoint every host and person starts from.",
      "type": "string",
      "pattern": "^(?!/)(?!\\./)(?!.*(^|/)\\.\\.(/|$))(?!.*\\\\).+\\.md$"
    },
    "categories": {
      "type": "object",
      "description": "Logical category to path mapping. Keys are the five category identifiers.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "domain": {
          "description": "Where domain content lives: business language and product semantics.",
          "$ref": "#/$defs/categoryMapping"
        },
        "system": {
          "description": "Where system content lives: architecture and the invariants every change respects.",
          "$ref": "#/$defs/categoryMapping"
        },
        "practice": {
          "description": "Where practice content lives: conventions and proven patterns.",
          "$ref": "#/$defs/categoryMapping"
        },
        "governance": {
          "description": "Where governance content lives: agent guardrails and operating rules.",
          "$ref": "#/$defs/categoryMapping"
        },
        "decisions": {
          "description": "Where decision records live: dated records of why things are the way they are.",
          "$ref": "#/$defs/categoryMapping"
        }
      }
    },
    "machine": {
      "description": "Locations of the machine-readable artifacts: index, changelog, profiles, and decision records.",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "indexPath": {
          "description": "Path to the generated context index.",
          "$ref": "#/$defs/relPath"
        },
        "changelogPath": {
          "description": "Path to the machine-readable context changelog.",
          "$ref": "#/$defs/relPath"
        },
        "agentProfilesPath": {
          "description": "Directory holding agent profile documents.",
          "$ref": "#/$defs/relPath"
        },
        "decisionRecordsPath": {
          "description": "Directory holding decision records.",
          "$ref": "#/$defs/relPath"
        }
      }
    },
    "agents": {
      "type": "object",
      "description": "Role to agent-profile binding. Keys are role identifiers (e.g. \"thought-partner\", \"reviewer\"); values are paths to agent profile documents. Protocols engage roles; this map decides who fills them.",
      "additionalProperties": false,
      "patternProperties": {
        "^[a-z0-9]+(-[a-z0-9]+)*$": {
          "$ref": "#/$defs/relPath"
        }
      }
    },
    "owners": {
      "type": "object",
      "description": "Who is accountable for the context layer's health. Owners answer for the context layer staying current and pruned; they do not author or curate it alone (governance.md).",
      "required": [
        "primary"
      ],
      "additionalProperties": false,
      "properties": {
        "primary": {
          "$ref": "#/$defs/owner",
          "description": "The person accountable for the context layer's currency. Ownerless context layers rot."
        },
        "continuity": {
          "$ref": "#/$defs/owner",
          "description": "Optional. A different person who carries the same accountability when the primary is unavailable or steps away; assisted adoptions should name one before outside help leaves. Naming the primary again provides no continuity, and an agent cannot fill it: owners are accountable people."
        }
      }
    },
    "conformance": {
      "description": "The conformance level this context layer claims, checkable by tooling.",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "claimedLevel": {
          "description": "The conformance level claimed: core, indexed, governed, or federated.",
          "enum": [
            "core",
            "indexed",
            "governed",
            "federated"
          ]
        },
        "claimedAt": {
          "description": "ISO 8601 date the claim was last asserted.",
          "$ref": "#/$defs/isoDate"
        }
      }
    },
    "federation": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "mounts": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "path",
              "name",
              "owner"
            ],
            "additionalProperties": false,
            "properties": {
              "path": {
                "$ref": "#/$defs/relPath",
                "description": "Where the sibling layer is mounted in this repository, e.g. \"context/product/\". The sibling's own leji.json lives at this path's root."
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "description": "The sibling layer's name; must match the `name` in the sibling's own manifest."
              },
              "owner": {
                "$ref": "#/$defs/owner",
                "description": "The owner of the sibling layer. Ownership stays with the sibling's team; the host never absorbs its content."
              },
              "role": {
                "type": "string",
                "description": "What this sibling layer carries, e.g. \"product-side context\"."
              },
              "source": {
                "type": "string",
                "description": "Upstream repository URL of the sibling layer. Optional; enables stale-pin reporting when git submodule metadata alone isn't available."
              }
            }
          },
          "description": "Each mount is a docs-only sibling layer at a pinned version, keeping its own repository, ownership, and review gate."
        }
      },
      "description": "Sibling layers mounted into this repository (spec: distribution.md pattern 3). The circle composes ownership; it doesn't centralize it."
    },
    "vendorAdapters": {
      "type": "array",
      "description": "Vendor entrypoint files present in this repository; each must redirect to the boot profile.",
      "items": {
        "$ref": "#/$defs/relPath"
      }
    },
    "docs": {
      "type": "object",
      "description": "Presentation preferences read by `leji docs`. Non-normative convenience configuration; presentation itself is out of normative scope.",
      "additionalProperties": false,
      "properties": {
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535,
          "description": "Preferred local-preview port for `leji docs --serve`. The --port flag overrides; the default is 5354 (LEJI on a phone keypad)."
        }
      }
    }
  },
  "$defs": {
    "relPath": {
      "type": "string",
      "pattern": "^(?!/)(?!\\./)(?!.*(^|/)\\.\\.(/|$))(?!.*\\\\).+$"
    },
    "isoDate": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}([T ].*)?$"
    },
    "owner": {
      "type": "object",
      "required": [
        "name"
      ],
      "additionalProperties": false,
      "properties": {
        "name": {
          "description": "The person's name.",
          "type": "string",
          "minLength": 1
        },
        "contact": {
          "description": "How to reach them, e.g. an email address.",
          "type": "string"
        }
      }
    },
    "categoryMapping": {
      "type": "object",
      "required": [
        "paths"
      ],
      "additionalProperties": false,
      "properties": {
        "paths": {
          "description": "One or more repository-root-relative paths where this category's content lives.",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/relPath"
          }
        }
      }
    }
  }
}
