{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://raw.githubusercontent.com/ssheleg/agent-sync/main/agent-sync.schema.json",
  "title": "agent-sync project configuration",
  "description": "Shape only, committed. Identity never lives here: the instance URL and container id live in .env.agent-sync; the token lives in the Observatory vault for the notion backend (1.22.0), else in .env.agent-sync. Neither is ever committed.",
  "type": "object",
  "required": [
    "backend"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string"
    },
    "backend": {
      "type": "string",
      "enum": [
        "outline",
        "fs"
      ],
      "description": "Which adapter holds coordination state. 'fs' is degraded: not the lease authority across machines."
    },
    "leaseTtlSeconds": {
      "type": "integer",
      "minimum": 60,
      "default": 2700
    },
    "renewIntervalSeconds": {
      "type": "integer",
      "minimum": 30,
      "default": 300
    },
    "gated": {
      "type": "boolean",
      "default": true,
      "description": "Whether runs may be reported as enforced. Effective only when the backend is the lease authority."
    },
    "idRegisters": {
      "type": "object",
      "description": "Registers whose next free id must be reserved before it is written to git. Needs an allocator: leaseBackend 'git' (ids from refs/agent-sync/ids/* at the remote, whatever the backend) or a backend that orders writes (outline, notion). `check` refuses registers that neither serves.",
      "additionalProperties": {
        "type": "object",
        "required": [
          "file",
          "nextFreeIdPattern"
        ],
        "additionalProperties": false,
        "properties": {
          "file": {
            "type": "string",
            "description": "Repo-relative path to the register file."
          },
          "nextFreeIdPattern": {
            "type": "string",
            "description": "Regex with one capture group holding the next free number."
          }
        }
      }
    },
    "guardedFiles": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Glob patterns that require a live lease before being written. A submodule lists only its own registers; cross-repository files belong to the parent repository."
    },
    "claimTags": {
      "type": "object",
      "description": "How a lease is written through to the roadmap. See references/roadmap.md.",
      "additionalProperties": {
        "type": "object",
        "required": [
          "mode"
        ],
        "additionalProperties": false,
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "cell"
            ],
            "description": "'cell' edits one cell of the single table row containing the task id."
          },
          "cell": {
            "type": "integer",
            "description": "0-based cell index in that row; negative counts from the end."
          },
          "held": {
            "type": "string",
            "description": "Template for the held value. {prev} is the current cell text, {holder} the run id."
          }
        }
      }
    },
    "gates": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Shell commands run at the pipeline's lint stage; each result is journaled."
    },
    "mirror": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": false
        },
        "sources": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Files or directories rendered one-way into the knowledge store, stamped with the source commit."
        }
      }
    },
    "setupFile": {
      "type": "string",
      "description": "Where the generated setup snapshot is written. Defaults to docs/AGENT_SYNC.md when docs/ exists, else AGENT_SYNC.md."
    },
    "leaseBackend": {
      "type": "string",
      "enum": [
        "local",
        "git"
      ],
      "default": "local",
      "description": "Where a contended lease is decided. 'local' is an atomic file create \u2014 exclusive between processes on one filesystem. 'git' pushes a ref, and the remote's non-fast-forward rule is a real compare-and-swap, exclusive across machines."
    },
    "leaseRemote": {
      "type": "string",
      "default": "origin",
      "description": "Git remote holding the lease refs when leaseBackend is 'git'."
    },
    "settleSeconds": {
      "type": "number",
      "minimum": 0,
      "default": 3.0,
      "description": "Unused by the git and local backends; retained for adapters that must wait for writes to become visible."
    },
    "integrationBranch": {
      "type": "string",
      "description": "The branch work lands on. Claims are written through only there; on any other branch the holder lives in the coordination plane until `merge`. Default: the repository's own default branch."
    },
    "mergeLog": {
      "type": "object",
      "additionalProperties": false,
      "description": "The record of what landed on the integration branch, written by `merge`.",
      "properties": {
        "file": {
          "type": "string",
          "description": "Path to the log, relative to the repository root. Default docs/MERGES.md."
        },
        "retentionDays": {
          "type": "integer",
          "minimum": 1,
          "description": "How many days keep full detail; older entries are compacted to one line on the next write. Default 7."
        }
      }
    },
    "scheduledIntervalSeconds": {
      "type": "integer",
      "minimum": 60,
      "description": "The interval of a scheduled job that stands aside while any lease here is held — Project Observatory's scheduled tick, for one. `status` warns when this run has held a lease longer than it, so a session does not silently turn every tick in that window into a no-op. Absent: no warning."
    }
  }
}
