ORCH_PLANE_API_URL=http://plane-app-api-1:8000
# External (browser) web URL of Plane for clickable issue links in notifications
# (ORCH-017). Falls back to ORCH_PLANE_API_URL; a loopback fallback is treated as
# "no web URL" and the Plane link is omitted. Example: https://plane.example.org
ORCH_PLANE_WEB_URL=
ORCH_PLANE_API_TOKEN=
ORCH_PLANE_WORKSPACE_SLUG=
# Webhook secrets are GENERATED PER HOST: python3 scripts/gen_secrets.py
# (ORCH-101 / AC-5: production secrets are NEVER copied to a new host).
ORCH_PLANE_WEBHOOK_SECRET=
ORCH_GITEA_URL=http://localhost:3000
# External (browser) URL of Gitea for clickable Branch/PR links in comments;
# empty -> falls back to ORCH_GITEA_URL.
ORCH_GITEA_PUBLIC_URL=
ORCH_GITEA_TOKEN=
ORCH_GITEA_WEBHOOK_SECRET=
ORCH_GITEA_OWNER=admin
# Per-agent Plane bot tokens (optional): when set, comments are posted under
# the matching bot so Plane shows the real author; empty -> ORCH_PLANE_API_TOKEN.
ORCH_PLANE_BOT_ANALYST=
ORCH_PLANE_BOT_ARCHITECT=
ORCH_PLANE_BOT_DEVELOPER=
ORCH_PLANE_BOT_REVIEWER=
ORCH_PLANE_BOT_TESTER=
ORCH_PLANE_BOT_DEPLOYER=
ORCH_PLANE_BOT_STREAM=
# ORCH-117: sandbox-only fail-closed guard for Plane WRITES from a test/worktree
# process (regression of ORCH-114, where pytest mutated a live prod board issue).
# In the live runtime (uvicorn, no pytest) the guard is a no-op; in a test process
# it BLOCKS every Plane write unless BOTH the opt-in is true AND the target project
# is in the sandbox allowlist. Defaults are SAFE (default-deny): leave both as-is.
#   ORCH_PLANE_TEST_WRITE_ENABLED -> opt-in for REAL Plane writes from a test process.
#     false (default) = no test may write to Plane. NOT a kill-switch for the prod
#     block: even true, only the sandbox allowlist below is writable (a prod write
#     from pytest stays impossible).
#   ORCH_PLANE_TEST_SANDBOX_PROJECTS -> CSV allowlist of sandbox project ids the
#     opt-in may write to. Default = the single SANDBOX project; empty = none.
ORCH_PLANE_TEST_WRITE_ENABLED=false
ORCH_PLANE_TEST_SANDBOX_PROJECTS=8c5a3025-4f9d-4190-b79f-fa06276bb27e
# Telegram live-tracker / alerts (empty -> notifications are logged, not sent).
ORCH_TELEGRAM_BOT_TOKEN=
ORCH_TELEGRAM_CHAT_ID=
# ORCH-6: project registry — JSON array of {plane_project_id, repo,
# work_item_prefix, name}. Empty -> built-in default registry (src/projects.py)
# whose Plane UUIDs belong to the ORIGINAL host. On a NEW host this key is
# MANDATORY (ORCH-101 replication checklist, docs/operations/REPLICATION.md).
ORCH_PROJECTS_JSON=
ORCH_CLAUDE_BIN=/usr/bin/claude
ORCH_DB_PATH=/app/data/orchestrator.db

# ── ORCH-101: host parametrization (replication foundation, ADR-001 D1–D7) ───
# Every host-specific value lives HERE (defaults = the current production host;
# an empty/absent value keeps behaviour 1:1). The same names are read by BOTH
# pydantic Settings (env_file) and docker-compose ${VAR:-default} interpolation
# (compose reads .env/shell, NOT a service's env_file). Full variable map and
# the new-host procedure: docs/operations/REPLICATION.md.
#   AGENT_HOME_DIR -> HOME of all actor subprocesses (agents/finalizer/monitor)
#                     AND the target of the .claude/.claude.json/.ssh mounts AND
#                     Dockerfile ARG APP_HOME (ORCH-040 group moves together).
#   AGENT_GIT_NAME / GIT_EMAIL_DOMAIN -> git identity of agent commits; system
#                     actors keep platform names deploy-finalizer/post-deploy-
#                     monitor under the same domain.
#   STAGING_PORT   -> staging instance port; image_freshness fail-closes when it
#                     equals the prod port (ORCH-058 AC-9 guard).
#   HOST_*         -> host-side sources of the bind mounts (repos, ~/.claude,
#                     ~/.claude.json, ssh keydir, claude-code dist, node binary).
#   RUN_UID/RUN_GID/DOCKER_GID -> container uid:gid + host docker group for
#                     docker.sock access (group_add «МИНА 1», ORCH-040).
ORCH_AGENT_HOME_DIR=/home/slin
ORCH_AGENT_GIT_NAME=claude-bot
ORCH_GIT_EMAIL_DOMAIN=mva154.local
ORCH_STAGING_PORT=8501
ORCH_HOST_REPOS_DIR=/home/slin/repos
ORCH_HOST_CLAUDE_DIR=/home/slin/.claude
ORCH_HOST_CLAUDE_JSON=/home/slin/.claude.json
ORCH_HOST_SSH_DIR=/home/slin/.orchestrator-ssh
ORCH_HOST_CLAUDE_CODE_DIR=/usr/lib/node_modules/@anthropic-ai/claude-code
ORCH_HOST_NODE_BIN=/usr/bin/node
ORCH_RUN_UID=1000
ORCH_RUN_GID=1000
ORCH_DOCKER_GID=999

# ── Agent model / effort / fallback (ORCH-41, validation ORCH-74) ─────────────
# Per-agent LLM model + reasoning effort, resolved by launcher.resolve_agent_*.
# Resolution priority (per agent): project-override (projects_json agent_models/
# agent_efforts) > ORCH_AGENT_MODEL_<AGENT> / ORCH_AGENT_EFFORT_<AGENT> >
# ORCH_AGENT_MODEL_DEFAULT / ORCH_AGENT_EFFORT_DEFAULT > CLI default (no flag).
# The frontmatter `model:` in .openclaw/agents/*.md is DESCRIPTIVE only and is NOT
# read — config below is the single source of truth for the model (ORCH-74 G1).
#
# ORCH-74 (G2): a resolved MODEL name is validated (^claude-…$ format check) before
# it reaches --model. A structurally invalid name (typo, gpt-4, empty) is logged and
# the next valid level is used (in the limit: no --model flag). Forward-compatible:
# a future claude-* version passes without editing any allowlist. EFFORT is validated
# against low|medium|high|xhigh|max (ORCH-41); an invalid effort is dropped.
#
# All 6 agents resolve to claude-opus-4-8 (model-routing G3 NOT enabled). Leave the
# per-agent overrides empty to use the default. Do NOT hardcode the model version
# anywhere except ORCH_AGENT_MODEL_DEFAULT.
ORCH_AGENT_MODEL_DEFAULT=claude-opus-4-8
ORCH_AGENT_MODEL_ANALYST=
ORCH_AGENT_MODEL_ARCHITECT=
ORCH_AGENT_MODEL_DEVELOPER=
ORCH_AGENT_MODEL_REVIEWER=
ORCH_AGENT_MODEL_TESTER=
ORCH_AGENT_MODEL_DEPLOYER=
# Effort split (ORCH-081/ORCH-52h): thinking agents (analyst/architect/reviewer)
# -> high; developer -> xhigh (coding/agentic role, Opus 4.8 canon); mechanical
# agents (tester/deployer) -> medium. NB: an empty ORCH_AGENT_EFFORT_*= no longer
# zeroes the effort — the launcher falls back to a per-role floor (= the config.py
# class-default) so each role still runs at its canonical level (ORCH-081).
ORCH_AGENT_EFFORT_DEFAULT=high
ORCH_AGENT_EFFORT_ANALYST=high
ORCH_AGENT_EFFORT_ARCHITECT=high
ORCH_AGENT_EFFORT_DEVELOPER=xhigh
ORCH_AGENT_EFFORT_REVIEWER=high
ORCH_AGENT_EFFORT_TESTER=medium
ORCH_AGENT_EFFORT_DEPLOYER=medium
# Optional --fallback-model used when the primary is overloaded. Empty -> no flag
# (G4 NOT enabled, ADR-001 ORCH-74: determinism — all agents stay on opus-4-8). A
# non-empty value is validated by the SAME predicate as the model; a typo is dropped.
ORCH_AGENT_FALLBACK_MODEL=

