Skip to content
Open
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
26 changes: 26 additions & 0 deletions docs/architecture/LOCAL-MODEL-RESERVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,29 @@ ausdrücklich gestartete tatsächliche Aufgabe mit Tool-/Ergebnis-/Leasebeleg.
Rückweg: laufenden Versuch an sicherer Grenze beenden, Policy ausdrücklich
deaktivieren oder den vorherigen integrierten Stand einsetzen. Das alte Verhalten
ohne Reservensperre wird dadurch nicht zu einer Kapazitätsfreigabe.


## Pfadprüfung über den installierten API-Einstieg

Die Ressourcenprüfung bezieht den TaskDB-Pfad direkt aus der bestehenden
Pfadautorität `hub.bach_paths.BACH_DB`. Sie benötigt keinen privaten Export
aus `bach_api`. Der Root-Einstieg für editable Installationen exportiert
nur die öffentliche API; eine andere Importreihenfolge darf die
Ressourcenprüfung deshalb nicht verändern. Slot- und Runtimepfade folgen
weiterhin ihrer vorhandenen Konfiguration. Die Pfadprüfung öffnet oder
migriert keine Datenbank und legt fehlende Dateien nicht an.

Die Regression verwendet einen eigenen Prozess mit dem echten Root-Einstieg
vor `system`, isolierten TaskDB-/Slot-/Runtimepfaden und einem gesperrten
Datenbankzugriff. Sie prüft sowohl die Dateisystemwurzeln als auch eine
Ressourcenprobe mit synthetischen Modell- und Kapazitätswerten. Diese Prüfung
belegt den Importvertrag, keine echte Inferenz oder Kapazität des Hauptmodells.

Am 12.10.2026 wurden die native Ressourcenpolicy und die Modellsteckplatz-
Registry explizit aktiviert. Der danach gestartete Buddha-Versuch für #2120
scheiterte vor der Inferenz am privaten API-Import; die Task blieb offen und
ihre Lease wurde zurückgegeben. Die vorherigen Metadatenproben verwendeten
einen anderen Importkontext und deckten diesen Fehler nicht ab.
Eine Quellenkorrektur allein startet den Worker nicht neu. Nach regulärer
Integration sind der tatsächliche Control-Importkontext, ein ausdrücklich
gestarteter Lauf, Werkzeugwirkung und Ergebnis erneut nachzuweisen.
4 changes: 2 additions & 2 deletions system/hub/_services/chat/local_resource_reserve.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ def read_policy():
def _data_roots():
# Path resolution does not open or migrate the canonical task database.
from hub._services.chat.slots_config import DEFAULT_SLOTS_FILE
from hub.bach_paths import BACH_DB

