Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@

---

## 2026-09-28 — Pre-commit hook fail-open при потере маркеров (Open)
## 2026-09-28 — Pre-commit hook fail-open при потере маркеров (Fixed)

- **Локация:** `.githooks/pre-commit:31-53` (`find_project_root` + `run_script`).
- **Симптом:** если ни `.git`, ни `KNOWN_ISSUES.md` не найдены (переименование, копия дерева, битый `.git`), fallback указывает мимо проекта; все 9 гейтов печатают ⏭️ «скрипт не найден» и возвращают True → «All pre-commit checks passed», exit 0, коммит идёт без единой проверки.
- **Repro (Verified 2026-09-28):** копия хука в `%TEMP%` (без маркеров) → 9× «скрипт не найден», итог PASS.
- **Guard:** fallback-ветвь обязана fail-closed (sys.exit(1) с явным «project root not found»), либо `run_script` считает missing-script провалом, когда пропущены ВСЕ скрипты; regression-тест: исполнение с `__file__` в markerless-tmpdir → exit ≠ 0.
- **Статус:** 🟡 Fixed-pending-verification (branch `fix/redteam-open-triple`: `find_project_root() -> Path | None`, `run_script` fail-closed; `tests/test_hook_root.py` 3/3 green + full suite 1940 passed).
- **Статус:** ✅ Fixed (fix `0b6ca7c4`, merge `e8811af1` = PR #56: `find_project_root() -> Path | None`, `run_script` fail-closed; `tests/test_hook_root.py` 3/3 green; PR #56 CI all green incl. clean-state + ubuntu/windows tests).

## 2026-09-28 — silent_subprocess: STARTUPINFO ctor outside narrowed try (Open)
## 2026-09-28 — silent_subprocess: STARTUPINFO ctor outside narrowed try (Fixed)

- **Локация:** `src/core/silent_subprocess.py:32-33` (S1), `:51` (S2 — unwrapped `setdefault`).
- **Симптом:** `subprocess.STARTUPINFO()` на L33 вне `try`; на экзотическом win32-билде без `STARTUPINFO` — `AttributeError` из `apply()` на импорте (S2/L51 тот же путь без обёртки; S4/L67 в безопасности — вызов внутри try).
- **Контекст:** на CPython/win32 `STARTUPINFO` всегда есть; все реальные `creationflags=`-вызывающие передают int — практический риск ≈ 0.
- **Guard:** перенести конструирование внутрь try (S1) + обернуть L51 как L67; regression-тест: monkeypatch `subprocess.STARTUPINFO = <missing>` → `apply()` не бросает.
- **Статус:** 🟡 Fixed-pending-verification (low; branch `fix/redteam-open-triple`: ctor inside try + `si = None` init, `:51` wrapped like `:66-69`; `tests/test_silent_subprocess.py` 3/3 green + full suite 1940 passed).
- **Статус:** ✅ Fixed (fix `0b6ca7c4`, merge `e8811af1` = PR #56: ctor inside try + `si = None` init, `:51` wrapped like `:66-69`; `tests/test_silent_subprocess.py` 3/3 green; PR #56 CI all green). Tails (branch `fix/redteam-tails`): модуль был INERT — заведён в entry point (`src/main.py` import + `apply()` at startup, как требует docstring модуля) + TypeError-guard на не-классовый `Popen` (тестовые шимы); liveness доказан `tests/test_silent_subprocess_wired.py` (fresh-процесс: импорт `src.main` → `_APPLIED=True`, на win32 `Popen=_SilentPopen`).

## 2026-09-28 — o1_holdout_gate: hung query hangs whole gate, no timeout (Open)
## 2026-09-28 — o1_holdout_gate: hung query hangs whole gate, no timeout (Fixed)

- **Локация:** `scripts/o1_holdout_gate.py:129-156` (`_run_all`), вызов L106.
- **Симптом:** 15 запросов идут последовательно в одном loop без `wait_for`/глобального капа; один зависший `hybrid_search_async` вешает весь гейт навсегда (единственный `timeout=10` — git-rev диагностика, L99-101).
- **Guard:** per-query `asyncio.wait_for(..., timeout=120)` + timeout → fail-row (как `degraded`); regression — фейковый searcher с висящим запросом → гейт падает за ~120с, а не висит.
- **Статус:** 🟡 Fixed-pending-verification (medium — CI-stall; branch `fix/redteam-open-triple`: per-query `wait_for(timeout=120)` + `timed_out` fail-row in both gates; `tests/test_holdout_harness_timeout.py` 6/6 green + full suite 1940 passed).
- **Статус:** ✅ Fixed (fix `0b6ca7c4`, merge `e8811af1` = PR #56: per-query `wait_for(timeout=120)` + `timed_out` fail-row in both gates; `tests/test_holdout_harness_timeout.py` 6/6 green incl. positive controls; PR #56 CI all green).

## 2026-09-28 — Ретриевер-замеры без сброса реранкер-кэша недействительны (Open)

Expand Down
33 changes: 20 additions & 13 deletions src/core/silent_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,26 @@ def _silent_startupinfo():

_orig_popen = subprocess.Popen

class _SilentPopen(_orig_popen): # type: ignore[misc]
def __init__(self, *args, **kwargs):
kwargs.setdefault("creationflags", _CNW)
# CREATE_NO_WINDOW может быть скомбинирован — OR, не замена
try:
kwargs["creationflags"] |= _CNW
except TypeError:
pass
try:
kwargs.setdefault("startupinfo", _silent_startupinfo())
except (AttributeError, OSError, TypeError):
pass
super().__init__(*args, **kwargs)
try:

class _SilentPopen(_orig_popen): # type: ignore[misc]
def __init__(self, *args, **kwargs):
kwargs.setdefault("creationflags", _CNW)
# CREATE_NO_WINDOW может быть скомбинирован — OR, не замена
try:
kwargs["creationflags"] |= _CNW
except TypeError:
pass
try:
kwargs.setdefault("startupinfo", _silent_startupinfo())
except (AttributeError, OSError, TypeError):
pass
super().__init__(*args, **kwargs)

except TypeError:
# Popen — не класс (тестовый шим/экзотика): патчить нечего,
# стартовый импорт не должен падать (ср. S1/S2-стиль выше).
return

_orig_run = subprocess.run
_orig_check_output = subprocess.check_output
Expand Down
9 changes: 9 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Главная точка входа в приложение.
"""

# Guard от мигающих консолей на Windows: патчит subprocess до любых spawn'ов.
# Идемпотентен (повторный apply() — no-op); на non-win32 — только флаг.
try:
from src.core.silent_subprocess import apply as _apply_silent_subprocess

_apply_silent_subprocess()
except Exception:
pass

import logging
import logging.handlers
import os
Expand Down
44 changes: 44 additions & 0 deletions tests/test_silent_subprocess_wired.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Liveness: silent_subprocess is wired into the entry point (2026-09-29).

PR #56 shipped src/core/silent_subprocess.py INERT — nothing imported it.
fix/redteam-tails wires it into src/main.py (import + apply() at startup).

Proves liveness in a FRESH interpreter (no conftest Popen-shim at import
time — the autouse _no_console_windows fixture replaces Popen with a plain
function, which would mask the real startup path): the child imports
src.main as the server would, then asserts the guard is applied — and on
win32 that subprocess.Popen is the silent wrapper.
"""

import subprocess
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent

_CHILD = (
"import subprocess, sys; "
"import src.main; "
"import src.core.silent_subprocess as mod; "
"assert 'src.core.silent_subprocess' in sys.modules, 'not wired'; "
"assert mod._APPLIED is True, 'not applied'; "
"print('WIRED_OK'); "
"print('POPEN=' + type(subprocess.Popen).__name__ + ':' + subprocess.Popen.__name__)"
)


def test_entry_point_wires_silent_subprocess_fresh_process():
proc = subprocess.run(
[sys.executable, "-c", _CHILD],
cwd=str(PROJECT_ROOT),
capture_output=True,
text=True,
timeout=120,
)
assert proc.returncode == 0, f"child startup import failed:\n{proc.stderr[-2000:]}"
assert "WIRED_OK" in proc.stdout, f"guard not applied after startup import:\n{proc.stdout}"
if sys.platform == "win32":
assert "_SilentPopen" in proc.stdout, (
f"Popen is not the silent wrapper after startup import:\n{proc.stdout}"
)
Loading