# ── Agent timeout / wall-clock budgets (ORCH-7, raised per-role ORCH-109) ─────
# The in-process watchdog kills a run that exceeds its wall-clock budget
# (SIGTERM -> grace -> SIGKILL, exit_code=-9). _resolve_timeout ladder (highest
# first): OVERRIDES_JSON[agent] > dedicated role key > SECONDS (global default).
#   SECONDS                -> global default budget for every role WITHOUT a raised
#                             key (analyst/architect/tester/deployer).
#   KILL_GRACE_SECONDS     -> pause between SIGTERM and SIGKILL so claude can flush
#                             artifacts before the hard kill.
#   OVERRIDES_JSON         -> optional per-agent override object, e.g.
#                             {"reviewer":3600,"architect":2700}; wins for ANY role.
#                             Malformed JSON -> ignored + WARNING (never-break).
# ORCH-109: the two HEAVY roles get raised dedicated budgets (defaults = prod, so an
# empty .env reproduces prod — ORCH-101 canon). A non-positive value falls back to
# SECONDS + WARNING.
#   DEVELOPER_S            -> developer budget (xhigh, coding/agentic bottleneck), 60m.
#   REVIEWER_S             -> reviewer budget (large diff + high reasoning), 50m.
# CROSS-INVARIANT (ORCH-065): ORCH_REAPER_MAX_RUNNING_S MUST stay > max(budget)+grace;
# it is raised to 5400 in lockstep below (5400 > 3600 + 20 = 3620).
ORCH_AGENT_TIMEOUT_SECONDS=1800
ORCH_AGENT_KILL_GRACE_SECONDS=20
ORCH_AGENT_TIMEOUT_OVERRIDES_JSON=
ORCH_AGENT_TIMEOUT_DEVELOPER_S=3600
ORCH_AGENT_TIMEOUT_REVIEWER_S=3000
# ORCH-042/ORCH-067: live-tracker mode. bump (DEFAULT since ORCH-067) -> on every
# update the old card is deleted and a fresh one is sent silently to the BOTTOM of
# the chat (deleteMessage + sendMessage + repoint), so the current status is always
# the last message in an active chat. edit -> the task card is edited in place
# (editMessageText). One card per task in both modes. Any value other than "bump"
# (incl. empty/garbage) -> edit.
ORCH_TRACKER_MODE=bump
# ORCH-067: best-effort live-overlay for the card status line. The offline core
# (stage -> Plane status, In Review from the brd-clock) always works without network;
# the overlay only fills in branches indistinguishable offline (Needs Input / Blocked /
# Rejected / Cancelled / Deploying / Monitoring after Deploy) by reading the LIVE Plane
# status with a short timeout + per-issue TTL cache. It NEVER blocks the pipeline and
# NEVER raises.
#   LIVE_STATUS         -> kill-switch (false -> offline core only).
#   LIVE_STATUS_TTL_S   -> TTL (seconds) of the per-issue live-uuid cache (hot-path guard).
#   LIVE_STATUS_TIMEOUT_S -> timeout (seconds) of a single live-GET on the render path.
ORCH_TRACKER_LIVE_STATUS=true
ORCH_TRACKER_LIVE_STATUS_TTL_S=60
ORCH_TRACKER_LIVE_STATUS_TIMEOUT_S=3
# ORCH-043: merge-gate (auto-rebase onto current origin/main + re-test + merge-lock)
# on the deploy-staging -> deploy edge. Deterministic sub-gate (no LLM) that catches
# the branch up to the CURRENT origin/main, re-tests it, and serialises merges so two
# green parallel branches can't break main.
#   ENABLED   -> global kill-switch (false -> whole gate is a no-op pass).
#   REPOS     -> CSV of repos where the gate is REAL; empty -> only the self-hosting
#                repo (orchestrator); other repos -> conditional no-op (mirrors ORCH-35).
#   RETEST_TIMEOUT_S -> wall-clock budget for the post-rebase re-test.
#   RETEST_TARGET    -> pytest target for the re-test.
#   LOCK_TIMEOUT_S   -> max merge-lease age before a stale lease is reclaimed.
#   DEFER_DELAY_S    -> delay before re-running the gate when the lock is busy.
#   DEFER_MAX_ATTEMPTS -> defer retries before escalation (avoids livelock).
ORCH_MERGE_GATE_ENABLED=true
ORCH_MERGE_GATE_REPOS=
# ORCH-110 (D5): re-test budget raised 600 -> 900 (74% headroom over the observed
# 516.7s suite). Cross-invariant (ORCH-065/109): keep ORCH_REAPER_MAX_RUNNING_S
# (5400) > Σ(deploy-staging gate-work) + grace if you raise this — see
# docs/work-items/ORCH-110/07-infra-requirements.md.
ORCH_MERGE_RETEST_TIMEOUT_S=900
ORCH_MERGE_RETEST_TARGET=tests/
ORCH_MERGE_LOCK_TIMEOUT_S=300
ORCH_MERGE_DEFER_DELAY_S=60
ORCH_MERGE_DEFER_MAX_ATTEMPTS=5
# ORCH-110: merge-gate re-test infra-timeout tolerance + tree-kill of the
# orchestrator-spawned pytest subprocess (re-test + coverage). Each default = the
# desired prod behaviour; each flag is an independent kill-switch (off ->
# byte-for-byte pre-ORCH-110). The tree-kill grace reuses ORCH_AGENT_KILL_GRACE_SECONDS.
#   SUBPROCESS_TREE_KILL_ENABLED          -> D1: spawn re-test/coverage pytest in its
#       own process group; tree-kill the WHOLE group on timeout (no orphan grandchildren).
#   MERGE_RETEST_INFRA_TOLERANCE_ENABLED  -> D3: a re-test TIMEOUT is a transient
#       (bounded infra-retry, NOT a code-fault rollback to development).
#   MERGE_RETEST_INFRA_MAX_RETRIES        -> D3: infra-retry budget before an infra-alert.
#   MERGE_RETEST_INFRA_RETRY_DELAY_S      -> D3: delay before the staging-deployer re-run.
#   MERGE_RETEST_SKIP_WHEN_CURRENT_ENABLED-> D4: skip the local re-test when the
#       pre-merge rebase was a proven no-op (HEAD already CI/tester/staging-validated).
ORCH_SUBPROCESS_TREE_KILL_ENABLED=true
ORCH_MERGE_RETEST_INFRA_TOLERANCE_ENABLED=true
ORCH_MERGE_RETEST_INFRA_MAX_RETRIES=2
ORCH_MERGE_RETEST_INFRA_RETRY_DELAY_S=120
ORCH_MERGE_RETEST_SKIP_WHEN_CURRENT_ENABLED=true
# ORCH-026 Level A: unconditional pre-merge rebase. With the flag ON (default),
# check_branch_mergeable ALWAYS rebases the branch onto origin/main under the held
# merge-lease (not only when behind) — a deterministic structural anti-phantom on
# the scheduler edge. No-op on an up-to-date branch (rebase keeps HEAD, force-with-
# lease -> "Everything up-to-date", CI not triggered). Scope = ORCH_MERGE_GATE_REPOS.
#   PREMERGE_REBASE_ALWAYS=false -> strictly pre-ORCH-026 (rebase only when behind).
ORCH_PREMERGE_REBASE_ALWAYS=true
# ORCH-026 Level B: declarative task dependencies ("B waits for A"). claim_next_job
# gates jobs whose depends-on tasks are not yet 'done' (additive job_deps table,
# NOT EXISTS) WITHOUT occupying a max_concurrency slot. Inert on an empty job_deps.
#   TASK_DEPS_ENABLED=false -> claim query is 1:1 the ORCH-1 query (no gate).
#   TASK_DEPS_SOURCE=db|plane|hybrid -> declaration source; db (default) never calls
#     Plane on the hot path; plane/hybrid ingest Plane `blocked-by` relations and
#     cache them into job_deps (the scheduler then reads only the DB).
ORCH_TASK_DEPS_ENABLED=true
ORCH_TASK_DEPS_SOURCE=db
# ORCH-088 (Stage 1, serial e2e): per-repo serial gate. A NEW task's analyst-job does
# NOT enter analysis (no branch cut, no analyst) while the same repo has an EARLIER
# unfinished task (FIFO, tasks.id < the job's task) OR the repo is frozen. The branch
# cut is DEFERRED from start_pipeline to the analyst-job claim so its base is a fresh
# origin/main already containing the predecessor (anti-stale-base). Gate lives in
# claim_next_job (offline hot-path, fail-OPEN on error); freeze (FR-5) is a durable
# repo_freeze row set on post-deploy DEGRADED, cleared manually via
# POST /serial-gate/unfreeze?repo=<repo>. Leaf src/serial_gate.py (never-raise).
#   SERIAL_GATE_ENABLED=false -> claim AND start_pipeline are 1:1 as before ORCH-088.
#   SERIAL_GATE_REPOS (CSV) -> scope; EMPTY = ALL repos (not self-hosting-only).
#   SERIAL_GATE_FREEZE_ENABLED=false -> the rollback-freeze layer is off (not set/read).
#   SERIAL_GATE_PAUSE_ENABLED (ORCH-124) -> per-task "park" axis. true (default) -> a
#     task with tasks.paused_at NOT NULL (POST /serial-gate/pause?work_item=<id>) is
#     excluded from the "active task" predicate so an URGENT successor may overtake a
#     paused predecessor. TRUE no-op until an operator pauses a task. false -> pause-term
#     omitted, serial-gate byte-for-byte ORCH-088/090. Scope reuses SERIAL_GATE_REPOS.
ORCH_SERIAL_GATE_ENABLED=true
ORCH_SERIAL_GATE_REPOS=
ORCH_SERIAL_GATE_FREEZE_ENABLED=true
ORCH_SERIAL_GATE_PAUSE_ENABLED=true
# ORCH-090: STOP-status task cancellation (stop active agent + full progress reset)
# and the relaunch-hole close. A dedicated Plane "STOP" status (logical key `stop`,
# fail-closed: absent from _DEFAULT_STATES, so a board without the status -> no-op)
# routes to a cancel handler that drives the task to the system-terminal state
# `cancelled` (stop agent via the graceful SIGTERM cascade, cancel all jobs, remove
# worktree + delete the remote feature branch [never main / never force-push],
# tombstone the natural keys for a clean re-create via "To Analyse"; docs preserved).
# STOP during a critical merge/deploy window is DEFERRED until the irreversible step
# finishes honestly. The relaunch-hole gate restricts the "To Analyse" agent relaunch
# to the `analysis` stage (the sole Needs-Input owner). Additive, never-raise.
# Infra precondition: create a "STOP" status with the `cancelled` group on the ORCH
# board (07-infra-requirements.md). Leaf src/cancel.py.
#   STOP_STATUS_ENABLED=false -> STOP handling AND the relaunch-hole gate are inert
#                                (behaviour strictly as before ORCH-090).
#   STOP_STATUS_REPOS (CSV)   -> scope; EMPTY = ALL repos (cancellation is meaningful
#                                for enduro too).
ORCH_STOP_STATUS_ENABLED=true
ORCH_STOP_STATUS_REPOS=
# ORCH-019: bug-fast-track — a cheaper/shorter pipeline route for bug-fix tasks.
# A task carrying the Plane `Bug` label skips the whole `architecture` stage; EVERY
# Quality Gate / sub-gate runs UNCHANGED (route is a scheduler property, not a gate).
# Additive, never-raise, fail-safe -> full cycle. Infra precondition: create a `Bug`
# label on the ORCH board (its absence = full cycle, fail-safe). Leaf src/bug_fast_track.py.
#   BUG_FAST_TRACK_ENABLED=false -> start_pipeline AND advance_stage are 1:1 as before
#                                   ORCH-019 (zero regression).
#   BUG_FAST_TRACK_LABEL         -> Plane label that activates the track (default `Bug`).
#   BUG_FAST_TRACK_REPOS (CSV)   -> scope; EMPTY = self-hosting only (orchestrator).
ORCH_BUG_FAST_TRACK_ENABLED=true
ORCH_BUG_FAST_TRACK_LABEL=Bug
ORCH_BUG_FAST_TRACK_REPOS=
# ORCH-094: terminal-window-aware guard for the three deploy-phase Plane status
# setters (set_issue_awaiting_deploy / set_issue_deploying / set_issue_monitoring).
# A DB stage=done task converges to Done idempotently instead of flapping
# Awaiting <-> Monitoring, EXCEPT the legitimate post-deploy Monitoring while the
# window is active (ARMED & not DONE). Leaf src/deploy_status_guard.py, never-raise;
# STAGE_TRANSITIONS / QG_CHECKS / machine-verdict keys untouched (no DB migration).
#   DEPLOY_STATUS_GUARD_ENABLED=false -> setters are terminal-blind (1:1 pre-ORCH-094).
#   DEPLOY_STATUS_GUARD_REPOS (CSV)   -> scope; EMPTY = self-hosting only (orchestrator),
#                                        the only repo where deploy-phase statuses are set.
ORCH_DEPLOY_STATUS_GUARD_ENABLED=true
ORCH_DEPLOY_STATUS_GUARD_REPOS=
# ORCH-071/073: merge-verify under-gate on the `deploy -> done` edge (врезка in
# advance_stage, NOT a new STAGE_TRANSITIONS edge / registered QG). A deterministic
# merge-actor merges the feature code-PR via the Gitea PR-merge API (never push/
# force-push to main), then `done` is allowed ONLY when the deployed SHA is proven an
# ancestor of origin/main (ORCH-073 FR-1: SHA-in-main is the single criterion; a
# merged PR alone no longer confirms). A secondary regression guard then checks a
# declarative marker set (MAIN_REGRESSION_MARKERS) is still in origin/main; a missing
# marker -> alert + HOLD (NOT done), a git error of the grep itself -> fail-open.
#   MERGE_VERIFY_ENABLED      -> global kill-switch (false -> strictly pre-ORCH-071).
#   MERGE_VERIFY_REPOS        -> CSV of repos where the under-gate is REAL; empty ->
#                                only the self-hosting repo (orchestrator); non-self -> no-op.
#   MERGE_PR_TIMEOUT_S        -> per Gitea list/merge HTTP call timeout.
#   MERGE_VERIFY_TIMEOUT_S    -> git fetch/merge-base timeout for the ancestor + marker checks.
#   REGRESSION_GUARD_ENABLED  -> kill-switch for the ORCH-073 main-integrity regression
#                                guard (false -> SHA-in-main alone gates done); reuses the
#                                merge-verify scope, so non-self repos are a no-op.
#   MERGE_VERIFY_AUTOCREATE_PR_ENABLED -> ORCH-082: guarantee an open code-PR
#                                (head==branch, base==main) via merge_gate.ensure_open_pr
#                                BEFORE the deterministic merge_pr (fixes the false HOLD
#                                "no open PR"). false -> exactly pre-ORCH-082 behaviour.
#                                Reuses the merge-verify scope; non-self repos -> no-op.
ORCH_MERGE_VERIFY_ENABLED=true
ORCH_MERGE_VERIFY_REPOS=
ORCH_MERGE_PR_TIMEOUT_S=60
ORCH_MERGE_VERIFY_TIMEOUT_S=60
ORCH_REGRESSION_GUARD_ENABLED=true
ORCH_MERGE_VERIFY_AUTOCREATE_PR_ENABLED=true
# ORCH-093: deterministic merge-actor retry of TRANSIENT Gitea merge errors. merge_pr
# wraps ONLY the mutating POST /pulls/{n}/merge in a bounded exponential-backoff
# retry-loop on transient outcomes (405 "try again later" / 408 / 5xx / network /
# timeout, and 409|422 while the PR is still mergeable); terminal outcomes
# (403/404/real conflict) -> fast honest False (the ORCH-071/081 HOLD backstop is
# unchanged). Fixes the ORCH-063 false HOLD + manual re-merge. The already-in-main
# guard (no commits beyond origin/main -> no garbage PR) is always-on under
# MERGE_VERIFY_AUTOCREATE_PR_ENABLED (no separate flag).
#   MERGE_RETRY_ENABLED        -> kill-switch; false -> exactly one POST (one-shot, prior behaviour).
#   MERGE_RETRY_MAX_ATTEMPTS   -> max POST attempts on a transient outcome.
#   MERGE_RETRY_BACKOFF_BASE_S -> exponential backoff base seconds (sleep = base*2^(i-1)).
#   MERGE_RETRY_BACKOFF_MAX_S  -> per-sleep backoff ceiling seconds (bounds total wait).
ORCH_MERGE_RETRY_ENABLED=true
ORCH_MERGE_RETRY_MAX_ATTEMPTS=3
ORCH_MERGE_RETRY_BACKOFF_BASE_S=2
ORCH_MERGE_RETRY_BACKOFF_MAX_S=5
# ORCH-036: executable self-deploy of the `deploy` stage. For the self-hosting repo
# (orchestrator) the stage REALLY restarts prod (8500) via a detached host hook;
# deploy_status: SUCCESS means proven health-ok, not an LLM declaration. Three
# deterministic phases (A: request approve, B: human Approved -> detached deploy,
# C: finalizer maps hook exit-code -> deploy_status). Non-self repos: unchanged
# synchronous ssh deploy. SECRETS / host paths live ONLY on the host — do NOT commit.
#   SELF_DEPLOY_ENABLED -> global kill-switch (false -> legacy synchronous deploy for all).
#   SELF_DEPLOY_REPOS   -> CSV of repos where Phase A/B/C is REAL; empty -> only the
#                          self-hosting repo (orchestrator); others -> no-op (mirrors ORCH-35).
#   DEPLOY_REQUIRE_MANUAL_APPROVE -> require a human Plane "Approved" before the prod
#                          deploy (true on rollout; full auto is ORCH-54).
#   DEPLOY_FINALIZE_DELAY_S       -> delay before the first/each finalize poll (>= hook+health).
#   DEPLOY_FINALIZE_MAX_ATTEMPTS  -> bounded finalize-defer budget (anti-livelock).
#   DEPLOY_SSH_USER / DEPLOY_SSH_HOST -> ssh target for the host hook (DEPLOY_SSH_HOST
#                          empty -> detached deploy will NOT launch; set on the host).
#   DEPLOY_HOOK_SCRIPT            -> path to the hook ON THE HOST (relative to the repo).
#   DEPLOY_HOST_REPO_PATH         -> orchestrator clone path on the host.
#   DEPLOY_PROD_SOURCE_IMAGE      -> staging-validated image, retagged build-once (no rebuild).
#   DEPLOY_PROD_TARGET_SERVICE / _PORT / _IMAGE / _COMPOSE_PROFILE -> prod compose profile.
#   DEPLOY_PROD_PREV_IMAGE_FILE   -> prod prev-image snapshot (separate from staging's).
ORCH_SELF_DEPLOY_ENABLED=true
ORCH_SELF_DEPLOY_REPOS=
ORCH_DEPLOY_REQUIRE_MANUAL_APPROVE=true
ORCH_DEPLOY_FINALIZE_DELAY_S=90
ORCH_DEPLOY_FINALIZE_MAX_ATTEMPTS=10
ORCH_DEPLOY_SSH_USER=slin
ORCH_DEPLOY_SSH_HOST=
ORCH_DEPLOY_HOOK_SCRIPT=scripts/orchestrator-deploy-hook.sh
ORCH_DEPLOY_HOST_REPO_PATH=/home/slin/repos/orchestrator
ORCH_DEPLOY_PROD_SOURCE_IMAGE=orchestrator-orchestrator-staging
ORCH_DEPLOY_PROD_TARGET_SERVICE=orchestrator
ORCH_DEPLOY_PROD_TARGET_PORT=8500
ORCH_DEPLOY_PROD_TARGET_IMAGE=orchestrator-orchestrator
ORCH_DEPLOY_PROD_COMPOSE_PROFILE=
ORCH_DEPLOY_PROD_PREV_IMAGE_FILE=.deploy-prev-image-prod

