{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Cauce engine configuration",
  "type": "object",
  "required": [
    "project",
    "mode",
    "workspaceRoots",
    "runner"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "minLength": 1
    },
    "cauceVersion": {
      "type": "string",
      "minLength": 1
    },
    "project": {
      "type": "string",
      "minLength": 1
    },
    "mode": {
      "enum": [
        "embedded",
        "sidecar",
        "toolkit"
      ]
    },
    "workspaceRoots": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "name",
          "path"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "path": {
            "type": "string",
            "minLength": 1
          },
          "verify": {
            "type": "string",
            "description": "Comando que corre la puerta de esta raíz —pruebas, lint, typecheck, build—, tal como se invoca desde ella. Sin él, quien verifica tiene que descubrirlo leyendo el repositorio en cada tarea, y eso es trabajo de modelo repetido para siempre sobre una respuesta que no cambia."
          },
          "scope": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "Qué rutas lee la puerta de esta raíz, relativas a ella, con * ? y **. Sirve para que un archivo sucio que el gate no va a abrir no fuerce la copia del índice. Sin él, cualquier diferencia entre árbol e índice la fuerza, que es el comportamiento de siempre."
          }
        },
        "additionalProperties": false
      }
    },
    "writableOutsideRoots": {
      "type": "array",
      "description": "Rutas que el proyecto declara escribibles sin ser raíces de código —la memoria del runner, un scratchpad, un directorio de salida—. Sólo levantan el límite de workspace-boundary: no entran a scan ni al inventario de credenciales, que recorren workspaceRoots. `~` se expande a la casa del usuario y el resto se resuelve contra la raíz de ops. `check` las muestra resueltas en cada corrida, porque una exención que no se ve es un límite que ya no existe.",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "runner": {
      "type": "object",
      "required": [
        "maxTaskHours",
        "humanCheckpointBetweenMilestones",
        "commitPerTask",
        "allowPush"
      ],
      "properties": {
        "maxTaskHours": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "humanCheckpointBetweenMilestones": {
          "type": "boolean"
        },
        "commitPerTask": {
          "type": "boolean"
        },
        "allowPush": {
          "type": "boolean"
        },
        "pushToLiveBranches": {
          "type": "array",
          "description": "Las ramas vivas —main, master y la rama por defecto de cada remoto— en las que se puede publicar. Sin nombrarla acá, ni allowPush ni una orden en el chat llegan a una rama viva. Nombres exactos, sin patrones.",
          "items": {
            "type": "string",
            "pattern": "^[^\\s*?\\[]+$"
          },
          "uniqueItems": true
        }
      },
      "additionalProperties": false
    },
    "migrations": {
      "type": "object",
      "description": "Qué cuenta como migración para el guard `migrations`. Sin esto sólo juzga `.sql`, así que en un proyecto TypeORM, Prisma, Django, Rails o Alembic el guard no mira nada.",
      "additionalProperties": false,
      "properties": {
        "extensions": {
          "type": "array",
          "description": "Extensiones, sin el punto y en minúscula. Por defecto [\"sql\"].",
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9]+$"
          },
          "minItems": 1
        }
      }
    },
    "inbox": {
      "type": "object",
      "description": "El aviso de tamaño del INBOX. `check` advierte —nunca falla— cuando `planning/INBOX.md` pasa de este número de líneas: el INBOX es de la persona, y lo que el aviso pide es recorrerlo, no dejar de escribir.",
      "additionalProperties": false,
      "properties": {
        "warnLines": {
          "type": "integer",
          "minimum": 1,
          "description": "Líneas a partir de las cuales `check` avisa. Por defecto 300."
        }
      }
    }
  },
  "additionalProperties": false
}
