simbiot-harness/harness/paths.py
Simbiot Harness 3dc6ca6ab9 t-261004113351: изоляция памяти инстанса — SIMBIOT_HOME (репо = чистый код)
- harness/paths.py: единая карта персистентных путей; SIMBIOT_HOME → db/ rag/
  env/ docs/ digests/ audit/ inbox/ run/ voice/; без него — legacy-пути без
  изменений; точечные env (SIMBIOT_DB и пр.) сильнее схемы
- es/tg_inbox/voice/portal_channel/aum/audit/runtime/rag_lookup/isolated_
  runner/cli: все записи-чтения памяти через paths
- scripts/init_instance.py: новый инстанс — чистая база + идентичность
  владельца + его доки (+RAG) + селективный импорт выжимок/доков
- tests/test_instance_isolation.py: копия с пустой памятью не вспоминает
  наши проекты (контекст тика/RAG/прод — 14 проверок ALL PASS, регресс 7/7)
- scripts/migrate_to_home.sh: перенос боевого инстанса в хом (тихое окно,
  идемпотентно, RAG-корпус коопа сохраняется точечным SIMBIOT_RAG_DB)
- install.sh/service/README: свежие установки сразу с ~/.simbiot-data
2026-10-04 12:34:45 +00:00

143 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Simbiot Harness — единая карта персистентных путей (SIMBIOT_HOME).
t-261004113351-2858c1 (мультитенантность): память и знания инстанса — данные,
а не код. Репозиторий = чистый код; всё, что живёт дольше процесса (ES-база,
RAG-индекс знаний, входящие каналы, выжимки, секреты, доки инстанса),
размещается в одном каталоге-хоме.
SIMBIOT_HOME (env) — корень данных инстанса. Задан → структура:
<home>/db/harness.db — ES-база (события, проекции, delivery-леджер)
<home>/rag/rag.db — RAG-индекс знаний (векторные чанки)
<home>/env/.env — секреты инстанса (токены, ключи роутера)
<home>/inbox/ — входящие каналов (portal/state, legacy outbox)
<home>/digests/ — файлы-выжимки уровней (summary_ref)
<home>/audit/ — ключи крипто-аудита (ed25519)
<home>/docs/ — проектные документы инстанса (aum/balances.json)
<home>/voice/ — кэш голосовых (если используется ASR)
<home>/run/ — runtime-штампы каналов (channel_<tg|portal>.json)
Не задан (legacy) → всё по-старому из корня репо: harness/harness.db, .env,
docs/, out/, .audit/ — обратная совместимость существующих установок и
systemd-юнитов без правки окружения.
Приоритет источников (12-фактор): точечная env-переменная (SIMBIOT_DB,
SIMBIOT_RAG_DB, SIMBIOT_AUDIT_KEY_DIR, ...) сильнее схемы SIMBIOT_HOME,
SIMBIOT_HOME сильнее legacy-дефолта.
"""
import os
_HARNESS_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(_HARNESS_DIR)
_HOME = os.environ.get("SIMBIOT_HOME", "").strip()
IS_ISOLATED = bool(_HOME)
def home() -> str:
"""Корень данных инстанса (SIMBIOT_HOME или корень репо — legacy)."""
return _HOME or REPO_ROOT
# ── ES-база: события/проекции/идемпотентная доставка ────────────────────────
def db_path() -> str:
env = os.environ.get("SIMBIOT_DB", "").strip()
if env:
return env
if IS_ISOLATED:
return os.path.join(_HOME, "db", "harness.db")
return os.path.join(_HARNESS_DIR, "harness.db") # legacy: как было
# ── .env: секреты инстанса ──────────────────────────────────────────────────
def env_path() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "env", ".env")
return os.path.join(REPO_ROOT, ".env")
# ── RAG-индекс знаний ───────────────────────────────────────────────────────
def rag_db() -> str:
env = os.environ.get("SIMBIOT_RAG_DB", "").strip()
if env:
return env
if IS_ISOLATED:
return os.path.join(_HOME, "rag", "rag.db")
return "/root/rag/rag.db" # legacy (общая база кооп-хоста, как было)
# ── выжимки уровней (файлы summary_ref) ─────────────────────────────────────
def digests_dir() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "digests")
return os.path.join(REPO_ROOT, "out") # legacy out/
# ── проектные документы инстанса ────────────────────────────────────────────
def docs_dir() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "docs")
return os.path.join(REPO_ROOT, "docs")
# ── ключи крипто-аудита ─────────────────────────────────────────────────────
def audit_key_dir() -> str:
env = os.environ.get("SIMBIOT_AUDIT_KEY_DIR", "").strip()
if env:
return env
if IS_ISOLATED:
return os.path.join(_HOME, "audit")
return os.path.join(REPO_ROOT, ".audit")
# ── входящие каналов: state + legacy-каталоги файлопередачи ─────────────────
def channel_state_file() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "inbox", ".channel-last")
return os.path.join(REPO_ROOT, "portal", "inbox", ".channel-last")
def inbox_pending_dir() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "inbox", "pending")
return os.path.join(REPO_ROOT, "portal", "inbox", "pending")
def inbox_outbox_dir() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "inbox", "outbox")
return os.path.join(REPO_ROOT, "portal", "inbox", "outbox")
# ── runtime-штампы каналов для дашборда ─────────────────────────────────────
def stamp_dir() -> str:
if IS_ISOLATED:
return os.path.join(_HOME, "run")
return REPO_ROOT # legacy: <repo>/harness/channel_<name>.json
# ── кэш голосовых ───────────────────────────────────────────────────────────
def voice_dir() -> str:
env = os.environ.get("SIMBIOT_VOICE_DIR", "").strip()
if env:
return env
if IS_ISOLATED:
return os.path.join(_HOME, "voice")
return "/var/lib/simbiot-harness/voice" # legacy
def ensure_instance_dirs() -> list:
"""Создать структуру хома изолированного инстанса. Идемпотентно.
В legacy-режиме ничего не создаёт (там всё уже лежит как лежало).
Возвращает список созданных каталогов.
"""
if not IS_ISOLATED:
return []
made = []
for sub in ("db", "rag", "env", "inbox", "digests", "audit", "docs", "voice", "run"):
p = os.path.join(_HOME, sub)
if not os.path.isdir(p):
os.makedirs(p, exist_ok=True)
made.append(p)
return made