# ORCH-112: deploy-base checkout-hygiene (resilient-pull). The self-deploy hook
# converges a DIRTY shared deploy-base to a clean, current origin/main BEFORE the
# `git pull` (git fetch + reset --hard + a SCOPED `git clean -fd`, NEVER `-x`), so
# manual/abandoned WIP left by a failed/cancelled task never blocks the deploy
# (incident ORCH-111). False -> bare `git pull origin main` 1:1 as before ORCH-112.
# Empty REPOS -> only the self-hosting repo (orchestrator).
ORCH_CHECKOUT_HYGIENE_ENABLED=true
ORCH_CHECKOUT_HYGIENE_REPOS=

# ORCH-058: staging-image provenance before the BUILD-ONCE prod retag (INV-FRESH).
# Guarantees the staging image promoted to prod is the EXACT artefact rebuilt from the
# validated commit — two layers, self-hosting only:
#   A (liveness): QG sub-check `check_staging_image_fresh` on the deploy-staging->deploy
#     edge rebuilds orchestrator-orchestrator-staging from the validated commit + recreates
#     8501; FAIL -> rollback to development. (builds/recreate STAGING only, never prod.)
#   B (safety):  the Dockerfile stamps `org.opencontainers.image.revision`; the prod hook
#     fail-closes (exit 1) before `docker tag` if SOURCE_IMAGE's label != EXPECTED_REVISION.
#   ENABLED -> single kill-switch for A+B as a WHOLE (never "B without A"); false -> legacy.
#   REPOS   -> CSV of repos where the gate is REAL; empty -> only self-hosting (orchestrator).
ORCH_IMAGE_FRESHNESS_ENABLED=true
ORCH_IMAGE_FRESHNESS_REPOS=

