{
  "$defs": {
    "RawCredentialsSection": {
      "additionalProperties": false,
      "description": "The ``credentials:`` section \u2014 vault routing for proxy DB and agent mounts.",
      "properties": {
        "dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Shared credentials directory (proxy DB, agent config mounts)",
          "title": "Dir"
        },
        "passphrase": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Unsafe headless fallback for the SQLCipher passphrase; only set when no OS keyring or systemd-creds is available.",
          "title": "Passphrase"
        },
        "use_keyring": {
          "default": false,
          "description": "Opt-in switch for the OS keyring tier of the passphrase resolution chain.  Off by default because Linux Secret Service has per-collection (not per-item) ACLs.",
          "title": "Use Keyring",
          "type": "boolean"
        },
        "passphrase_command": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Operator-supplied shell command (e.g. ``pass show terok-sandbox/vault-passphrase``) that prints the SQLCipher passphrase on stdout.  Tokenised with ``shlex.split``; resolver tier slots between OS keyring and the plaintext config-file fallback.  Canonical headless option for hosts without systemd \u2265 257 \u2014 covers ``pass``, ``bw``, ``op``, HashiCorp ``vault``, and the cloud secret-manager CLIs (AWS, GCP, Azure).",
          "title": "Passphrase Command"
        }
      },
      "title": "RawCredentialsSection",
      "type": "object"
    },
    "RawGateServerSection": {
      "additionalProperties": false,
      "description": "The ``gate_server:`` section \u2014 host-side gate listen port + repo dir.",
      "properties": {
        "port": {
          "anyOf": [
            {
              "maximum": 65535,
              "minimum": 1,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gate server listen port",
          "title": "Port"
        },
        "repos_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Override gate repo directory (default: ``state_dir/gate``)",
          "title": "Repos Dir"
        },
        "suppress_systemd_warning": {
          "default": false,
          "description": "Suppress the systemd unit installation suggestion",
          "title": "Suppress Systemd Warning",
          "type": "boolean"
        }
      },
      "title": "RawGateServerSection",
      "type": "object"
    },
    "RawGlobalGitSection": {
      "additionalProperties": false,
      "description": "The ``git:`` section of global config.yml (identity fields only).",
      "properties": {
        "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": "RawGlobalGitSection",
      "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"
    },
    "RawLogsSection": {
      "additionalProperties": false,
      "description": "Global ``logs:`` section.",
      "properties": {
        "partial_streaming": {
          "default": true,
          "description": "Enable typewriter-effect streaming for log viewing",
          "title": "Partial Streaming",
          "type": "boolean"
        }
      },
      "title": "RawLogsSection",
      "type": "object"
    },
    "RawNetworkSection": {
      "additionalProperties": false,
      "description": "The ``network:`` section \u2014 port range for service / container ports.",
      "properties": {
        "port_range_start": {
          "default": 18700,
          "maximum": 65535,
          "minimum": 1024,
          "title": "Port Range Start",
          "type": "integer"
        },
        "port_range_end": {
          "default": 32700,
          "maximum": 65535,
          "minimum": 1024,
          "title": "Port Range End",
          "type": "integer"
        }
      },
      "title": "RawNetworkSection",
      "type": "object"
    },
    "RawPathsSection": {
      "additionalProperties": false,
      "description": "The ``paths:`` section \u2014 umbrella state root and per-purpose overrides.\n\n``root`` is the namespace state root read by every ecosystem package\n(Podman model \u2014 see also `terok_sandbox.paths.umbrella_state_dir`).",
      "properties": {
        "root": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Namespace state root shared by all ecosystem packages (Podman model \u2014 one config, multiple readers)",
          "title": "Root"
        },
        "build_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Build artifacts directory (generated Dockerfiles)",
          "title": "Build Dir"
        },
        "sandbox_live_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Container-writable runtime data (tasks, agent mounts). For hardened installs, mount the target with ``noexec,nosuid,nodev``",
          "title": "Sandbox Live Dir"
        },
        "user_projects_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "User projects directory (per-user project configs)",
          "title": "User Projects Dir"
        },
        "user_presets_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "User presets directory (per-user preset configs)",
          "title": "User Presets Dir"
        },
        "port_registry_dir": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Shared port registry directory for multi-user isolation",
          "title": "Port Registry Dir"
        }
      },
      "title": "RawPathsSection",
      "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"
    },
    "RawServicesSection": {
      "additionalProperties": false,
      "description": "The ``services:`` section \u2014 transport mode for host \u2194 container IPC.",
      "properties": {
        "mode": {
          "default": "socket",
          "enum": [
            "tcp",
            "socket"
          ],
          "title": "Mode",
          "type": "string"
        }
      },
      "title": "RawServicesSection",
      "type": "object"
    },
    "RawShieldSection": {
      "additionalProperties": false,
      "description": "The ``shield:`` section \u2014 egress firewall policy + audit + task lifecycle defaults.",
      "properties": {
        "bypass_firewall_no_protection": {
          "default": false,
          "description": "**Dangerous**: disable egress firewall entirely",
          "title": "Bypass Firewall No Protection",
          "type": "boolean"
        },
        "profiles": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Named shield profiles for per-project firewall rules",
          "title": "Profiles"
        },
        "audit": {
          "default": true,
          "description": "Enable shield audit logging",
          "title": "Audit",
          "type": "boolean"
        },
        "drop_on_task_run": {
          "default": true,
          "title": "Drop On Task Run",
          "type": "boolean"
        },
        "on_task_restart": {
          "default": "retain",
          "enum": [
            "retain",
            "up"
          ],
          "title": "On Task Restart",
          "type": "string"
        }
      },
      "title": "RawShieldSection",
      "type": "object"
    },
    "RawTUISection": {
      "additionalProperties": false,
      "description": "Global ``tui:`` section.",
      "properties": {
        "default_tmux": {
          "default": false,
          "description": "Default to tmux mode when launching the TUI",
          "title": "Default Tmux",
          "type": "boolean"
        },
        "theme": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Textual theme applied at TUI startup (e.g. ``textual-dark``, ``ansi-dark``).  Maintained automatically: picking a theme in the TUI's command palette writes the choice here so it persists across sessions.  Unset \u2014 or a name the installed Textual doesn't know \u2014 keeps the default theme.",
          "title": "Theme"
        },
        "external_editor": {
          "default": true,
          "description": "Open instruction-editing actions in ``$EDITOR`` when it is set, instead of the integrated text editor.  Honoured only on a local-terminal TUI \u2014 the web TUI (``terok-web`` / textual-serve) always uses the integrated editor, as there is no terminal to suspend to.  Set ``false`` to always use the integrated editor.",
          "title": "External Editor",
          "type": "boolean"
        },
        "desktop_entry": {
          "default": "auto",
          "description": "XDG desktop-entry install policy for ``terok setup`` (default: ``auto``).  ``auto`` installs only when ``xdg-utils`` is on PATH and otherwise skips with a hint.  ``skip`` always skips silently \u2014 recommended for headless hosts that will never resolve the launcher.  ``install`` always installs, using the built-in fallback writer when ``xdg-utils`` is missing.",
          "enum": [
            "auto",
            "skip",
            "install"
          ],
          "title": "Desktop Entry",
          "type": "string"
        },
        "container_resync_seconds": {
          "default": 14400,
          "description": "Full container-state resync interval, in seconds (default: 14400 = 4 hours).  The task list is driven by events \u2014 inotify on task metadata plus a podman event stream \u2014 so this periodic resync is only insurance against a missed event, and is deliberately slow (\u00e0 la a Kubernetes informer resync).  Set it low (e.g. ``2``) on a monitor where inotify can't be trusted (network filesystem, or no podman event stream), trading disk activity for fault tolerance; set ``0`` to disable the resync entirely and rely purely on events.",
          "minimum": 0,
          "title": "Container Resync Seconds",
          "type": "integer"
        }
      },
      "title": "RawTUISection",
      "type": "object"
    },
    "RawTasksGlobalSection": {
      "additionalProperties": false,
      "description": "Global ``tasks:`` section.",
      "properties": {
        "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": "RawTasksGlobalSection",
      "type": "object"
    },
    "RawVaultSection": {
      "additionalProperties": false,
      "description": "The ``vault:`` section \u2014 token broker and SSH signer ports.\n\nThe container-side transport was previously configured via\n``vault.transport``; since 0.7.4 it is derived from\n``services.mode`` so the two knobs stay in lockstep (tcp listener\n\u2194 direct routing, socket listener \u2194 socket routing).  Any prior\n``vault.transport:`` line in ``config.yml`` must be removed.",
      "properties": {
        "bypass_no_secret_protection": {
          "default": false,
          "title": "Bypass No Secret Protection",
          "type": "boolean"
        },
        "port": {
          "anyOf": [
            {
              "maximum": 65535,
              "minimum": 1,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Port"
        },
        "ssh_signer_port": {
          "anyOf": [
            {
              "maximum": 65535,
              "minimum": 1,
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Ssh Signer Port"
        }
      },
      "title": "RawVaultSection",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "Validated structure of the global ``config.yml`` file.\n\nComposed from the ecosystem's per-package schemas:\n\n- Sandbox-owned sections (``paths``, ``credentials``, ``vault``,\n  ``gate_server``, ``services``, ``shield``, ``network``, ``ssh``)\n  come from [`SandboxConfigView`][terok_sandbox.config_schema.SandboxConfigView]\n  via [`ExecutorConfigView`][terok_executor.config_schema.ExecutorConfigView].\n- Executor-owned ``image`` comes from\n  [`ExecutorConfigView`][terok_executor.config_schema.ExecutorConfigView].\n- The ``run`` section (including the nested ``hooks``) is\n  inherited transparently from sandbox via the same chain \u2014 the\n  same schema applies at both project and global level, with\n  project values overriding globals per the resolver below.\n- The four terok-owned global sections (``tui``, ``logs``,\n  ``tasks``-global, ``git``-global) are added explicitly.\n\n``extra=\"forbid\"`` flips back on at this top-of-stack layer because\nterok knows every legitimate section.  A typo at the top level\n(``tuii:``) is caught here, even though sandbox / executor would\nhave tolerated it via their ``extra=\"allow\"`` posture.",
  "properties": {
    "credentials": {
      "$ref": "#/$defs/RawCredentialsSection"
    },
    "paths": {
      "$ref": "#/$defs/RawPathsSection"
    },
    "shield": {
      "$ref": "#/$defs/RawShieldSection"
    },
    "services": {
      "$ref": "#/$defs/RawServicesSection"
    },
    "vault": {
      "$ref": "#/$defs/RawVaultSection"
    },
    "gate_server": {
      "$ref": "#/$defs/RawGateServerSection"
    },
    "network": {
      "$ref": "#/$defs/RawNetworkSection"
    },
    "ssh": {
      "$ref": "#/$defs/RawSSHSection"
    },
    "run": {
      "$ref": "#/$defs/RawRunSection"
    },
    "experimental": {
      "default": false,
      "description": "Cross-package opt-in for experimental features.  Gates terok's krun runtime and sandbox's krun-only host-binary prereq probes (``ip``).  Lives on the top level rather than in any one section because it's shared between sandbox, executor, and terok \u2014 the topmost layer (terok) inherits this declaration.",
      "title": "Experimental",
      "type": "boolean"
    },
    "image": {
      "$ref": "#/$defs/RawImageSection"
    },
    "tui": {
      "$ref": "#/$defs/RawTUISection"
    },
    "logs": {
      "$ref": "#/$defs/RawLogsSection"
    },
    "tasks": {
      "$ref": "#/$defs/RawTasksGlobalSection"
    },
    "git": {
      "$ref": "#/$defs/RawGlobalGitSection"
    },
    "default_agent": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Default Agent"
    },
    "default_provider": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Default Provider"
    },
    "default_shell": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Default Shell"
    },
    "agent": {
      "additionalProperties": true,
      "title": "Agent",
      "type": "object"
    }
  },
  "title": "terok config.yml",
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema"
}
