feat(fs): legacy root-owned ownership detect + actionable worktree error (ORCH-057)

Follow-up ORCH-040: legacy root:root files in /repos broke worktree creation
under uid 1000 with a raw "Permission denied" (agent never started, no diagnosis).
Three additive, kill-switch-reversible layers; STAGE_TRANSITIONS / QG_CHECKS /
check_* / machine-verdict keys / DB schema are byte-for-byte unchanged.

- D1: ensure_worktree classifies the permission class and raises an actionable
  RuntimeError (cause + chown command + INFRA.md ref); non-permission errors keep
  the prior raw-stderr contract; kill-switch off -> contract 1:1 as before ORCH-057.
- D2: new never-raise leaf src/fs_normalize.py — scan_ownership (TTL-cached,
  early-exit per root), applies()-first scope (empty CSV -> self-hosting only),
  opt-in normalize() that chowns ONLY when privileged (no-op under uid 1000).
- D3: best-effort startup detect in main.lifespan (WARNING + Telegram on mismatch,
  never-fatal); read-only fs_ownership block in GET /queue; POST /fs-normalize/check.
  Claim is NOT blocked — the clear early outcome is delivered by D1 at launch.
- Docs/config: .env.example flags + CHANGELOG (architecture README / adr-0031 /
  INFRA.md procedure already landed on the branch).
- Tests: test_fs_normalize.py, test_git_worktree_perm.py,
  test_fs_normalize_startup.py, test_api_queue.py (TC-01..TC-12). Full suite green.

Refs: ORCH-057
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 02:53:15 +03:00
committed by orchestrator-deployer
parent 34778217fe
commit a98d605477
10 changed files with 1228 additions and 5 deletions

View File

@@ -394,6 +394,29 @@ ORCH_COVERAGE_EPSILON=0.5
ORCH_COVERAGE_TOOL_FAIL_CLOSED=false
ORCH_COVERAGE_RUN_TIMEOUT_S=900
# 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,