# ORCH-061: staging-verdict tolerance to sandbox-infra-only FAILs. The self-hosting
# orchestrator looped on deploy-staging because staging_check.py exited 1 on ANY FAIL,
# so two infra-only checks (C9a sandbox branch / C9b analyst-job — caused by SANDBOX
# bot accounts not being members of the sandbox Plane project, NOT a pipeline regress)
# forced staging_status: FAILED -> rollback -> loop. With this ON, C9a/C9b are WAIVED
# to SUCCESS when every REAL check is green; any REAL failure still fails closed.
#   true (default) -> tolerant; false -> legacy strict (1:1 pre-ORCH-061, any FAIL rolls back).
# Lives in .env.staging (the staging instance). CLI --strict overrides this per-run.
ORCH_STAGING_INFRA_TOLERANCE_ENABLED=true

# ORCH-053: stuck-task reconciler (sweeper for lost webhooks). A background daemon
# replays a missed stage transition through the SAME gates/handlers a webhook would,
# fixing tasks that got stuck on a dropped event (502 on rebuild, no Plane/Gitea
# retries, unresolved sha->branch).
#   ENABLED            -> global kill-switch (self-hosting safety / staged rollout).
#   PLANE_ENABLED      -> separate flag for the F-2 Plane-API poll (mute only F-2).
#   INTERVAL_S         -> background sweep period (seconds).
#   GRACE_DEFAULT_S    -> default "stuck" threshold on tasks.updated_at (seconds).
#   GRACE_OVERRIDES_JSON -> per-stage thresholds, e.g. {"development":300}; bad JSON -> default.
#   NOTIFY_UNBLOCK     -> send a Telegram message when a stuck task is unblocked.
#   SKIP_BLOCKED_ENABLED -> ORCH-060 F-1 Guard 2: skip reconciling issues a human moved
#                        to Blocked / Needs Input (per-candidate Plane state lookup).
#                        false mutes ONLY the networked Guard 2; Guard 1 (escalated by
#                        developer retries, local+deterministic) is always active.
ORCH_RECONCILE_ENABLED=true
ORCH_RECONCILE_PLANE_ENABLED=true
ORCH_RECONCILE_INTERVAL_S=120
ORCH_RECONCILE_GRACE_DEFAULT_S=600
ORCH_RECONCILE_GRACE_OVERRIDES_JSON=
ORCH_RECONCILE_NOTIFY_UNBLOCK=true
ORCH_RECONCILE_SKIP_BLOCKED_ENABLED=true

