Clean up nested duplicate: v2/v2/selftest.py
Browse files- v2/v2/selftest.py +0 -201
v2/v2/selftest.py
DELETED
|
@@ -1,201 +0,0 @@
|
|
| 1 |
-
"""Rivet v2 self-test — the full pipeline, offline, no model required.
|
| 2 |
-
|
| 3 |
-
Covers: config parsing (fallback parser specifically), BDI seeding,
|
| 4 |
-
intent classification, plan construction + DAG validation, Pharos
|
| 5 |
-
routing (keyword fallback), the tool guard, and two end-to-end runs
|
| 6 |
-
through the real DAG executor with a fake model:
|
| 7 |
-
|
| 8 |
-
1. a destructive-SQL draft -> gate BLOCKS
|
| 9 |
-
2. a clean draft -> gate passes, confidence graded
|
| 10 |
-
|
| 11 |
-
Run: python selftest.py
|
| 12 |
-
Exit: 0 all passed, 1 otherwise.
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
import json
|
| 16 |
-
import sys
|
| 17 |
-
import tempfile
|
| 18 |
-
from pathlib import Path
|
| 19 |
-
|
| 20 |
-
V2_ROOT = Path(__file__).parent
|
| 21 |
-
sys.path.insert(0, str(V2_ROOT))
|
| 22 |
-
|
| 23 |
-
PASS, FAIL = 0, 0
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
def check(name: str, condition: bool, detail: str = ""):
|
| 27 |
-
global PASS, FAIL
|
| 28 |
-
if condition:
|
| 29 |
-
PASS += 1
|
| 30 |
-
print(f" ok {name}")
|
| 31 |
-
else:
|
| 32 |
-
FAIL += 1
|
| 33 |
-
print(f" FAIL {name} {detail}")
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
class FakeModel:
|
| 37 |
-
"""ModelClient stand-in with scripted replies."""
|
| 38 |
-
|
| 39 |
-
def __init__(self, reply_text: str):
|
| 40 |
-
self.reply_text = reply_text
|
| 41 |
-
self.calls = []
|
| 42 |
-
|
| 43 |
-
def generate(self, prompt, system="", knowledge="", max_tokens=1024):
|
| 44 |
-
from engine.model_client import ModelReply
|
| 45 |
-
self.calls.append({"prompt": prompt, "system": system,
|
| 46 |
-
"knowledge": knowledge})
|
| 47 |
-
return ModelReply(
|
| 48 |
-
text=self.reply_text, ok=True, backend="fake",
|
| 49 |
-
knowledge_injected="system_prompt" if knowledge else "none",
|
| 50 |
-
)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
def main() -> int:
|
| 54 |
-
print("== config ==")
|
| 55 |
-
from engine.config import _parse_subset, load_config
|
| 56 |
-
cfg_text = (V2_ROOT / "kintsugi_config.yaml").read_text()
|
| 57 |
-
cfg_fallback = _parse_subset(cfg_text)
|
| 58 |
-
check("fallback parser: model section",
|
| 59 |
-
cfg_fallback.get("model", {}).get("name") == "qwen2.5-coder:32b",
|
| 60 |
-
str(cfg_fallback.get("model")))
|
| 61 |
-
check("fallback parser: constraints list",
|
| 62 |
-
len(cfg_fallback.get("beliefs", {}).get("constraints", [])) == 3)
|
| 63 |
-
check("fallback parser: nested scalar types",
|
| 64 |
-
cfg_fallback["session"]["ttl_seconds"] == 3600
|
| 65 |
-
and cfg_fallback["tools"]["repo_write_enabled"] is False)
|
| 66 |
-
cfg = load_config(V2_ROOT / "kintsugi_config.yaml")
|
| 67 |
-
check("load_config parses", isinstance(cfg, dict) and "org" in cfg)
|
| 68 |
-
|
| 69 |
-
print("== kintsugi core ==")
|
| 70 |
-
from kintsugi_core import KINTSUGI_SOURCE, BDIStore
|
| 71 |
-
print(f" (kintsugi source: {KINTSUGI_SOURCE})")
|
| 72 |
-
check("kintsugi imports resolve", True)
|
| 73 |
-
|
| 74 |
-
print("== BDI seeding ==")
|
| 75 |
-
from engine.beliefs import seed_bdi
|
| 76 |
-
context_dir = (V2_ROOT / ".." / "context").resolve()
|
| 77 |
-
bdi = BDIStore("selftest_org")
|
| 78 |
-
seed_bdi(bdi, cfg, context_dir)
|
| 79 |
-
beliefs = bdi.list_beliefs()
|
| 80 |
-
shared = bdi.get_belief("belief_constraint_shared_db")
|
| 81 |
-
check("beliefs seeded", len(beliefs) >= 15, f"got {len(beliefs)}")
|
| 82 |
-
check("shared-db constraint at confidence 1.0",
|
| 83 |
-
shared is not None and shared.confidence == 1.0)
|
| 84 |
-
check("audit findings became beliefs",
|
| 85 |
-
bdi.get_belief("belief_audit_c2") is not None)
|
| 86 |
-
check("desires seeded", len(bdi.list_desires()) == 3)
|
| 87 |
-
|
| 88 |
-
print("== tool guard ==")
|
| 89 |
-
from tools.guard import check_command
|
| 90 |
-
check("guard blocks unlisted binary",
|
| 91 |
-
check_command(["curl", "http://x"]) != "")
|
| 92 |
-
check("guard blocks suspicious pattern",
|
| 93 |
-
check_command(["git", "log", ";rm -rf /"]) != "")
|
| 94 |
-
check("guard allows clean git", check_command(["git", "log"]) == "")
|
| 95 |
-
|
| 96 |
-
print("== migration classification ==")
|
| 97 |
-
from skills.migration_safety import classify_sql, extract_sql
|
| 98 |
-
destructive = classify_sql("ALTER TABLE students DROP COLUMN score;")
|
| 99 |
-
additive = classify_sql(
|
| 100 |
-
"ALTER TABLE students ADD COLUMN score int DEFAULT 0;")
|
| 101 |
-
check("DROP COLUMN classified destructive", destructive["destructive"])
|
| 102 |
-
check("ADD COLUMN classified additive",
|
| 103 |
-
additive["additive"] and not additive["destructive"])
|
| 104 |
-
prose = extract_sql("You should never drop a table in prod.")
|
| 105 |
-
fenced = extract_sql("```sql\nDROP TABLE users;\n```")
|
| 106 |
-
check("prose SQL not extracted", prose == [])
|
| 107 |
-
check("fenced SQL extracted", len(fenced) == 1)
|
| 108 |
-
|
| 109 |
-
print("== intent classification ==")
|
| 110 |
-
from engine.planner import classify_intent
|
| 111 |
-
check("migration intent",
|
| 112 |
-
classify_intent("Add a migration for a new lessons column") == "migration")
|
| 113 |
-
check("auth intent",
|
| 114 |
-
classify_intent("Why does verifyToken reject the Bearer token?") == "auth")
|
| 115 |
-
check("question intent",
|
| 116 |
-
classify_intent("What does the campus use for state?") == "question")
|
| 117 |
-
|
| 118 |
-
print("== pharos routing (keyword fallback) ==")
|
| 119 |
-
with tempfile.TemporaryDirectory() as tmp:
|
| 120 |
-
import subprocess
|
| 121 |
-
result = subprocess.run(
|
| 122 |
-
[sys.executable, str(V2_ROOT / "pharos" / "build_campus_pack.py"),
|
| 123 |
-
"--context-dir", str(context_dir), "--out", tmp],
|
| 124 |
-
capture_output=True, text=True,
|
| 125 |
-
)
|
| 126 |
-
check("campus pack builds", result.returncode == 0, result.stderr[:300])
|
| 127 |
-
from pharos.pack_loader import PackLibrary, PharosRouter
|
| 128 |
-
library = PackLibrary(tmp)
|
| 129 |
-
check("campus pack loads",
|
| 130 |
-
len(library.packs) == 1
|
| 131 |
-
and len(library.packs[0].triples) >= 40,
|
| 132 |
-
f"packs={[p.name for p in library.packs]}")
|
| 133 |
-
router = PharosRouter(library)
|
| 134 |
-
# force keyword path even if sentence-transformers is installed —
|
| 135 |
-
# the fallback must work on a bare box
|
| 136 |
-
router._st_model = None
|
| 137 |
-
routed = router.route(
|
| 138 |
-
"How do I write a safe migration for the shared database?")
|
| 139 |
-
check("keyword routing finds campus pack",
|
| 140 |
-
"campus_architecture" in routed.pack_names,
|
| 141 |
-
routed.explanation)
|
| 142 |
-
|
| 143 |
-
print("== end-to-end: destructive draft is BLOCKED ==")
|
| 144 |
-
import rivet as rivet_mod
|
| 145 |
-
bad_model = FakeModel(
|
| 146 |
-
"Just drop the old column:\n```sql\nALTER TABLE students DROP "
|
| 147 |
-
"COLUMN legacy_score;\n```\nThat cleans it up."
|
| 148 |
-
)
|
| 149 |
-
agent = rivet_mod.RivetAgent(model_client=bad_model)
|
| 150 |
-
out = agent.ask("Write a migration to remove legacy_score from students",
|
| 151 |
-
user="selftest")
|
| 152 |
-
check("plan used migration intent", out.get("intent") == "migration",
|
| 153 |
-
json.dumps(out)[:300])
|
| 154 |
-
check("gate blocked destructive SQL", out.get("gate_passed") is False,
|
| 155 |
-
json.dumps(out.get("flags", []))[:300])
|
| 156 |
-
check("blocked answer withholds the SQL",
|
| 157 |
-
"DROP COLUMN" not in out.get("response", "").upper()
|
| 158 |
-
or "BLOCKED" in out.get("response", ""))
|
| 159 |
-
check("shared-db belief consulted",
|
| 160 |
-
"belief_constraint_shared_db" in out.get("beliefs_consulted", []))
|
| 161 |
-
|
| 162 |
-
print("== end-to-end: clean draft passes ==")
|
| 163 |
-
good_model = FakeModel(
|
| 164 |
-
"Add the column additively:\n```sql\nALTER TABLE students ADD "
|
| 165 |
-
"COLUMN score integer DEFAULT 0;\n```\nWhat it changes: one new "
|
| 166 |
-
"nullable-with-default column. What could break: nothing — old "
|
| 167 |
-
"code ignores it. Tests: extend the students model test."
|
| 168 |
-
)
|
| 169 |
-
agent2 = rivet_mod.RivetAgent(model_client=good_model)
|
| 170 |
-
out2 = agent2.ask("Add a migration for a students score column",
|
| 171 |
-
user="selftest2")
|
| 172 |
-
check("gate passed clean answer", out2.get("gate_passed") is True,
|
| 173 |
-
json.dumps(out2.get("flags", []))[:400])
|
| 174 |
-
check("confidence graded (MEDIUM without source reads)",
|
| 175 |
-
out2.get("confidence") == "MEDIUM", out2.get("confidence"))
|
| 176 |
-
check("gate annotations attached",
|
| 177 |
-
"Discipline Gate" in out2.get("response", "")
|
| 178 |
-
or "Confidence" in out2.get("response", ""))
|
| 179 |
-
check("synthesis saw beliefs in prompt",
|
| 180 |
-
"ORGANIZATIONAL BELIEFS" in good_model.calls[0]["prompt"]
|
| 181 |
-
and "share one PostgreSQL" in good_model.calls[0]["prompt"])
|
| 182 |
-
|
| 183 |
-
print("== end-to-end: auth question flags review ==")
|
| 184 |
-
auth_model = FakeModel(
|
| 185 |
-
"The middleware order is verifyToken then rejectIfIneligible. "
|
| 186 |
-
"To add a route guard use requireAdmin."
|
| 187 |
-
)
|
| 188 |
-
agent3 = rivet_mod.RivetAgent(model_client=auth_model)
|
| 189 |
-
out3 = agent3.ask("How should I change the JWT auth middleware to add "
|
| 190 |
-
"a new admin route?", user="selftest3")
|
| 191 |
-
check("auth intent planned", out3.get("intent") == "auth", out3.get("intent"))
|
| 192 |
-
check("security review required",
|
| 193 |
-
"security" in out3.get("requires_review", []),
|
| 194 |
-
json.dumps(out3)[:300])
|
| 195 |
-
|
| 196 |
-
print(f"\n{PASS} passed, {FAIL} failed")
|
| 197 |
-
return 0 if FAIL == 0 else 1
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
if __name__ == "__main__":
|
| 201 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|