{
  "$defs": {
    "RawAutoSync": {
      "additionalProperties": false,
      "description": "Nested ``gatekeeping.auto_sync`` settings.",
      "properties": {
        "enabled": {
          "default": false,
          "description": "Auto-sync branches from upstream to gate",
          "title": "Enabled",
          "type": "boolean"
        },
        "branches": {
          "description": "Branch names to auto-sync",
          "items": {
            "type": "string"
          },
          "title": "Branches",
          "type": "array"
        }
      },
      "title": "RawAutoSync",
      "type": "object"
    },
    "RawCredentialsSection": {
      "additionalProperties": false,
      "description": "The ``credentials:`` section of project.yml.\n\nControls whether the project shares the host-wide credential bucket\n(the default \u2014 Claude, Codex, gh, etc. logins are reused across every\nproject) or carves out its own isolated set.  Opting in is destructive\nfor first-run UX: the project starts with no stored credentials and\nhas to be authenticated from scratch via ``terok auth --project``.",
      "properties": {
        "scope": {
          "default": "shared",
          "description": "``shared`` (default) reuses the host-wide credential bucket and the global agent-config mount tree.  ``project`` carves out a private set under the project's own state directory \u2014 agent logins, OAuth tokens, and shared config files live separately from every other project and must be re-authenticated.",
          "enum": [
            "shared",
            "project"
          ],
          "title": "Scope",
          "type": "string"
        }
      },
      "title": "RawCredentialsSection",
      "type": "object"
    },
    "RawGateSection": {
      "additionalProperties": false,
      "description": "The ``gate:`` section of project.yml.\n\n``enabled`` and ``upstream_url`` are orthogonal knobs.  Four combinations:\n\n- ``enabled=True`` + upstream set \u2192 host mirrors upstream; container\n  clones from the mirror (the default; current behaviour).\n- ``enabled=True`` + no upstream \u2192 host initialises a remoteless bare\n  repo; the container still gets a remote to push to.\n- ``enabled=False`` + upstream set \u2192 host never touches the remote;\n  the container fetches directly from upstream.  Useful when the host\n  has no path to the upstream but the container does (firewall,\n  corporate proxy), or when the mirror is simply unwanted.\n- ``enabled=False`` + no upstream \u2192 no git plumbing; the container\n  starts with an empty workspace.\n\nWhen upstream is absent the two classes still differ: ``gatekeeping``\nclones the workspace from the gate's local mirror, while ``online`` \u2014\nwhose purpose is exposing an upstream \u2014 has nothing to expose and the\ncontainer starts with an empty workspace.",
      "properties": {
        "enabled": {
          "default": true,
          "description": "Enable the host-side git gate mirror for this project",
          "title": "Enabled",
          "type": "boolean"
        },
        "path": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Override git gate (mirror) path",
          "title": "Path"
        }
      },
      "title": "RawGateSection",
      "type": "object"
    },
    "RawGatekeepingSection": {
      "additionalProperties": false,
      "description": "The ``gatekeeping:`` section of project.yml.",
      "properties": {
        "staging_root": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Staging directory for gatekeeping builds",
          "title": "Staging Root"
        },
        "expose_external_remote": {
          "default": false,
          "description": "Add upstream URL as ``external`` remote in gatekeeping containers",
          "title": "Expose External Remote",
          "type": "boolean"
        },
        "upstream_polling": {
          "$ref": "#/$defs/RawUpstreamPolling"
        },
        "auto_sync": {
          "$ref": "#/$defs/RawAutoSync"
        }
      },
      "title": "RawGatekeepingSection",
      "type": "object"
    },
    "RawGitSection": {
      "additionalProperties": false,
      "description": "The ``git:`` section of project.yml.",
      "properties": {
        "upstream_url": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Repository URL to clone into task containers",
          "title": "Upstream Url"
        },
        "default_branch": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Default branch name (e.g. ``main``)",
          "title": "Default Branch"
        },
        "human_name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Human name for git committer identity",
          "title": "Human Name"
        },
        "human_email": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Human email for git committer identity",
          "title": "Human Email"
        },
        "authorship": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "How agent/human map to git author/committer. Values: ``agent-human``, ``human-agent``, ``agent``, ``human``",
          "title": "Authorship"
        }
      },
      "title": "RawGitSection",
      "type": "object"
    },
    "RawHooksSection": {
      "additionalProperties": false,
      "description": "Task lifecycle hook commands.\n\nRun on the **host** (not inside the container) around container\nlifecycle events.  Sandbox owns them because the lifecycle events\nthemselves are sandbox-mediated \u2014 the orchestrator just opts into\nbeing notified.  The four hook points map to sandbox-internal\ntransitions:\n\n- ``pre_start``: before the container exists (host-side prep).\n- ``post_start``: after the container is created but possibly not ready.\n- ``post_ready``: after the readiness marker has been observed.\n- ``post_stop``: after the container has stopped (cleanup hook).\n\nEach value is a shell command string, run by the host shell with\nthe orchestrator's environment.  ``None`` means no hook.",
      "properties": {
        "pre_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Pre Start"
        },
        "post_start": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Post Start"
        },
        "post_ready": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Post Ready"
        },
        "post_stop": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Post Stop"
        }
      },
      "title": "RawHooksSection",
      "type": "object"
    },
    "RawImageSection": {
      "additionalProperties": false,
      "description": "The ``image:`` section \u2014 base image, agent roster, Dockerfile snippets.\n\nStrict on its own keys (``extra=\"forbid\"``).  Same shape used in both\nthe global ``config.yml`` (defaults across projects) and per-project\n``project.yml`` (project overrides).",
      "properties": {
        "base_image": {
          "default": "fedora:44",
          "description": "Base container image for builds",
          "title": "Base Image",
          "type": "string"
        },
        "family": {
          "anyOf": [
            {
              "enum": [
                "deb",
                "rpm"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Package family for the L0/L1 build (``deb`` or ``rpm``). Leave unset to auto-detect from *base_image*; set explicitly when the image is outside the known allowlist.",
          "title": "Family"
        },
        "agents": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Comma-separated roster entries to install in L1, or \"all\". Prefix a name with \"-\" to exclude it from the selection (e.g. \"all,-vibe\" or just \"-vibe\" \u2014 both mean \"everything except vibe\").  Inherits from the global config when unset.",
          "title": "Agents"
        },
        "user_snippet_inline": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Inline Dockerfile snippet injected into the project image",
          "title": "User Snippet Inline"
        },
        "user_snippet_file": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Path to a file containing a Dockerfile snippet",
          "title": "User Snippet File"
        }
      },
      "title": "RawImageSection",
      "type": "object"
    },
    "RawProjectSection": {
      "additionalProperties": false,
      "description": "The ``project:`` section of project.yml.",
      "properties": {
        "name": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Unique project name / slug (lowercase, ``[a-z0-9_-]``)",
          "title": "Name"
        },
        "description": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Free-text, human-readable project description (display only)",
          "title": "Description"
        },
        "security_class": {
          "default": "gatekeeping",
          "description": "Security mode: ``gatekeeping`` (gated mirror, default) or ``online`` (direct push)",
          "title": "Security Class",
          "type": "string"
        },
        "isolation": {
          "default": "shared",
          "description": "shared (bind mounts) or sealed (no mounts)",
          "title": "Isolation",
          "type": "string"
        }
      },
      "title": "RawProjectSection",
      "type": "object"
    },
    "RawRunSection": {
      "additionalProperties": false,
      "description": "The ``run:`` section \u2014 \"how the container runs\".\n\nCovers OCI-runtime selection, container resource limits,\ncapability toggles, environment, and lifecycle hooks.  Sandbox\nowns this because every field translates to a podman/runtime\nflag or annotation sandbox emits at launch time.\n\nInheritable in both directions:\n\n- At the **global** level, defaults apply to every project\n  (e.g. set ``runtime: krun`` once to opt the whole installation\n  into microVM isolation).\n- At the **project** level, fields override the global default\n  one-by-one via the orchestrator's merge logic.",
      "properties": {
        "shutdown_timeout": {
          "default": 10,
          "description": "Seconds to wait before SIGKILL on container stop",
          "title": "Shutdown Timeout",
          "type": "integer"
        },
        "gpus": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "GPU passthrough: ``true``, ``\"all\"``, or omit to disable",
          "title": "Gpus"
        },
        "memory": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Podman ``--memory`` value (e.g. ``\"4g\"``, ``\"512m\"``, ``\"4gib\"``, plain ``\"1024\"`` for bytes); ``None`` = unlimited.  Format mirrors what podman accepts \u2014 see ``man podman-run(1)`` --memory.",
          "title": "Memory"
        },
        "cpus": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Podman ``--cpus`` value (e.g. ``\"2.0\"``, ``\"0.5\"``); ``None`` = unlimited.  Non-negative decimal.",
          "title": "Cpus"
        },
        "nested_containers": {
          "default": false,
          "description": "Declares that the project runs podman/docker inside its container. When true, the outer container is launched with ``--security-opt label=nested`` and ``--device /dev/fuse`` so rootless nested containers work under SELinux without disabling labels wholesale.",
          "title": "Nested Containers",
          "type": "boolean"
        },
        "runtime": {
          "anyOf": [
            {
              "enum": [
                "crun",
                "krun"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "OCI runtime: ``crun`` (default) for conventional containers, or ``krun`` for KVM-microVM isolation (experimental).  ``None`` resolves to ``crun`` \u2014 the OCI runtime podman picks by default on every supported distro.  ``krun`` requires the global ``experimental: true`` flag at task launch.",
          "title": "Runtime"
        },
        "timezone": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "IANA timezone for the task container (e.g. ``Europe/Prague``, ``UTC``).  Propagated as ``TZ`` \u2014 resolved against the image's ``tzdata``.  Unset (default) means follow the host's timezone.",
          "title": "Timezone"
        },
        "hooks": {
          "$ref": "#/$defs/RawHooksSection"
        }
      },
      "title": "RawRunSection",
      "type": "object"
    },
    "RawSSHSection": {
      "additionalProperties": false,
      "description": "The ``ssh:`` section \u2014 auth strategy for the host-side gate.\n\nDefault is ``None`` (not ``False``) so ``model_dump(exclude_none=True)``\ncan distinguish *unset* from *explicitly false*.  Higher layers may\nlayer this with a ``project.yml`` ``ssh:`` section of the same shape;\nthe ``None`` sentinel keeps the project layer from stomping the\nglobal value when the user omits it.  The effective ``False`` default\nhappens at the consumer end.",
      "properties": {
        "use_personal": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Opt in to the user's ``~/.ssh`` keys for host-side ``gate-sync``. Default ``false`` \u2014 terok uses only its vault-managed key. Resolves through ConfigStack: ``terok-global config.yml`` \u2192 ``project.yml`` \u2192 CLI ``--use-personal-ssh`` (highest).",
          "title": "Use Personal"
        }
      },
      "title": "RawSSHSection",
      "type": "object"
    },
    "RawShieldProjectSection": {
      "additionalProperties": false,
      "description": "The ``shield:`` section of project.yml.\n\nBoth fields default to ``None`` (inherit from global ``config.yml``).",
      "properties": {
        "drop_on_task_run": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Drop shield (bypass firewall) when task container is created",
          "title": "Drop On Task Run"
        },
        "on_task_restart": {
          "anyOf": [
            {
              "enum": [
                "retain",
                "up"
              ],
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Shield policy on container restart: ``retain`` or ``up``",
          "title": "On Task Restart"
        }
      },
      "title": "RawShieldProjectSection",
      "type": "object"
    },
    "RawTasksSection": {
      "additionalProperties": false,
      "description": "The ``tasks:`` section of project.yml.",
      "properties": {
        "root": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Override task workspace root directory",
          "title": "Root"
        },
        "name_categories": {
          "anyOf": [
            {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Word categories for auto-generated task names (string or list of strings)",
          "title": "Name Categories"
        }
      },
      "title": "RawTasksSection",
      "type": "object"
    },
    "RawUpstreamPolling": {
      "additionalProperties": false,
      "description": "Nested ``gatekeeping.upstream_polling`` settings.",
      "properties": {
        "enabled": {
          "default": true,
          "description": "Poll upstream for new commits",
          "title": "Enabled",
          "type": "boolean"
        },
        "interval_minutes": {
          "default": 5,
          "description": "Polling interval in minutes",
          "title": "Interval Minutes",
          "type": "integer"
        }
      },
      "title": "RawUpstreamPolling",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "Validated structure of a ``project.yml`` file.",
  "properties": {
    "project": {
      "$ref": "#/$defs/RawProjectSection"
    },
    "git": {
      "$ref": "#/$defs/RawGitSection"
    },
    "ssh": {
      "$ref": "#/$defs/RawSSHSection"
    },
    "tasks": {
      "$ref": "#/$defs/RawTasksSection"
    },
    "gate": {
      "$ref": "#/$defs/RawGateSection"
    },
    "gatekeeping": {
      "$ref": "#/$defs/RawGatekeepingSection"
    },
    "run": {
      "$ref": "#/$defs/RawRunSection"
    },
    "shield": {
      "$ref": "#/$defs/RawShieldProjectSection"
    },
    "image": {
      "$ref": "#/$defs/RawImageSection"
    },
    "credentials": {
      "$ref": "#/$defs/RawCredentialsSection"
    },
    "default_agent": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Default agent provider (e.g. ``claude``, ``codex``)",
      "title": "Default Agent"
    },
    "default_provider": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Default LLM endpoint provider the agent routes to (e.g. ``openrouter``)",
      "title": "Default Provider"
    },
    "default_shell": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Default Shell"
    },
    "shared_dir": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Shared directory for multi-agent IPC (``true`` = auto-create under tasks root, or absolute path)",
      "title": "Shared Dir"
    },
    "agent": {
      "additionalProperties": true,
      "description": "Agent configuration dict (model, timeout, instructions, etc.)",
      "title": "Agent",
      "type": "object"
    }
  },
  "title": "terok project.yml",
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema"
}