# ORCH-068: TTL (seconds) for the per-project Plane states cache (plane_sync
# _STATES_CACHE). Historically the cache lived for the whole process lifetime,
# so a status added to Plane after start was invisible until a restart
# ("stale set -> no pipeline action"). With a TTL the entry self-heals by
# re-fetching /states/ once it expires (reuses reload_project_states()).
#   >0  -> re-fetch after this many seconds (default 300 = 5 min);
#   0   -> disable TTL -> strictly the previous lifetime cache (back-compat).
ORCH_PLANE_STATES_TTL_S=300

# ORCH-065: job-reaper + proactive merge-lease reclaim. A background daemon thread
# (src/job_reaper.py, started LAST in main.lifespan after requeue_running_jobs) reaps
# zombie 'running' jobs whose monitor/process died before writing the terminal status
# (one zombie at max_concurrency=1 blocks the whole shared queue) and periodically
# reclaims dead/stale merge-leases. Liveness is three-tier: Tier-1 dead jobs.pid
# (os.kill(pid,0)) after REAPER_DEAD_TICKS consecutive dead ticks (anti-false-positive
# for a live agent); Tier-2 agent_runs.exit_code recorded but job still 'running'
# (only after a REAPER_FINALIZE_GRACE_S finalization grace, so a live monitor still
# doing git push / PR / Plane comments is never reaped); Tier-3 backstop after
# REAPER_MAX_RUNNING_S. The terminal flip carries an atomic status='running' guard and
# precedes any advance/enqueue (claim-before-act) so it never double-processes/-advances
# a row racing a late monitor or requeue_running_jobs.
#   REAPER_ENABLED          -> global kill-switch (false -> strictly prior behaviour).
#   REAPER_INTERVAL_S       -> background scan period (seconds).
#   REAPER_DEAD_TICKS       -> consecutive dead-pid ticks before reaping (Tier-1, >=2).
#   REAPER_MAX_RUNNING_S    -> Tier-3 backstop ceiling; must exceed max agent_timeout+grace.
#                              ORCH-109: raised 3600 -> 5400 in lockstep with the developer
#                              budget (5400 > 3600 + 20 = 3620).
#   REAPER_FINALIZE_GRACE_S -> Tier-2 grace: how long agent_runs.exit_code must have been
#                              recorded before a still-'running' job is reaped; MUST exceed
#                              the max finalization window (git push + PR + Plane comments).
#   LEASE_RECLAIM_ENABLED   -> kill-switch for the proactive stale/dead lease reclaim
#                              (false -> only the legacy lazy TTL reclaim in acquire_merge_lease).
# (reuse) ORCH_MERGE_LOCK_TIMEOUT_S -> lease TTL; ORCH_MERGE_GATE_REPOS -> reclaim scope.
ORCH_REAPER_ENABLED=true
ORCH_REAPER_INTERVAL_S=60
ORCH_REAPER_DEAD_TICKS=2
ORCH_REAPER_MAX_RUNNING_S=5400
ORCH_REAPER_FINALIZE_GRACE_S=300
ORCH_LEASE_RECLAIM_ENABLED=true

