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
16 changes: 16 additions & 0 deletions AGENT_DIARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@

---

## [2026-09-11] — Burst-rename: fail-closed VOR отзывает 100% при ONE rename-sweep (ответ Statewave на dev.to)

**Status:** Closed (эксперименты, ответ опубликован)
**Root Cause:** VOR (ADR-0003) проверяет ПУТЬ-якоря против текущего HEAD. Rename/move = старый путь отсутствует = SILENT_ABSENCE = отзыв, хотя файл жив. Синтетика (1-C): git mv 30 файлов одним коммитом → 30/30 REFUTED (100%); body-hash carry → 30/30 уцелели. Реальная память (1-B): 24 авто-REFUTED = 13 мусор якорей + 10 настоящих удалений + 1 ЛОЖНЫЙ отзыв (ADR-7232a6e2ba34: узел жив, отозван по старому пути src/utils/paths.py из prose «X → Y» в теле; хранимые якоря adapters/zed/zed_config.py + src/main.py существуют).
**Fix (эксперименты, не код):** burst_rename_audit.py / burst_sweep_exp.py / redteam_burst.py в experiments/1V_memory_contamination/. Решение для производства не принято (вопрос владельцу: body-hash carry-compat против стоимости).
**Guard:** Red-Team показал — «батч по коммиту» (мульти-уёдание = move) смешивает R* с D* (e661861f: init.py удалён + windows.py R083): спасал бы и настоящие удаления. Точный ревью complex: git --diff-filter=R по истории.
**verified_from_clean_state:** ⚠️ не прогонялся (скрипты экспериментов, не runtime-код)

## [2026-09-07] — Lazy-only верификация: VOR вызывается только из intel_get_project_memory, нет TTL/фона

**Status:** Open — зафиксировано как проблема + план эксперимента (10-continuous-verification.md)
**Root Cause:** По дизайну (ADR-0003) VOR ленивый, но точки вызова всего одна (layer.py:1097); IdleScheduler включается только из record_tool_call(), VOR в idle не подключён, 2 из 3 idle-задач — заглушки (_improve_summaries_batch/_check_index_health — пустые тела). Живой срез текущего проекта: 42/136 узлов ACTIVE без verified_at/TTL висят с 2026-08-11; узлы без якорей → INCONCLUSIVE → VOR не пишет ничего → «проверено» = «кто-то когда-то вызвал».
**Fix (план эксперимента, не внесён):** H1 idle-ticker VOR с budget; H2 event-driven на HEAD (ключ hash(node_id+commit_sha) уже есть); H3 TTL-гниение INCONCLUSIVE → STALE. Baseline замера: полный прогон 136 узлов = 431.6ms (fingerprint 371.6ms) — дешевле порога. Контр-риски: false_retraction не выше 0.083%, цена при нагрузке.
Expand Down Expand Up @@ -200,6 +209,13 @@
**Guard:** дедуп в push + лимиты, single-threaded write под lock. .h-хедеры (H2) — отдельный коммит 0301fa93 (см. KNOWN_ISSUES «`.h` не парсился AST» → Fixed).
**verified_from_clean_state:** ⚠️ не проверено — чистый clone не гонялся (нет сети в сессии); локально полный pytest 1689 passed / 91 deselected (Windows, без e2e/shadow-маркеров — llama недоступен, slow/benchmark отсечены addopts).

## [2026-09-11] — VOR read-path fix (PR #34) + «8-минутный коммит» = НЕ баг (решение владельца)

