File size: 25,475 Bytes
28c70af | 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 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | #!/usr/bin/env python3
# SPDX-License-Identifier: MIT OR Apache-2.0
"""The Savante UI's data layer, offline (stdlib only; Gradio is not imported): proofs of data, CIDs, .history
search, .memory, metrics, and the view server's routes. A temporary state directory; nothing touches the canon.
run: python3 testing/test_ui.py"""
import json, os, sys, tempfile, threading, urllib.request, urllib.error
from pathlib import Path
tmp = Path(tempfile.mkdtemp(prefix="bankml-ui-"))
os.environ.update(BANKML_UI_STATE=str(tmp), RAGE_PATH=str(tmp / "no-rage"), SAVANTE_CANON=str(tmp / "no-canon"), BANKML_VOICE_DIR=str(tmp / "voice"), BANKML_EXPORT_DIR=str(tmp / "export"))
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "sAGI"))
import savante as u # noqa: E402
fails = 0
def check(name, ok):
global fails
fails += not ok
print(("ok " if ok else "FAIL ") + name)
# CIDv1 raw of "abc" — the value mindX rage.py and the canon's ledger construction give
check("cid_v1_raw('abc')", u.cid_v1_raw(b"abc") == "bafkreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu")
# .history: records, commitment, inclusion proofs (odd and even sizes), tamper detection
for i in range(5):
u.history_append({"ts": 1790000000 + i, "sent_at": f"2026-09-28T10:0{i}:00.000-0700", "first_token_s": 10.0 + i, "response_s": 20.0 + 2 * i,
"session": "s1", "user": f"question {i} about verification" if i % 2 else f"question {i} about ternary kernels",
"assistant": f"answer {i}", "receipt": {"prompt_tokens": 300, "completion_tokens": 20, "response_sha256": u.sha256(f"answer {i}".encode())}})
c = u.commitment(u.HISTORY)
check("commitment counts 5 records, has a root and a CID", c["records"] == 5 and len(c["merkle_root"]) == 64 and c["file_cid"].startswith("bafkrei"))
lines = u._lines(u.HISTORY)
check("every record's inclusion proof verifies", all(u.verify_inclusion(lines[k], u.inclusion_proof(u.HISTORY, k)) for k in range(5)))
check("a changed byte fails its proof", not u.verify_inclusion(lines[2].replace(b"answer 2", b"answer X"), u.inclusion_proof(u.HISTORY, 2)))
check("a proof for another record fails", not u.verify_inclusion(lines[1], u.inclusion_proof(u.HISTORY, 3)))
check("out-of-range proof is empty", u.inclusion_proof(u.HISTORY, 9) == {})
# RFC 6962: the certificate-transparency reference roots, and the second-preimage cases the old tree allowed
ct = [bytes.fromhex(x) for x in ["", "00", "10", "2021", "3031", "40414243", "5051525354555657", "606162636465666768696a6b6c6d6e6f"]]
check("Merkle roots equal RFC 6962's reference vectors (1, 3 and 8 leaves)",
u.merkle_root([u._leaf(x) for x in ct[:1]]) == "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"
and u.merkle_root([u._leaf(x) for x in ct[:3]]) == "aeb6bcfe274b70a14fb067a5e5578264db0fa9b51af5e0ba159158f329e06e77"
and u.merkle_root([u._leaf(x) for x in ct]) == "5dc9da79a70659a9ad559cb701ded9a2ab9d823aad2f4960cfe370eff4604328")
lv = [u._leaf(l) for l in lines]
node = bytes.fromhex(lv[0]) + bytes.fromhex(lv[1]) # an interior node's preimage, offered as a "line"
fake = {"scheme": u.MERKLE_SCHEME, "record": 0, "records": 4, "leaf": u._leaf(node), "path": [u._node(lv[2], lv[3])], "merkle_root": u.merkle_root(lv[:4])}
check("an interior node cannot pass as a record (leaves and nodes are domain-separated)", not u.verify_inclusion(node, fake))
check("3 records and 4 records with the last repeated have different roots", u.merkle_root(lv[:3]) != u.merkle_root(lv[:3] + lv[2:3]))
check("a proof moved to another index fails", not u.verify_inclusion(lines[1], {**u.inclusion_proof(u.HISTORY, 1), "record": 0}))
# a torn last line (a crash mid-write) must not blank the history, and the next record must not be glued onto it
import shutil as _sh
torn = tmp / "torn.history"
_sh.copy(u.HISTORY, torn)
with open(torn, "ab") as f:
f.write(b'{"ts": 1, "user": "half a rec')
u.jsonl_append(torn, {"ts": 2, "user": "after the crash"})
recs = u.jsonl_read(torn)
check("a torn line is skipped and counted, the rest survives, and the next append starts on a new line",
len(recs) == 6 and recs[-1]["user"] == "after the crash" and u.JSONL_BAD[str(torn)] == 1)
check("a receipt is checked against the raw answer (think block and whitespace included)",
u._hash_ok({"assistant": "hi", "assistant_raw": "<think>x</think>\n hi ", "receipt": {"response_sha256": u.sha256("<think>x</think>\n hi ".encode())}})
and u._hash_ok({"assistant": "hi", "receipt": {"response_sha256": u.sha256(b"hi")}}))
u.INFLIGHT.clear()
u.INFLIGHT["lost"] = __import__("time").time() - u.STALE_S - 1
check("a lost request (a closed tab) expires instead of blocking model switches forever", u.chat_busy() is False and not u.INFLIGHT)
# embeddings (bge-m3 through Ollama), offline: a fake Ollama whose vectors are bags of words hashed into 1,024 dims
import http.server as _hs, threading as _th, embed as _em, math as _m
def _vec(t):
v = [0.0] * _em.DIMS
for w in __import__("re").findall(r"[a-z0-9]+", t.lower()):
v[int(__import__("hashlib").sha256(w.encode()).hexdigest(), 16) % _em.DIMS] += 1.0
return v
class _FakeOllama(_hs.BaseHTTPRequestHandler):
def do_GET(self):
b = json.dumps({"models": []}).encode()
self.send_response(200); self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)
def do_POST(self):
req = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
b = json.dumps({"embeddings": [_vec(t) for t in req["input"]]}).encode()
self.send_response(200); self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)
def log_message(self, *a):
pass
_fo = _hs.ThreadingHTTPServer(("127.0.0.1", 0), _FakeOllama)
_th.Thread(target=_fo.serve_forever, daemon=True).start()
_em.OLLAMA = f"http://127.0.0.1:{_fo.server_address[1]}"
_em.provenance = lambda: {"model": "bge-m3", "digest": "d1" * 32, "bytes": 1, "licence": "mit", "open": True, "dims": _em.DIMS}
_em._mem_available = lambda: 8 * 10 ** 9
r_idx = _em.index(u.HISTORY, u.history_all())
check("embed.index: every exchange embedded once, cached beside .history with the model digest",
r_idx["embedded"] == 5 and _em.index(u.HISTORY, u.history_all())["embedded"] == 0 and len(_em.cached(u.HISTORY)) == 5
and all(abs(sum(x * x for x in v) - 1) < 1e-5 for v in _em.cached(u.HISTORY).values()))
_em.provenance = lambda: {"model": "bge-m3", "digest": "e2" * 32, "bytes": 1, "licence": "mit", "open": True, "dims": _em.DIMS}
check("a cache written by other weights is not used (vectors are tied to the model's digest)", _em.cached(u.HISTORY) == {})
_em.provenance = lambda: {"model": "bge-m3", "digest": "d1" * 32, "bytes": 1, "licence": "mit", "open": True, "dims": _em.DIMS}
check("reciprocal rank fusion: first in both beats first in one", [i for i, _ in _em.fuse([1, 2, 3], [2, 3, 1])][0] == 2
and _em.fuse([7], [])[0][0] == 7)
hits_h, eng_h = u.history_search("ternary kernels")
check("the ragebar fuses BM25 and bge-m3 and says so", "bge-m3" in eng_h and "reciprocal rank" in eng_h and hits_h)
_mem = _em._mem_available
_em._mem_available = lambda: 10 ** 8
_em._QCACHE.clear()
hits_b, eng_b = u.history_search("verification")
check("without the memory to load bge-m3, the ragebar is BM25 alone (and still answers)", "bge-m3" not in eng_b and hits_b)
_em._mem_available = _mem
_em.provenance = lambda: {}
check("without bge-m3 in Ollama, embed says why and search stands on BM25", not _em.status()["ready"] and "bge-m3" not in u.history_search("verification")[1])
_fo.shutdown()
# the history window moves in steps, so the engine's prompt cache is reused (KoboldCpp's Smart Context idea; vLLM's
# chained block hashes show a one-exchange slide defeats it)
starts = [u.window_start(n) for n in range(1, 61)]
check("the window moves 8 times in 60 turns (a one-exchange slide moves 48 times)", sum(1 for a, b in zip(starts, starts[1:]) if a != b) == 8
and all(12 <= n - s <= 17 for n, s in zip(range(13, 61), starts[12:])))
tt = [(f"q{i}", f"a{i}\n\n<sub>⏱ sent 12:00:00 · receipt</sub>") for i in range(30)]
mm = u.build_messages("S", tt, "Q", False)
check("the model never sees the chat's footer (clock, receipt)", "<sub>" not in json.dumps(mm) and mm[2]["content"] == "a18")
cnt = lambda t: max(1, len(t) // 4) # noqa: E731 — a stand-in tokenizer, offline
big = [("q" * 100, "a" * 1200) for _ in range(13)]
u.build_messages("s" * 3600, big, "Q", False, ctx_tokens=2048, reserve_tokens=256, count=cnt)
check("never silent amnesia: when history cannot all fit, as much as fits is sent (0.1.8 sent none), and it is recorded",
u.LAST_WINDOW["sent"] >= 2 and u.LAST_WINDOW["of"] == 13 and u.LAST_WINDOW["ctx"] == 2048)
inf = {}
u.build_messages("s", [("q" * 50, "a" * 200)] * 20, "Q", False, ctx_tokens=32768, count=cnt, info=inf, session="roomy")
check("with room, the history note stays quiet (the ordinary 12–17 window is not a trim)", inf["sent"] == 14 and not inf["trimmed"])
seq = []
for k in range(12, 19): # consecutive turns in one session once the context binds: the start is kept while it fits
inf = {}
u.build_messages("s" * 3600, [("q" * 100, "a" * 800)] * k, "Q", False, ctx_tokens=4096, reserve_tokens=256, count=cnt, info=inf, session="tight")
seq.append(k - inf["sent"])
check("when the context binds, the start is kept while it fits (a warm prompt cache), not moved every turn",
sum(1 for a, b in zip(seq, seq[1:]) if a != b) <= 2)
try:
u.build_messages("s" * 9000, [], "Q", False, ctx_tokens=2048, count=cnt)
check("a system prompt and question that cannot fit are refused with a reason, not sent", False)
except u.ContextTooSmall:
check("a system prompt and question that cannot fit are refused with a reason, not sent", True)
check("an answer's own <sub> survives; only the chat's clock footer is removed",
u.model_text("H\n\n<sub>2</sub>O\n\n<sub>⏱ sent 12:00</sub>") == "H\n\n<sub>2</sub>O")
check("the window fits the engine's context, moving in whole steps", len(u.build_messages("S", tt, "Q", False, ctx_tokens=60, reserve_tokens=10)) < len(mm))
# search (built-in BM25 here: RAGE_PATH points nowhere)
hits, engine = u.history_search("ternary kernels")
check("history search finds the ternary questions first (built-in engine)", engine.startswith("RAGE-shaped") and {h[1] for h in hits[:3]} == {0, 2, 4})
check("empty query returns nothing", u.history_search(" ")[0] == [])
# metrics from recorded timings
m = u.metrics()
check("metrics: 5 exchanges, median response 24 s, all hashes match", m["exchanges"] == 5 and abs(m["response_s"]["median"] - 24.0) < 1e-9 and m["hash_ok"] == 5)
check("metrics: prefill = prompt tokens / first token", abs(m["prefill_tok_s"]["median"] - 300 / 12.0) < 1e-9)
# .memory
u.memory_add("the operator prefers verdicts", {"kind": "typed"})
u.memory_add("second note", {"kind": "response"})
check(".memory holds 2 notes and a prompt block", len(u.memory_all()) == 2 and "second note" in u.memory_block() and "not evidence" in u.memory_block())
u.memory_remove(1)
check(".memory remove by number", [x["text"] for x in u.memory_all()] == ["second note"])
# custom agents: keccak256, the doctrine root, derive / verify / save, preflight, the template untouched
os.environ["BANKML_AGENTS"] = str(tmp / "agents")
import importlib, agents # noqa: E402
importlib.reload(agents)
check("keccak256('') and ('abc') vectors", agents.keccak256(b"").hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
and agents.keccak256(b"abc").hex() == "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45")
try: # an independent implementation, where installed: every length 0..400 crosses the 136-byte rate twice
from Crypto.Hash import keccak as _k
check("keccak256 == pycryptodome for every length 0..400", all(agents.keccak256((bytes(range(256)) * 2)[:n]) ==
_k.new(digest_bits=256, data=(bytes(range(256)) * 2)[:n]).digest() for n in range(401)))
except ImportError:
print("skip keccak256 vs pycryptodome (not installed)")
real = next((p for p in (Path.home() / "cryptoAGI" / "savante", Path.home() / "savante") if p.exists()), Path.home() / "cryptoAGI" / "savante")
if (real / "savante.persona").is_file():
sp, sl = json.loads((real / "savante.persona").read_bytes()), json.loads((real / "savante.commitments.json").read_bytes())
check("doctrine root reproduces Savante's published ledger value", agents.doctrine_root(sp, sl["doctrine_root"]["pointers"]) == sl["doctrine_root"]["value"])
else:
print("skip doctrine root vs Savante (no ~/cryptoAGI/savante here)")
tpl = {"persona": "tpl", "name": "Template", "source": "s", "format": "f", "system_prompt": "You are the template.", "mantra": "m", "oath": "o",
"bdi": {"beliefs": [{"id": "b", "belief": "x"}]}, "skills": {"primary": "p", "taxonomy": [], "defer_triggers": [], "validation": []},
"safety": {"scope": "s"}, "embodiment": {}, "token": {"intelligence": {"tool_allowlist": ["read"]}, "bindings": {"erc7857": "SECRET"}}}
tpl_bytes = json.dumps(tpl, sort_keys=True).encode()
slug = agents.derive(tpl, {"artifacts": {"identity": {"sha256": "ab"}}, "doctrine_root": {"value": "0x01"}}, "Auditor Ada", system_prompt="You are Ada.", description="an auditor")
f = agents.files(slug)
check("derive writes persona, prompt, card, ledger; it verifies", slug == "auditor_ada" and all(f[k].is_file() for k in ("persona", "prompt", "agentcard.json", "commitments.json"))
and all(ok for _, ok, _ in agents.verify(slug)))
pd = json.loads(f["persona"].read_text())
check("derived identity is new; the template's bindings are not copied", pd["name"] == "Auditor Ada" and pd["token"]["bindings"]["erc7857"] is None
and json.dumps(tpl, sort_keys=True).encode() == tpl_bytes)
card = json.loads(f["agentcard.json"].read_text())
check("agent card: ERC-8004 registration-v1, not_yet_minted, derived_from recorded", card["type"].endswith("#registration-v1") and card["bankml"]["status"] == "not_yet_minted"
and card["bankml"]["derived_from"]["persona_sha256"] == "ab")
r0 = json.loads(f["commitments.json"].read_text())["doctrine_root"]["value"]
led = agents.save(slug, prompt_text="You are Ada, revised.")
check("editing .prompt re-ledgers (prompt hash changes; the doctrine root, over the persona, does not)", led["doctrine_root"]["value"] == r0
and led["artifacts"]["prompt"]["sha256"] == u.sha256(b"You are Ada, revised.\n"))
pd["voice_examples"] = ["I check before I speak."]
led = agents.save(slug, persona_text=json.dumps(pd))
check("editing a field outside the doctrine re-ledgers with the same doctrine root", led["doctrine_root"]["value"] == r0
and led["doctrine_root"]["fixed_at_derivation"] == r0 and all(ok for _, ok, _ in agents.verify(slug)))
try:
agents.save(slug, persona_text=json.dumps(dict(pd, mantra="a new mantra")))
check("a doctrine clause cannot be edited after derivation (the docs' promise, now enforced)", False)
except ValueError as e:
check("a doctrine clause cannot be edited after derivation (the docs' promise, now enforced)", "/mantra" in str(e)
and json.loads(f["persona"].read_text())["mantra"] != "a new mantra")
led_p = f["commitments.json"]
lj = json.loads(led_p.read_text())
lj["doctrine_root"]["pointers"] = ["/name"] # a ledger that narrows its own check
led_p.write_text(json.dumps(lj))
check("verify uses bankml's pointer list, not the one in the ledger it checks", not dict((n, ok) for n, ok, _ in agents.verify(slug))["doctrine root"])
agents.rebind(slug)
f["prompt"].write_text("tampered\n")
check("a hand edit outside save() is caught by verify", not dict((n, ok) for n, ok, _ in agents.verify(slug))["prompt"])
for bad, why in ((dict(pd, weight=0.5), "float"), ({k: v for k, v in pd.items() if k != "oath"}, "missing clause")):
try:
agents.save(slug, persona_text=json.dumps(bad))
check(f"preflight refuses a {why}", False)
except ValueError:
check(f"preflight refuses a {why}", True)
try:
agents.agent_dir("../etc")
check("agent_dir rejects path traversal", False)
except ValueError:
check("agent_dir rejects path traversal", True)
# the chosen aivatar: validated by content and size, ledgered, and part of the THOT bundle
png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 64
led = agents.set_aivatar(slug, png)
check("a PNG aivatar is accepted and ledgered", led["artifacts"]["aivatar"]["sha256"] == u.sha256(png) and all(ok for _, ok, _ in agents.verify(slug)))
for bad, why in ((b"not an image at all", "a non-image"), (b"\x89PNG\r\n\x1a\n" + b"\x00" * (2 * 1024 * 1024), "an image over 2 MB")):
try:
agents.set_aivatar(slug, bad)
check(f"the aivatar refuses {why}", False)
except ValueError:
check(f"the aivatar refuses {why}", True)
# THOT manifests: the spec's test vectors (where those repositories are here), and a bankml bundle's lineage
import subprocess, thot # noqa: E402
VEC = [("savante", real, "1fcca89", "savante.thot.json", "0x235da8e993dc8af2c077f50d698962446b872b17b1e5b033d5c5d976532b8880",
"0xdc1d80957cf831aee6638fd569e22cf0f6e5a1ec99ddde91cfecb5a15408fbe1", "bafkreieerglzkjrkmwrxhmrk53bf3tvhp3aut3qlatwpdtoutmspuxrh4i"),
("jaimla", Path.home() / "cryptoAGI" / "jaimla", "8b57ccf", "jaimla.thot.json", "0x7f5bcc69dc9d50854189762dba0a97b3a48090ee546a0423aa9722917740e086",
"0xd7cb5363324645b4f6d0df19902fe9cd15dcb1c36ae925ee5ff2278315f6a258", "bafkreidczapw72pldcf5pxyoz2hhdleauto7ajvjgun7w2iiq7ccwwhvku"),
("luvai", Path.home() / "cryptoAGI" / "luvai", "0c1eef7", "luvai.thot.json", "0x2feb7ecfcffe91c035951298e5d977d880ec475edb87fff5f3038efc8d44f94c",
"0xf594d1b2ab3f98b20ca203227e71a437ac86d82f6c4d4942227ecf8ee018309b", "bafkreiefvcfiw2rkrcjzblcj5ivrux7mtpsha4fqaulgvteycpr4zbwqtq")]
for name, repo, commit, fn, br, mr, cid in VEC:
g = subprocess.run(["git", "-C", str(repo), "show", f"{commit}:{fn}"], capture_output=True, text=True)
if g.returncode:
print(f"skip THOT test vector {name} (repository not here)")
continue
m = json.loads(g.stdout)
recs = thot.records(m["facets"])
check(f"THOT spec vector {name}: bundle_root, Merkle root and identity CID", thot.bundle_root(recs)[0] == br and thot.merkle_root(recs) == mr
and thot.identity(m)["cid"] == cid)
m1 = thot.build(slug)
check("a bankml THOT bundle verifies, with the aivatar as a facet", thot.verify(slug) == [] and m1["merkle"]["populated"] == 6
and any(x["facet"] == "x-bankml.aivatar" for x in m1["facets"]))
check("the bundle commits history by digest and holds none of its text", "question" not in (agents.agent_dir(slug) / f"{slug}.thot.json").read_text()
and any(x["facet"] == "x-bankml.history" for x in m1["facets"]))
m1b = thot.build(slug)
f["history"].write_text(f["history"].read_text() + '{"user": "new"}\n')
m2 = thot.build(slug)
check("re-bind keeps the generation; a facet change is n+1 with parent = the previous CID",
m1b["bundle"]["generation"] == m1["bundle"]["generation"] and m2["bundle"]["generation"] == m1b["bundle"]["generation"] + 1
and m2["bundle"]["parent"] == m1b["identity"]["cid"] and thot.verify(slug) == [])
# Savante's voice: the pronunciation table, speech from markdown, one render, the view's audio gate
import speak # noqa: E402
t2 = {"format": "voaice-pronunciation/1", "version": 2, "entries": [{"match": "PYTHAIML", "say": "Pith AI M L"}, {"match": "SAVANTE", "say": "Sav ont"},
{"match": "PYTHAI", "say": "Pith AI"}]}
check("pronunciation: one pass, longest match first, case-insensitive", speak.say("savante and PYTHAIML and pythai", t2) == "Sav ont and Pith AI M L and Pith AI")
check("pronunciation is idempotent (a spoken form holds no match)", speak.say(speak.say("Savante", t2), t2) == "Sav ont")
sp = speak.speech("# Title\n\nSee [the docs](https://x.y) and `code`. Second sentence!\n\n```sh\nrm -rf /\n```\n\n| a | b |\n|---|---|\n\n- a **bold** item")
check("speech(): code and tables dropped, links read as text, markers gone", "rm -rf" not in " ".join(sp) and "https" not in " ".join(sp)
and "See the docs and code." in sp and "Second sentence!" in sp and "a bold item" in sp)
check("speech() keeps savante_sagi as written; say() speaks it as words", speak.speech("one seat of core_command, savante_sagi.") == ["one seat of core_command, savante_sagi."]
and speak.say("APPROVE_WITH_CONDITIONS", t2) == "APPROVE WITH CONDITIONS")
ok_v, why_v = speak.available()
if ok_v:
it = speak.render(["I am Savante."])
check("a statement renders to Opus in Savante's voice, cached by key", len(it) == 1 and it[0]["file"].is_file() and it[0]["file"].read_bytes()[:4] == b"OggS"
and speak.cached(["I am Savante."])[0]["key"] == it[0]["key"])
else:
print(f"skip voice render ({why_v})")
check("say(): SCIEN·TIFIC, bankml and a pythai address are said, not spelled", speak.say("SCIEN·TIFIC at scientific.pythai.net runs bankml", t2)
== "Sci-en, Tiffic at scientific dot Pith AI dot net runs bank M L")
check("say(): TECHNICAL.md notation read aloud", speak.say("w ∈ {−1, 0, +1}; 3⁵ = 243 ≤ 256 (§III.5)", t2)
== "w in minus one, zero, or plus one; three to the fifth equals 243 at most 256")
check("say(): names keep their separators; files, formats and references are read properly",
speak.say("Professor / OVERLORD", t2) == "Professor / OVERLORD"
and speak.say("mindX DAIO · savante_sagi", t2) == "mindX DAIO · Sav ont sagi"
and speak.say("see (q1_0.rs, 0.0.4) now", t2) == "see now" and speak.say("bankml.rs", t2) == "bank M L dot R S"
and speak.say("PQ2_0 = 142 and Q4_K_M", t2) == "P Q two zero equals 142 and Q four K M"
and speak.say("Bankml, as in §III.4.", t2) == "bank M L, as in section three point four."
and speak.say("(16 + 2) × 8 / 128", t2) == "(16 plus 2) times 8 over 128")
check("speech(): list markers only at line starts; an inline + survives", speak.speech("- item one\n\nThe cost is (16 + 2) bits.") == ["item one", "The cost is (16 + 2) bits."])
rc = speak.reading_chapters()
check("the reading: thesis, II.1, III.2, III.5 and III.8 from TECHNICAL.md", [t for t, _ in rc][0] == "The thesis" and len(rc) == 5
and rc[-1][0] == "Can a binary computer perform a ternary operation?" and rc[-1][1][0] == "Yes.")
try:
speak.export("t-incomplete", [("c", ["never rendered, never exported"])])
check("an export is refused until every sentence is rendered", False)
except RuntimeError:
check("an export is refused until every sentence is rendered", not (speak.EXPORT_DIR / "t-incomplete.opus").exists())
(speak.EXPORT_DIR).mkdir(parents=True, exist_ok=True)
(speak.EXPORT_DIR / "t-broken.opus").write_bytes(b"OggS")
(speak.EXPORT_DIR / "t-broken.json").write_text('{"sig": "abc', encoding="utf-8")
check("a damaged export record means not current, never an error", speak.export_state("t-broken", [("c", ["x"])])["current"] is False)
if ok_v:
speak.render(["It ends."])
e = speak.export("t-set", [("one", ["I am Savante."]), ("two", ["It ends."])])
pr = subprocess.run(["ffprobe", "-v", "error", "-show_chapters", "-of", "json", str(e["file"])], capture_output=True, text=True)
check("a complete export: one Ogg Opus file, one chapter mark per chapter, current until a clip changes",
e["current"] and e["file"].read_bytes()[:4] == b"OggS" and len(json.loads(pr.stdout or "{}").get("chapters", [])) == 2
and not speak.export_state("t-set", [("one", ["I am Savante."]), ("two", ["It ends.", "A new line."])])["current"])
import view # noqa: E402
check("view's audio route: only a 24-hex key the manifest lists", view.audio_file("../../etc/passwd") is None and view.audio_file("0" * 24) is None
and (not ok_v or view.audio_file(it[0]["key"]) is not None))
# the view server: fixed routes, commitments only, no traversal
from http.server import ThreadingHTTPServer
srv = ThreadingHTTPServer(("127.0.0.1", 0), view.H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
base = f"http://127.0.0.1:{srv.server_address[1]}"
def get(path, method="GET"):
try:
with urllib.request.urlopen(urllib.request.Request(base + path, method=method), timeout=10) as r:
return r.status, r.read()
except urllib.error.HTTPError as e:
return e.code, b""
st, body = get("/api/state")
state = json.loads(body)
check("view /api/state carries the .history commitment", st == 200 and state["private"]["history"]["merkle_root"] == c["merkle_root"])
check("view /api/state carries no .history content", b"question 1" not in body and b"answer 3" not in body)
check("view refuses traversal and unknown paths", get("/api/result?name=../../etc/passwd")[0] == 404 and get("/etc/passwd")[0] == 404)
check("view refuses POST", get("/api/state", "POST")[0] == 405)
st_k, body_k = get("/knobs.js")
check("view serves the DreamKnob bundle at /knobs.js", st_k == 200 and b"SavanteKnobs" in body_k)
check("view's export route: only a named, complete export", get("/export/t-set.opus")[0] == 404 and get("/export/..%2Fmanifest.opus")[0] == 404
and view.export_file("Savante") is None)
srv.shutdown()
print(f"{'all ok' if not fails else f'{fails} FAILED'}")
sys.exit(1 if fails else 0)
|