# ORCH-114 (adr-0045): durable transition-ownership lease + expected-stage CAS for
# side-effectful stage transitions. Generalises the process-local ORCH-113 finalizer-
# liveness into a DURABLE, cross-path owner-exclusion (additive table `transition_lease`)
# so a concurrent OR post-restart re-entry into a side-effectful transition (reaper /
# reconciler / webhook / startup-requeue) is deferred or a no-op instead of re-applying
# an irreversible effect (merge_pr / coverage-ratchet / image-rebuild / prod-deploy
# initiation / contradictory rollback<->done). Two layers, both gated by the SINGLE
# kill-switch below: (1) a durable lease on ENTRY to the side-effectful region (a second
# actor that sees a live owner does not start the heavy sub-gates at all); (2) an
# expected-stage CAS on the stage WRITE (a lost race -> abort with NO side effect), which
# also closes the paths that write the stage in bypass of advance_stage. Owner liveness =
# owner_pid + owner_boot_id (NOT a heartbeat), so restart recovery is free (new process ->
# new boot_id -> all prior leases instantly stale -> reclaimed). The lease has NO own TTL:
# its hard age ceiling IS the reaper Tier-3 backstop (ORCH_REAPER_MAX_RUNNING_S), so the
# cross-cutting budget invariant ORCH-065/109/110/113 is untouched. STAGE_TRANSITIONS /
# QG_CHECKS / check_* / machine-verdict keys / existing table schemas — byte-for-byte.
#   TRANSITION_LEASE_ENABLED -> SINGLE kill-switch. false -> the lease is neither written
#                               nor read AND the CAS degenerates to the prior unconditional
#                               update_task_stage -> behaviour byte-for-byte as before
#                               ORCH-114 (reaper -> ORCH-113 in-memory fallback,
#                               reconciler/webhook skip-guard inert). Default true.
#   TRANSITION_LEASE_REPOS   -> CSV scope. Empty -> applies ONLY to the self-hosting repo
#                               (orchestrator), where the irreversible side-effectful edges
#                               live; non-empty -> only the listed repos. Mirrors
#                               ORCH_COVERAGE_GATE_REPOS -> enduro untouched at the default.
ORCH_TRANSITION_LEASE_ENABLED=true
ORCH_TRANSITION_LEASE_REPOS=

# ORCH-063: disk-watchdog — background heartbeat that measures HOST-FS fill via the
# mounted bind-paths (/repos, /app/data) with shutil.disk_usage (NOT the container
# overlay /) and Telegram-alerts the operator at >= threshold. On 07.06.2026 the
# mva154 host disk silently hit 100% and stalled the WHOLE self-hosting pipeline;
# this is the missing proactive signal. Daemon thread modelled on reconciler/reaper
# (start/stop in main.lifespan, /queue snapshot, never-raise). Anti-spam state is
# in-memory (no DB migration); the watchdog only READS fill and SENDS Telegram — it
# never touches the disk/container or restarts prod (self-hosting safety).
#   DISK_MONITOR_ENABLED       -> kill-switch; false -> the daemon does not start (1:1 as before).
#   DISK_MONITOR_INTERVAL_S    -> heartbeat measurement period, seconds (order of minutes).
#   DISK_MONITOR_THRESHOLD_PCT -> fill % that triggers the alert (Owner-fixed 85; valid 1..100).
#   DISK_MONITOR_REALERT_S     -> cooldown between repeat alerts while above threshold (~6h).
#   DISK_MONITOR_PATHS         -> CSV of monitored HOST bind-paths; empty -> /repos,/app/data.
ORCH_DISK_MONITOR_ENABLED=true
ORCH_DISK_MONITOR_INTERVAL_S=300
ORCH_DISK_MONITOR_THRESHOLD_PCT=85
ORCH_DISK_MONITOR_REALERT_S=21600
ORCH_DISK_MONITOR_PATHS=/repos,/app/data

# ORCH-062: build-cache-pruner — the "second half" of the disk-watchdog
# (watchdog SIGNALS, pruner CLEANS). A daemon thread modelled on disk_watchdog
# that periodically runs STRICTLY `docker builder prune -f --filter until=<until>`
# on the HOST over ssh (BuildKit GC). Touches ONLY the build cache: never
# images/containers of running services, never restarts the docker daemon or the
# prod container (self-hosting safety). State is in-memory (no DB migration). No
# ssh host configured -> the tick is a no-op. See docs/operations/INFRA.md.
#   BUILD_CACHE_PRUNE_ENABLED       -> kill-switch; false -> the daemon does not start (1:1 as before).
#   BUILD_CACHE_PRUNE_INTERVAL_S    -> tick period, seconds (order of hours; default ~6h). >0, else default.
#   BUILD_CACHE_PRUNE_UNTIL         -> retention age for the warm cache (`--filter until=`); ^\d+[smhdw]?$, else 24h.
#   BUILD_CACHE_PRUNE_ALL           -> add `-a` (ALWAYS paired with until); default false.
#   BUILD_CACHE_PRUNE_TIMEOUT_S     -> bound on the ssh command, seconds. >0, else default.
#   BUILD_CACHE_PRUNE_NOTIFY_MIN_GB -> Telegram when reclaimed >= N GB; 0 -> silent.
ORCH_BUILD_CACHE_PRUNE_ENABLED=true
ORCH_BUILD_CACHE_PRUNE_INTERVAL_S=21600
ORCH_BUILD_CACHE_PRUNE_UNTIL=24h
ORCH_BUILD_CACHE_PRUNE_ALL=false
ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S=120
ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB=0

# ORCH-022: security-gate (secret-scanning + dependency audit) on the
# deploy-staging -> deploy edge, run FIRST among the edge sub-gates. Deterministic
# (no LLM): gitleaks (offline secret-scan, pinned Go binary in the image) + pip-audit
# (OSV/PyPI CVE audit). Verdict in the versioned 17-security-report.md frontmatter;
# FAIL -> rollback to development + developer-retry (cap 3). See ADR-001.
#   GATE_ENABLED          -> global kill-switch; false -> pipeline 1:1 as before ORCH-022.
#   GATE_REPOS            -> CSV of repos where the gate is REAL; empty -> only self-hosting.
#   DEP_BLOCK_SEVERITY    -> CVE severity that BLOCKS (CRITICAL>HIGH>MEDIUM>LOW); below /
#                            UNKNOWN -> warning only (anti-loop).
#   SCAN_TIMEOUT_S        -> per external scanner call timeout.
#   DEP_AUDIT_FAIL_CLOSED -> strict mode: unreachable CVE feed -> FAIL instead of the
#                            default fail-open + warning (anti-loop). Default false.
#   SECRETS_BLOCK         -> a found secret blocks (always true by default; the offline
#                            secrets guarantee is unconditional).
ORCH_SECURITY_GATE_ENABLED=true
ORCH_SECURITY_GATE_REPOS=
ORCH_SECURITY_DEP_BLOCK_SEVERITY=HIGH
ORCH_SECURITY_SCAN_TIMEOUT_S=300
ORCH_SECURITY_DEP_AUDIT_FAIL_CLOSED=false
ORCH_SECURITY_SECRETS_BLOCK=true

