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