**Status:** ✅ PR #34 создан, hooks green; скорость тестов — осознанное решение, код НЕ менялся.
**Root Cause:** (1) read-path VOR ре-сканировал prose тела ADR через `_PATH_RE`, хотя явные `data.anchors` уже были захвачены на write-path. Исторические «X → Y» пути из прозы (R083: src/utils/paths.py → adapters/local_fs/windows.py) оживали при rename-sweep и ложно REFUTEDали живой узел ADR-7232a6e2ba34. (2) «Коммит 8 мин» — gate-zero полный pytest ~178s standalone + 2 llama-server (embed+rerank) contention при коммите. Измерено: 1704 passed / 177s; hook-скрипты остальные 8 ≈10s; total ~189s standalone.
**Fix:** (1) `extract_anchors(read_path=True)` в verify_on_read.py — при непустых явных якорях prose НЕ сканируется; legacy-узлы без якорей сохраняют проза-скан (backward compat, дрифт-детект жив). Write-path (layer.py:1250/1555) не тронут (default False). 3 новых regression-теста; 53+28 passed. PR: https://github.com/ManSio/mscodebase-intelligence/pull/34. (2) Владелец: pytest single-thread остаётся, ~178s — норма; xdist/smart-selection НЕ вносить. Зафиксировано в WISDOM.
**Guard:** read-path проверяет ТОЛЬКО якоря, существовавшие на момент записи (не re-derives единственные из прозы); тест `test_read_path_prose_history_legacy_node_keeps_refuting` закрепляет обе ветки. basetemp-гонка (2 параллельных pytest → 452 ложных FileNotFoundError) — известна, не чиним.
**verified_from_clean_state:** ✅ да, локально — PR #34 не merged (ждёт ревью), но: hooks 9/9 OK, полный pytest 1704 passed / 177s, VOR+retraction 53+28 passed.
## [2026-09-10] — Exp 1 (Catch-up Rate) + Exp 3 (HEAD polling): VOR масштабирование и внешний дрифт

**Status:** ✅ Fix (замеры, кода не менялось). **Root Cause (KNOW ISSUES «Lazy-only верификация»):** вопрос, успевает ли VOR проверить ACTIVE-узлы в рамках budget_ms=50 (read-path) / 250 (background idle), и детектит ли он внешнее git-pull изменение без notify_change (H3).
Expand Down
21 changes: 21 additions & 0 deletions EXPERIMENTS_LOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# EXPERIMENTS_LOG.md — Audit Verification (2026-07-22)

## [2026-09-11] — 1-B/1-C/RT: burst-rename vs fail-closed VOR (ответ Statewave)