# ORCH-027: coverage-gate (deterministic test-coverage) on the deploy-staging ->
# deploy edge, run AFTER the merge-gate and BEFORE image-freshness. Measures line
# coverage of src/ with pytest-cov in the per-branch worktree, compares to an absolute
# floor and/or the ratchet baseline of `main`; FAIL -> rollback to development +
# developer-retry (cap 3). Verdict in the 18-coverage-report.md frontmatter
# (coverage_status:). See ADR-001-coverage-gate.md.
#   GATE_ENABLED       -> global kill-switch; false -> pipeline 1:1 as before ORCH-027.
#   GATE_REPOS         -> CSV of repos where the gate is REAL; empty -> only self-hosting.
#   MIN_PERCENT        -> absolute floor (% line coverage) for policy absolute/both.
#   POLICY             -> absolute | baseline | both (default both).
#   EPSILON            -> noise tolerance (%) at the boundary (anti-flap).
#   TOOL_FAIL_CLOSED   -> strict mode: a coverage-tool error -> FAIL instead of the
#                         default fail-open + warning (anti-loop). Default false.
#   RUN_TIMEOUT_S      -> wall-clock budget for the pytest --cov run.
ORCH_COVERAGE_GATE_ENABLED=true
ORCH_COVERAGE_GATE_REPOS=
ORCH_COVERAGE_MIN_PERCENT=0.0
ORCH_COVERAGE_POLICY=both
ORCH_COVERAGE_EPSILON=0.5
ORCH_COVERAGE_TOOL_FAIL_CLOSED=false
ORCH_COVERAGE_RUN_TIMEOUT_S=900

# ORCH-115: deterministic staging-runner replacing the LLM `deployer` on the
# `deploy-staging` stage (self-hosting orchestrator). Intercepted in launch_job
# BEFORE _spawn (deploy-finalizer / post-deploy-monitor precedent): runs the same
# staging suite, maps exit-code -> staging_status:, writes 15-staging-log.md and
# initiates the UNCHANGED check_staging_status gate. Replaces only the producer of
# the artifact; the gate / STAGE_TRANSITIONS / DB schema are byte-for-byte unchanged.
# See ADR-001-deterministic-staging-runner.md / adr-0048.
#   STAGING_RUNNER_ENABLED            -> kill-switch; false -> the prior LLM deployer
#                                        runs on deploy-staging via _spawn 1:1.
#   STAGING_RUNNER_REPOS              -> CSV scope; empty -> self-hosting only.
#   STAGING_RUNNER_TIMEOUT_S          -> wall-clock budget for the docker-exec suite
#                                        (malformed/non-positive -> default 600 + WARNING).
#   STAGING_RUNNER_INFRA_MAX_RETRIES  -> transient-infra (timeout/ssh) bounded DEFER
#                                        budget before an infra-HOLD (anti ORCH-110).
#   STAGING_RUNNER_INFRA_RETRY_DELAY_S-> delay before the re-queued deployer job.
#   STAGING_RUNNER_EXEC_HOST_SIDE     -> ORCH-123 (adr-0049): true (default = prod) wraps
#                                        the `docker exec` in `ssh <user@host> '<...>'` so
#                                        the suite runs HOST-SIDE (the prod container ships
#                                        no docker CLI; incident ORCH-116). false -> the
#                                        prior in-container `docker exec` (valid only where
#                                        a docker CLI is baked into the image). Rollback knob.
ORCH_STAGING_RUNNER_ENABLED=true
ORCH_STAGING_RUNNER_REPOS=
ORCH_STAGING_RUNNER_TIMEOUT_S=600
ORCH_STAGING_RUNNER_INFRA_MAX_RETRIES=2
ORCH_STAGING_RUNNER_INFRA_RETRY_DELAY_S=30
ORCH_STAGING_RUNNER_EXEC_HOST_SIDE=true

# ORCH-116: deterministic test-runner replacing the LLM `tester` agent on the
# `testing` stage for the self-hosting orchestrator (2nd determinization slice,
# mirror of the ORCH-115 staging-runner). A leaf src/test_runner.py is intercepted
# in launch_job BEFORE _spawn: it runs the SAME regression `python -m pytest <target>`
# in the task worktree (+ optional read-only smoke), maps the exit-code -> result:
# PASS|FAIL, writes 13-test-report.md and initiates the UNCHANGED check_tests_passed
# gate. Replaces only the producer of the artifact; the gate / STAGE_TRANSITIONS / DB
# schema are byte-for-byte unchanged. See ADR-001-deterministic-test-runner.md / adr-0050.
#   TEST_RUNNER_ENABLED            -> kill-switch; false -> the prior LLM tester runs on
#                                     testing via _spawn 1:1.
#   TEST_RUNNER_REPOS              -> CSV scope; empty -> self-hosting only. A repo with
#                                     no resolvable test-contract is never intercepted (BR-9).
#   TEST_RUNNER_TARGET            -> pytest target of the test-contract (default tests/).
#   TEST_RUNNER_TIMEOUT_S          -> wall-clock budget for the pytest regression
#                                     (malformed/non-positive -> default 900 + WARNING).
#   TEST_RUNNER_SMOKE_ENABLED      -> optional read-only smoke (/health,/status,/queue +
#                                     serial_gate block); false -> pytest exit-code is the sole signal.
#   TEST_RUNNER_INFRA_MAX_RETRIES  -> tool-error (suite did NOT execute) bounded DEFER
#                                     budget before a fail-closed FAIL (anti ORCH-110).
#   TEST_RUNNER_INFRA_RETRY_DELAY_S-> delay before the re-queued tester job.
ORCH_TEST_RUNNER_ENABLED=true
ORCH_TEST_RUNNER_REPOS=
ORCH_TEST_RUNNER_TARGET=tests/
ORCH_TEST_RUNNER_TIMEOUT_S=900
ORCH_TEST_RUNNER_SMOKE_ENABLED=true
ORCH_TEST_RUNNER_INFRA_MAX_RETRIES=2
ORCH_TEST_RUNNER_INFRA_RETRY_DELAY_S=30

# ORCH-057 (follow-up ORCH-040): legacy root-owned ownership detect + actionable
# worktree error. After the uid migration (user: "1000:1000") legacy root:root files
# in /repos broke worktree creation under uid 1000 with a raw "Permission denied".
# Three additive, kill-switch-reversible layers: an actionable RuntimeError in
# ensure_worktree, a cheap never-raise detect leaf (src/fs_normalize.py) with a
# startup WARNING/Telegram + GET /queue fs_ownership block, and an opt-in chown ONLY
# when privileged (under uid 1000 a no-op; the real fix is the operator procedure in
# docs/operations/INFRA.md «Миграция uid»). No STAGE_TRANSITIONS / QG_CHECKS / schema
# change.
#   ENABLED        -> kill-switch; false -> all code inert, behaviour 1:1 as before
#                     ORCH-057 (the actionable error too).
#   REPOS          -> CSV of repos the layer is REAL for; empty -> self-hosting only.
#   TARGET_UID     -> target uid fallback when os.getuid() is unavailable.
#   NORMALIZE_AUTO -> detect-only (false) | attempt chown when privileged (true).
#   SCAN_ROOTS     -> CSV override of the scan roots (empty -> default roots).
#   SCAN_CACHE_TTL_S -> TTL of the detect cache (mirrors ORCH_PREFLIGHT_CACHE_TTL).
ORCH_FS_NORMALIZE_ENABLED=true
ORCH_FS_NORMALIZE_REPOS=
ORCH_FS_TARGET_UID=1000
ORCH_FS_NORMALIZE_AUTO=false
ORCH_FS_SCAN_ROOTS=
ORCH_FS_SCAN_CACHE_TTL_S=300