from bach_api import _resolve_db_path
roots = {"taskdb": _resolve_db_path().parent, "runtime": runtime_root(),
roots = {"taskdb": Path(BACH_DB).expanduser().parent, "runtime": runtime_root(),
"slots": Path(DEFAULT_SLOTS_FILE).expanduser().parent}
result = {}
for label, root in roots.items():
Expand Down
109 changes: 109 additions & 0 deletions system/tests/test_local_resource_reserve.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"""Fake model metadata and isolated files; no real inference or task writes."""
import asyncio
import json
import os
import subprocess
import sys
import textwrap
from copy import deepcopy
from pathlib import Path
from types import SimpleNamespace

import httpx
Expand Down Expand Up @@ -572,3 +577,107 @@ async def forbidden(*_, **__): pytest.fail("An internal gate wait cannot authori
asyncio.run(runtime._chat_with_compute_turn(backend, []))
assert runtime._compute_turn_gate.active is False
assert runtime.compute_turn_status()["active"] is False


@pytest.mark.parametrize("operation", ["roots", "probe"])
def test_root_api_shim_resource_paths_without_database_access(tmp_path, operation):
"""Exercise the editable-install facade instead of the system-only import."""
source_root = Path(__file__).resolve().parents[2]
database_dir = tmp_path / "database"
slots_dir = tmp_path / "slots"
runtime_dir = tmp_path / "runtime"
for directory in (database_dir, slots_dir, runtime_dir):
directory.mkdir()
isolated_env = os.environ.copy()
isolated_env.update({
"BACH_DB": str(database_dir / "absent.db"),
"BACH_LOCAL_DIR": str(database_dir),
"BACH_RUNTIME_DIR": str(runtime_dir),
"BACH_SLOTS_CONFIG_PATH": str(slots_dir / "absent.json"),
"PYTHONDONTWRITEBYTECODE": "1",
"PYTHONIOENCODING": "utf-8",
"PYTHONPATH": os.pathsep.join(filter(None, (
str(source_root), str(source_root / "system"),
isolated_env.get("PYTHONPATH", ""),
))),
})
script = textwrap.dedent("""
import asyncio
import json
import os
import sqlite3
import sys
from pathlib import Path
from types import SimpleNamespace

def forbidden_database(*args, **kwargs):
raise AssertionError("Resource path resolution must not open a DB")

sqlite3.connect = forbidden_database
import bach_api
source_root = Path(sys.argv[1])
assert Path(bach_api.__file__).resolve() == source_root / "bach_api.py"
assert not hasattr(bach_api, "_resolve_db_path")
from hub.bach_paths import BACH_DB
from hub._services.chat import local_resource_reserve as reserve
from hub._services.llm.model_backend import OllamaBackend
expected = {
"taskdb": Path(os.environ["BACH_DB"]).parent,
"slots": Path(os.environ["BACH_SLOTS_CONFIG_PATH"]).parent,
"runtime": Path(os.environ["BACH_RUNTIME_DIR"]).expanduser().resolve(),
}
# The process guard may supply its own isolated runtime directory.
expected["runtime"].mkdir(parents=True, exist_ok=True)
assert BACH_DB == Path(os.environ["BACH_DB"])
assert reserve._data_roots() == expected, (reserve._data_roots(), expected)
if sys.argv[2] == "probe":
model = "example:small"
digest = "a" * 64
gib = reserve.GIB
policy = {
"schema": reserve.SCHEMA, "enabled": True,
"memory_reserve_bytes": gib, "disk_reserve_bytes": gib,
"wait_seconds": 2, "poll_seconds": 1,
"models": {model: {
"digest": digest, "weight_budget_bytes": 2 * gib,
"kv_headroom_bytes": gib // 2,
"overhead_bytes": gib // 4, "num_ctx": 1024,
}},
}
path = reserve.policy_path()
path.parent.mkdir(parents=True)
path.write_text(json.dumps(policy), encoding="utf-8")

async def fake_metadata(*args):
return {
"external": False, "digest": digest,
"weight_floor_bytes": 2 * gib, "resident_context": None,
}

reserve.observe_ollama = fake_metadata
reserve.psutil.virtual_memory = lambda: SimpleNamespace(
total=32 * gib, available=8 * gib
)
checked_roots = []

def fake_disk_usage(path):
checked_roots.append(path)
return SimpleNamespace(free=4 * gib)

reserve.shutil.disk_usage = fake_disk_usage
receipt = asyncio.run(reserve.probe(
OllamaBackend(default_model=model, num_ctx=1024), model
))
assert receipt["state"] == "ready" and receipt["admitted"]
assert set(checked_roots) == set(expected.values())
assert not Path(os.environ["BACH_DB"]).exists()
assert not Path(os.environ["BACH_SLOTS_CONFIG_PATH"]).exists()
print("ROOT_API_RESOURCE_PATHS_OK")
""")
completed = subprocess.run(
[sys.executable, "-c", script, str(source_root), operation],
cwd=tmp_path, env=isolated_env, capture_output=True, text=True,
timeout=30, check=False,
)
assert completed.returncode == 0, completed.stdout + completed.stderr
assert "ROOT_API_RESOURCE_PATHS_OK" in completed.stdout
Loading