**Гипотеза (вопрос dev.to «How large does that queue get in practice?»):** после ОДНОГО refactor-коммита с массовыми move/rename fail-closed VOR (ADR-0003, path-якоря) отзывает ВСЕ затронутые узлы (queue = 100%), хотя файлы живы — это ложные отзывы; body-hash-якорь (limpet/OpenLore) переживает rename без потерь.
**Команда:**
```
python experiments/1V_memory_contamination/burst_rename_audit.py # реальная память (24 авто-REFUTED)
python experiments/1V_memory_contamination/burst_sweep_exp.py # синтетика: 30 файлов, git mv, 1 коммит
python experiments/1V_memory_contamination/redteam_burst.py # 3 атаки (batch-hazard/starve/present-trap)
```
**Сырой результат:**
```
[1-B реальная память] авто-REFUTED=24: anchor_junk 13, TRUE_REFUTE_deleted 10, FALSE_REFUTE_renamed 1 (9.1%)
ADR-7232a6e2ba34 FALSE_REFUTE_renamed src/utils/paths.py -> adapters/local_fs/windows.py (git R083)
[1-C синтетика N=30] Baseline A: VERIFIED 30/0 REFUTED. Sweep B (git mv src/mod src/moved):
After sweep: VERIFIED 0 REFUTED 30/30 (100%) | body-hash carry: 30/30 (100% уцелели)
[Red-Team-1 batch-hazard] коммит e661861f = D(adapters/local_fs/__init__.py) + R083(windows.py): «исчезли скопом = move» спас бы и правду, и удаление
[Red-Team-3 starve] бюджет 0.05ms @ N=40: checked=1 inconclusive=39 budget_exceeded=39
[Red-Team-4 present-trap] файл пересоздан на том же пути с другим телом: path-anchor VERIFIED (протух), body-hash честно delete+add
```
**Вердикт:** подтверждена частично. Очередь в практике = не огромная (24 накопленных авто-отзыва за месяц), но ДОМИНИРУЕТ мусор якорей (13/24: import:for/filter/maps, pkg:name/-), а не renames. Настоящие renames дали 1 ложный отзыв (ADR-7232a6e2ba34 — узел жив, якоря adapters/zed/*.py + src/main.py существуют; отозван по СТАРОМУ пути из prose тела). Синтетика показывает паттерн: один rename-sweep = 100% очереди. **Урок:** батч по коммиту НЕ решает — он смешивает move с delete (e661861f); нужна body-identity (exact-body/exact-signature, OpenLore PR#206) или уточнённый reviewer: сравнение failed-якоря против R*-src в git-истории (rename_sources в audit). Связь: ADR-0003, OpenLore#206, limpet (AST body-hash).

## [2026-08-05] — Neuro-Symbolic spike: NL → LLM → Cypher → parser+schema → PropertyGraph (exp-lab-2026-01)

**Ожидание:** связка (LLM-генерация Cypher + двухслойная валидация parser+schema + исполнение) отсекает невалидные генерации ДО исполнения: 10 вопросов → 8 исполнимых + 2 отсечённых (галлюцинированная метка SERVICE, функция cycle()).
Expand Down
6 changes: 6 additions & 0 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

**14 entries** — compressed per §4.8 R3 (conclusion-first; dedup 2026-09-08)

## 2026-09-11 — Burst-rename: fail-closed VOR отзывает узлы по rename-sweep; 1 ЛОЖНЫЙ отзыв (ADR-7232a6e2ba34)

- **Источник:** AGENT_DIARY.md 2026-09-11 + EXPERIMENTS_LOG 1-B/1-C/RT
- **Описание:** VOR (ADR-0003) проверяет path-якоря против HEAD: rename/move = старый путь отсутствует = SILENT_ABSENCE. Real: 24 авто-REFUTED = 13 мусор якорей + 10 настоящих удалений + 1 ЛОЖНЫЙ (ADR-7232a6e2ba34 жив, отозван по старому пути src/utils/paths.py из prose тела). Synthetic 1-C: git mv 30 файлов одним коммитом → 30/30 REFUTED (100%); body-hash → 30/30 уцелели. Red-Team: batch-по-коммиту спасает настоящие удаления (e661861f = D+R083 в одном коммите).
- **Статус:** 🔬 открыт — решение не принято (вопрос владельцу: body-hash carry против стоимости)

## 2026-09-02 20:51 — drift_gate заблокировал коммит: контроль остановил самого автора

- **Источник:** AGENT_DIARY.md
Expand Down
7 changes: 7 additions & 0 deletions WISDOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@
- LanceDB atomic delete+add: фикс table.version + restore(prev_version) при сбое add
(db_writer.py) — нативный versioning лучше temp+os.replace.

## Commit overhead — НЕ баг (2026-09-11, решение владельца)
- Pre-commit gate-zero (scripts/verify_diary.py:443) = полный pytest ~178s standalone;
остальные 7 hook-скриптов ≈ 10s. «8-минутный коммит» — CPU contention от 2 llama-servers
(embed+rerank) при коммите, НЕ медленные тесты. pytest остаётся single-thread, НЕ хтейзить.
- Помогать не нужно: временный `pytest --basetemp <tmp>` — при параллельных прогонах два pytest
коллизят на общем `pytest-of-misha/pytest-0` → 452 ложных FileNotFoundError (а не баг кода).

## Doc-vs-code semantic drift (2026-08-12)
- stale_detector сверяет ТОЛЬКО version-строки — имена тулов/счётчики в доках
НЕ проверяются. Кросс-чек: AGENTS.md B перечислял НЕСУЩЕСТВУЮЩИЕ тулы
Expand Down
146 changes: 146 additions & 0 deletions experiments/1V_memory_contamination/burst_rename_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Experiment 1-B: Burst-Rename Audit — сколько «SILENT_ABSENCE» на деле переименования.

Гипотеза (из вопроса Statewave в dev.to): после refactor-коммита с массовыми
move/rename пачка якорей исчезает разом. Часть таких исчезновений — не удаления,
а переименования (файл жив под новым путём). Сколько из авто-REFUTED на реальной
памяти проекта — ложные отзывы по переименованным файлам?

Дизайн:
- Читаем реальный project_memory.json (bfe9644b).
- Для каждой ноды с retract_source == verify_on_read извлекаем failed-якорь из
retract_reason ("SILENT_ABSENCE_ON_READ: file:X").
- Для X ищем в git-истории (--diff-filter=R`), переименован ли он:
has_rename -> файл был переименован после записи ноды (live elsewhere) -> FALSE_REFUTE
no_rename -> файл никогда не существовал или удалён без rename -> TRUE_REFUTE
never_had -> импорт/мусорный якорь (import:X, pkg:- и т.п.)
- Выход: таблица + агрегат (сколько отзывов — реальные удаления).

Запуск (GitBash/win):
venv/Scripts/python.exe experiments/1V_memory_contamination/burst_rename_audit.py
"""
from __future__ import annotations

import json
import re
import subprocess
import sys
from pathlib import Path

if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")

ROOT = Path(r"D:\Project\MSCodeBase")
MEM = Path(r"C:\Users\misha\AppData\Local\mscodebase\projects\bfe9644b\intelligence\project_memory.json")

_CREATE_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0


def git(*args: str, max_commit: str | None = None) -> str:
"""git <args> -- <file>, ограниченный временем до коммита (если задан)."""
cmd = ["git", "-C", str(ROOT), *args]
if max_commit is not None:
cmd += [max_commit]
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
creationflags=_CREATE_NO_WINDOW,
)
out, _ = proc.communicate(timeout=20)
return out.decode("utf-8", "replace")


RENAME_SOURCES: set[str] | None = None


def rename_sources() -> set[str]:
"""Все src-пути R*-переименований в истории (кэш на процесс)."""
global RENAME_SOURCES
if RENAME_SOURCES is not None:
return RENAME_SOURCES
s: set[str] = set()
try:
out = git("log", "--all", "--diff-filter=R", "--name-status", "--format=")
except Exception:
RENAME_SOURCES = s
return s
for line in out.splitlines():
parts = line.strip().split("\t")
if len(parts) >= 2 and parts[0].startswith("R"):
src = parts[1].replace("\\", "/").lstrip("./")
s.add(src)
# basename-маппинг: якорь мог быть записан как `src/utils/paths.py`
# или `paths.py` (нормализация в ADR-теле). Ложь только для
# дублирующихся basename — здесь редкое исключение, принимаем.
s.add(src.split("/")[-1])
RENAME_SOURCES = s
return s


def had_rename(path: str) -> bool:
"""Был ли путь целю R*-переименования где-либо в git-истории."""
norm = path.replace("\\", "/").lstrip("./")
return norm in rename_sources() or norm.split("/")[-1] in rename_sources()


def main() -> int:
try:
nodes = json.loads(MEM.read_text(encoding="utf-8"))
if not isinstance(nodes, list):
raise ValueError("project_memory.json: ожидался список нод")

rows = []
for n in nodes:
if n.get("retract_source") != "verify_on_read":
continue
reason = n.get("retract_reason") or ""
m = re.search(r"file:(\S+)", reason)
failed = m.group(1) if m else None
if failed is None or "://" in failed or failed.startswith("C:"):
verdict = "anchor_junk"
note = reason
elif had_rename(failed):
verdict = "FALSE_REFUTE_renamed"
note = "файл переименован — жив под другим путём"
else:
verdict = "TRUE_REFUTE_deleted"
note = "файл никогда не существовал/удалён без rename"
rows.append({
"node_id": n.get("node_id"),
"status": n.get("status"),
"failed_anchor": failed,
"verdict": verdict,
"note": note,
})

agg: dict = {}
for r in rows:
agg[r["verdict"]] = agg.get(r["verdict"], 0) + 1

print("=" * 88)
print("Experiment 1-B: Burst-Rename Audit (реальные авто-отзывы VOR)")
print(f"авто-REFUTED нод (retract_source=verify_on_read): {len(rows)}")
print("-" * 88)
for r in rows:
print(f" {r['node_id']:<18} {r['verdict']:<22} {r['failed_anchor']}")
print(f" {r['note']}")
print("-" * 88)
print("Агрегат:")
for k, v in sorted(agg.items(), key=lambda kv: -kv[1]):
print(f" {k:<24} {v}")
n_false = agg.get("FALSE_REFUTE_renamed", 0)
n_true = agg.get("TRUE_REFUTE_deleted", 0)
total_checked = n_false + n_true
if total_checked:
rate = n_false / total_checked
print(f"\nЛожные отзывы по rename-якорям: {n_false}/{total_checked} = {rate:.1%}")
print("=" * 88)
return 0
except (OSError, ValueError, json.JSONDecodeError) as e:
import traceback
traceback.print_exc()
return 1


if __name__ == "__main__":
sys.exit(main())
Loading
Loading