# ORCH-099 (FND/F1a): operator off-switch for the read-only GET /metrics endpoint
# (raw-signal snapshot for the F1b sidecar). Default true -> available out of the
# box. false -> /metrics returns a minimal parsable body {"schema_version":1,
# "enabled":false} (200, not 404). The endpoint is inert / read-only anyway.
ORCH_METRICS_ENABLED=true

# ORCH-021: post-deploy production monitoring + degradation reaction. After the
# terminal deploy->done transition for an applicable repo, a reserved-agent job
# `post-deploy-monitor` (no LLM, modelled on deploy-finalizer) probes prod over a
# window and reacts to a degradation the restart-time health-check missed (class
# "green deploy, red prod", precedent ET-8). State is in sentinel files
# (.post-deploy-state-<repo>/<wi>/), no DB migration.
#   MONITOR_ENABLED  -> global kill-switch; false -> pipeline is 1:1 as before ORCH-021.
#   REPOS            -> CSV of repos where monitoring is REAL; empty -> only self-hosting.
#   WINDOW_S         -> observation window length (~15 min).
#   INTERVAL_S       -> seconds between probe ticks.
#   FAIL_THRESHOLD   -> N CONSECUTIVE health failures -> DEGRADED.
#   5XX_THRESHOLD    -> window 5xx ratio above this -> DEGRADED.
#   AUTO_ROLLBACK    -> allow auto-rollback; acts ONLY for non-self repos. Self-hosting
#                       is ALWAYS ALERT_ONLY (a tick NEVER restarts the prod container).
#   BASE_URL         -> base URL of the observed prod instance.
ORCH_POST_DEPLOY_MONITOR_ENABLED=true
ORCH_POST_DEPLOY_REPOS=
ORCH_POST_DEPLOY_WINDOW_S=900
ORCH_POST_DEPLOY_INTERVAL_S=30
ORCH_POST_DEPLOY_FAIL_THRESHOLD=3
ORCH_POST_DEPLOY_5XX_THRESHOLD=0.5
ORCH_POST_DEPLOY_AUTO_ROLLBACK=false
ORCH_POST_DEPLOY_BASE_URL=http://localhost:8500

# ── QG-0 entry validation (ORCH-069) ──────────────────────────────────────────
# Upper title-length limit for the QG-0 entry gate (_qg0_errors). The old 80-char
# cap was a hygiene limit, not structural (slug is cut to [:30] independently, the
# DB title TEXT is unbounded). Default 200. An invalid/empty value gracefully
# degrades to 200 (the process never crashes on startup).
ORCH_QG0_TITLE_MAX=200

# ── ORCH-100 (FND/F1b): sidecar-watchdog (orchestrator-watchdog container) ─────
# The monitoring brain runs in a SEPARATE container with its OWN config. These
# keys are read by the watchdog package (watchdog/config.py), NOT by the
# orchestrator. At runtime they live in `.env.watchdog` (env_file of the
# orchestrator-watchdog service); this block is the canon. NO real secrets here.
#   ENABLED        -> kill-switch; false (or not starting the service) -> inert.
#   INTERVAL_S     -> seconds between ticks.
#   HTTP_TIMEOUT_S -> per-request timeout (metrics / pings / docker / telegram).
#   COOLDOWN_S     -> re-alert throttle for a sustained signal (anti-spam).
#   METRICS_URL    -> orchestrator /metrics (host-network -> 127.0.0.1:8500).
#   ORCH_DOWN_TICKS-> K consecutive /metrics failures before "орк не отвечает".
#   MEM_PCT        -> host memory used-% threshold.
#   DISK_CRIT_*    -> OPT-IN independent disk CEILING (disk_watchdog/ORCH-063 owns
#                     the 85% alert; this is a higher ceiling on the sidecar's own
#                     channel, OFF by default -> no double disk-alert, AC-5/D6).
#   DISK_PATHS     -> host paths measured for the opt-in ceiling.
#   AGENT_HUNG_MIN -> runtime minutes before an agent with ~0 CPU is "hung".
#   AGENT_CPU_FLOOR-> CPU fraction below which a long-running agent counts as hung.
#   STAGE_STUCK_MIN-> minutes a task may sit in one stage before alerting.
#   QUEUE_DEPTH    -> queued-job depth threshold.
#   CONTAINERS     -> CSV of container names to watch (status != running/healthy).
#   DOCKER_SOCK    -> path to the read-only docker.sock inside the container.
#   DEPS           -> CSV of name=url dependency pings (empty -> no pings).
#   PROC_ENABLED   -> ORCH-111 opt-in: alert on a long-lived test process (pytest)
#                     orphaned on the host (needs `pid: host`, default OFF).
#   PROC_AGE_MIN   -> minutes a test process may live before alerting; MUST exceed
#                     max(merge_retest_timeout_s, coverage_run_timeout_s)/60.
#   PROC_PATTERNS  -> CSV of cmdline substrings that mark the test-class (pytest).
#   PROC_COOLDOWN_S-> per-signal re-alert throttle for proc_blocking.
#   TG_BOT_TOKEN / TG_CHAT_ID -> the sidecar's OWN Telegram bot/chat (independent
#                     of the orchestrator's; absent -> logs, does not send).
WATCHDOG_ENABLED=true
WATCHDOG_INTERVAL_S=30
WATCHDOG_HTTP_TIMEOUT_S=5
WATCHDOG_COOLDOWN_S=1800
WATCHDOG_METRICS_URL=http://127.0.0.1:8500/metrics
WATCHDOG_ORCH_DOWN_TICKS=3
WATCHDOG_MEM_PCT=90
WATCHDOG_DISK_CRIT_ENABLED=false
WATCHDOG_DISK_CRIT_PCT=97
WATCHDOG_DISK_PATHS=/repos,/app/data
WATCHDOG_AGENT_HUNG_MIN=20
WATCHDOG_AGENT_CPU_FLOOR=0.01
WATCHDOG_STAGE_STUCK_MIN=120
WATCHDOG_QUEUE_DEPTH=20
WATCHDOG_CONTAINERS=orchestrator
WATCHDOG_DOCKER_SOCK=/var/run/docker.sock
WATCHDOG_DEPS=
WATCHDOG_PROC_ENABLED=false
WATCHDOG_PROC_AGE_MIN=60
WATCHDOG_PROC_PATTERNS=pytest
WATCHDOG_PROC_COOLDOWN_S=1800
WATCHDOG_TG_BOT_TOKEN=
WATCHDOG_TG_CHAT_ID=
