Fix session resume and PSD container configuration
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from importlib import reload
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_cli_default_config_honors_tht_config(monkeypatch):
|
||||
from tht.cli import config_cmd
|
||||
|
||||
previous = os.environ.get("THT_CONFIG")
|
||||
try:
|
||||
monkeypatch.setenv("THT_CONFIG", "/app/harness/config/tht.yaml")
|
||||
reload(config_cmd)
|
||||
|
||||
assert config_cmd.CONFIG_OPT.default == Path("/app/harness/config/tht.yaml")
|
||||
finally:
|
||||
if previous is None:
|
||||
monkeypatch.delenv("THT_CONFIG", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("THT_CONFIG", previous)
|
||||
reload(config_cmd)
|
||||
@@ -0,0 +1,63 @@
|
||||
import json
|
||||
|
||||
from tht.decisions import append_decision
|
||||
from tht.phase import advance_problems
|
||||
|
||||
|
||||
def _phase_four_session(tmp_path):
|
||||
session = tmp_path / "session"
|
||||
session.mkdir()
|
||||
for phase in range(1, 4):
|
||||
append_decision(session, type="phase_approved", subject=f"phase:{phase}")
|
||||
return session
|
||||
|
||||
|
||||
def test_phase_four_cannot_close_multiple_tables_without_reviewed_structured_joins(tmp_path):
|
||||
session = _phase_four_session(tmp_path)
|
||||
(session / "schema_linking.json").write_text(json.dumps({
|
||||
"question": "q",
|
||||
"candidates": [
|
||||
{"kind": "table", "name": "fact_event", "decision": "promoted"},
|
||||
{"kind": "table", "name": "dim_patient", "decision": "promoted"},
|
||||
],
|
||||
"joins": [],
|
||||
}))
|
||||
|
||||
problems = advance_problems(session, 4)
|
||||
|
||||
assert any("join" in problem.lower() for problem in problems)
|
||||
|
||||
|
||||
def test_phase_four_allows_a_single_promoted_table_without_joins(tmp_path):
|
||||
session = _phase_four_session(tmp_path)
|
||||
(session / "schema_linking.json").write_text(json.dumps({
|
||||
"question": "q",
|
||||
"candidates": [
|
||||
{"kind": "table", "name": "dim_patient", "decision": "promoted"},
|
||||
],
|
||||
"joins": [],
|
||||
}))
|
||||
|
||||
assert advance_problems(session, 4) == []
|
||||
|
||||
|
||||
def test_phase_four_allows_reviewed_structured_joins_for_multiple_tables(tmp_path):
|
||||
session = _phase_four_session(tmp_path)
|
||||
append_decision(
|
||||
session,
|
||||
type="join_modified",
|
||||
subject="fact_event_to_patient",
|
||||
detail="fact_event.cod_paz = dim_patient.cod_paz",
|
||||
)
|
||||
(session / "schema_linking.json").write_text(json.dumps({
|
||||
"question": "q",
|
||||
"candidates": [
|
||||
{"kind": "table", "name": "fact_event", "decision": "promoted"},
|
||||
{"kind": "table", "name": "dim_patient", "decision": "promoted"},
|
||||
],
|
||||
"joins": [
|
||||
{"from": "fact_event.cod_paz", "to": "dim_patient.cod_paz"},
|
||||
],
|
||||
}))
|
||||
|
||||
assert advance_problems(session, 4) == []
|
||||
@@ -0,0 +1,124 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class ComposeLoader(yaml.SafeLoader):
|
||||
pass
|
||||
|
||||
|
||||
def _compose_override(loader, node):
|
||||
if isinstance(node, yaml.MappingNode):
|
||||
return loader.construct_mapping(node)
|
||||
return loader.construct_sequence(node)
|
||||
|
||||
|
||||
ComposeLoader.add_constructor("!override", _compose_override)
|
||||
|
||||
|
||||
def test_psd_overlay_uses_generated_workspace_for_default_and_named_commands():
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
compose = yaml.load(
|
||||
(root / "deploy/compose.psd-local.yaml.example").read_text(),
|
||||
Loader=ComposeLoader,
|
||||
)
|
||||
core = compose["services"]["core"]
|
||||
|
||||
assert core["environment"]["THT_CONFIG"] == "/app/harness/config/tht.yaml"
|
||||
assert core["environment"]["THT_SECRETS_FILE"] == "/run/secrets/thothii.secrets"
|
||||
for name in (
|
||||
"THT_DB_NAME", "THT_DWH_REST_URL", "THT_VEC_REST_URL",
|
||||
"THT_VEC_WRITE_REST_URL", "THT_OLLAMA_URL", "THT_PROFILE",
|
||||
"PI_PROVIDER", "PI_MODEL", "PI_THINKING",
|
||||
):
|
||||
assert name in core["environment"]
|
||||
def target(volume):
|
||||
if isinstance(volume, dict):
|
||||
return volume["target"]
|
||||
parts = volume.rsplit(":", 2)
|
||||
return parts[-2] if parts[-1] in {"ro", "rw"} else parts[-1]
|
||||
|
||||
targets = {target(volume) for volume in core["volumes"]}
|
||||
assert "/app/harness/config/tht.yaml" in targets
|
||||
assert "/app/harness/workspaces/psd.yaml" not in targets
|
||||
assert "/data/workspaces/psd/config/tht.yaml" not in targets
|
||||
assert "/data" in targets
|
||||
assert "/home/thoth/.pi" in targets
|
||||
assert "/home/thoth/.pi/agent/models.json" in targets
|
||||
assert "/home/thoth/.pi/agent/settings.json" in targets
|
||||
assert "/data/evidence" in targets
|
||||
assert "/run/secrets/thothii.secrets" in targets
|
||||
assert set(compose["volumes"]) == {"thoth_data", "thoth_pi_config"}
|
||||
assert core["networks"]["default"]["aliases"] == ["core", "thothii-core"]
|
||||
assert compose["networks"]["default"] == {
|
||||
"external": True,
|
||||
"name": "thothii_default",
|
||||
}
|
||||
|
||||
|
||||
def test_core_image_prepares_the_writable_pi_profile_before_mounting_config_files():
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
dockerfile = (root / "docker/core.Dockerfile").read_text()
|
||||
|
||||
assert "mkdir -p /home/thoth/.pi/agent" in dockerfile
|
||||
assert "chown -R thoth:thoth /home/thoth/.pi" in dockerfile
|
||||
assert (
|
||||
"cp --remove-destination /app/harness/workspaces/local.yaml "
|
||||
"/app/harness/config/tht.yaml" in dockerfile
|
||||
)
|
||||
assert "ln -sf /app/harness/workspaces/local.yaml /app/harness/config/tht.yaml" not in dockerfile
|
||||
assert (
|
||||
"ln -sfn /app/harness/config/tht.yaml /app/harness/workspaces/psd.yaml"
|
||||
in dockerfile
|
||||
)
|
||||
|
||||
|
||||
def test_psd_bootstrap_materializes_the_base_compose_env_file(tmp_path):
|
||||
source_root = Path(__file__).resolve().parents[2]
|
||||
root = tmp_path / "ThothII"
|
||||
(root / "scripts").mkdir(parents=True)
|
||||
(root / "deploy/workspaces").mkdir(parents=True)
|
||||
shutil.copy(
|
||||
source_root / "scripts/bootstrap-local-psd-docker-config.sh",
|
||||
root / "scripts/bootstrap-local-psd-docker-config.sh",
|
||||
)
|
||||
shutil.copy(
|
||||
source_root / "deploy/compose.psd-local.yaml.example",
|
||||
root / "deploy/compose.psd-local.yaml.example",
|
||||
)
|
||||
shutil.copy(
|
||||
source_root / "deploy/workspaces/psd.yaml.example",
|
||||
root / "deploy/workspaces/psd.yaml.example",
|
||||
)
|
||||
source_env = tmp_path / "source.env"
|
||||
source_env.write_text("\n".join([
|
||||
"THT_DB_NAME=postgres",
|
||||
"THT_DWH_REST_URL=https://dwh.invalid/",
|
||||
"THT_VEC_REST_URL=https://vec.invalid/read/",
|
||||
"THT_VEC_WRITE_REST_URL=https://vec.invalid/write/",
|
||||
"THT_DWH_API_KEY=dwh",
|
||||
"THT_VEC_API_KEY=reader",
|
||||
"THT_VEC_WRITE_API_KEY=writer",
|
||||
"",
|
||||
]))
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
auth = tmp_path / "auth.json"
|
||||
auth.write_text('{"zai":{"key":"model"}}')
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"sh", str(root / "scripts/bootstrap-local-psd-docker-config.sh"),
|
||||
str(source_env), str(workspace), str(auth),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert (root / "deploy/thothii.env").is_file()
|
||||
assert "THT_SECRETS_FILE=./deploy/secrets/thothii.secrets" in (
|
||||
root / ".env"
|
||||
).read_text()
|
||||
Reference in New Issue
Block a user