Files
tai/taicpp/tests/misc_driver.cpp
T

1667 lines
79 KiB
C++

// C++ side of the differential tests (tests/misc_diff.py): prints the same transcript as tests/misc_py.py.
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <map>
#include <sstream>
#include "bundled.h"
#include "bundled_data.h"
#include "audittool.h"
#include "release.h"
#include "chunker.h"
#include "telegram.h"
#include "ragtool.h"
#include "retrievaleval.h"
#include "install.h"
#include "installconsts.h"
#include "ftssearch.h"
#include "miscdatetime.h"
#include "miscjson.h"
#include "miscpy.h"
#include "miscsha256.h"
#include "schedtime.h"
#include "schedtool.h"
#include "schedvault.h"
#include "sqlitedb.h"
#include "scheduler.h"
#include "schedtool.h"
#include "skillfile.h"
#include "skilltool.h"
using detz::misc::Json;
using detz::misc::PyFs;
using detz::misc::PyStr;
namespace sk = detz::skills;
static std::string DATA_DIR;
static Json data(const std::string& name) {
std::ifstream in(DATA_DIR + "/" + name, std::ios::binary);
std::stringstream ss;
ss << in.rdbuf();
return Json::loads(ss.str());
}
static std::string j(const Json& v) { return v.dumps(); }
static std::string js(const std::string& s) { return Json(s).dumps(); }
static Json arr_of(const std::vector<std::string>& v) {
Json a = Json::array();
for (const auto& s : v) a.push(s);
return a;
}
static void out(const std::string& line = "") { std::cout << line << "\n"; }
static std::string tai_home() { return PyFs::env("TAI_HOME"); }
static std::string py_bool(bool b) { return b ? "True" : "False"; }
static void write(const std::string& path, const std::string& text) {
PyFs::makedirs(PyFs::dirname(path));
PyFs::write_bytes(path, text);
}
static std::string read(const std::string& path) { return PyFs::read_bytes(path); }
// ------------------------------------------------------------------ scenarios without state
static void sc_json(const std::string&) {
for (const auto& c : data("json_cases.json").items()) {
try {
Json v = Json::loads(c.as_string());
out(js(c.as_string()) + " => " + v.dumps());
} catch (const detz::misc::JsonError&) {
out(js(c.as_string()) + " => ERR");
}
}
}
static void sc_datetime(const std::string&) {
for (const auto& c : data("datetime_cases.json").items()) {
std::string iso, parsed;
try {
iso = detz::misc::DateTime::fromisoformat(c.as_string()).isoformat();
} catch (const std::exception&) {
iso = "ERR";
}
try {
parsed = detz::sched::ScheduleTime::parse_schedule_at(c.as_string());
} catch (const std::exception& e) {
parsed = std::string("ERR ") + e.what();
}
bool err = parsed.rfind("ERR", 0) == 0;
std::string shown = "-";
if (!err) {
try {
shown = js(detz::sched::ScheduleTime::local_display(parsed));
} catch (const std::exception& e) {
shown = std::string("ERR ") + e.what();
}
}
out(js(c.as_string()) + " => " + iso + " | " + parsed + " | " + shown);
}
for (const auto& c : data("duration_cases.json").items()) {
auto d = detz::sched::ScheduleTime::parse_duration(c.as_string());
out("duration " + js(c.as_string()) + " => " + (d ? std::to_string(*d) : std::string("null")));
}
for (const auto& c : data("delay_cases.json").items()) {
out("delay " + j(c) + " => " + js(detz::sched::ScheduleTime::format_delay(c.as_double())));
}
}
static void sc_chunk(const std::string&) {
for (const auto& c : data("chunk_cases.json").items()) {
std::string text = c.get("text")->as_string();
auto chunks = detz::rag::Chunker::chunk_lines(text, static_cast<int>(c.get("max_lines")->as_int()), static_cast<int>(c.get("overlap")->as_int()),
static_cast<int>(c.get("max_chars")->as_int()));
out("chunks " + std::to_string(chunks.size()) + " " + j(arr_of(chunks)));
out("collapse " + js(detz::rag::Chunker::collapse_repeated_lines(text)));
out("lines " + j(arr_of(PyStr::splitlines(text))));
out("strip " + js(PyStr::strip(text)));
}
}
static void sc_fts(const std::string&) {
using detz::rag::FtsQuery;
for (const auto& c : data("fts_cases.json").items()) {
auto terms = FtsQuery::fts_escape(c.as_string());
out(js(c.as_string()) + " => " + (terms ? j(arr_of(*terms)) : std::string("null")));
if (terms) {
out(" match " + js(FtsQuery::fts_match(*terms)));
out(" or " + js(FtsQuery::fts_match_or(*terms)));
out(" prefix " + js(FtsQuery::fts_match_prefix(*terms)));
out(" variants " + j(arr_of(FtsQuery::fts_match_variants(*terms))));
}
}
out("rank " + js(FtsQuery::fts_rank("fts_docs")));
for (const std::string& raw : std::vector<std::string>{"Release Notes!", " a b ", "---", std::string(40, 'x'), "\xc3\xa9t\xc3\xa9", "a,b;c", "Hello World 2", "..", "UPPER", "a-b-c"}) {
out("tag " + js(raw) + " => " + js(detz::misc::TagNames::normalize_tag(raw)));
}
std::vector<std::string> items = {"A b", "a-b", "", "C!", std::string(40, 'd'), std::string(39, 'd') + "e"};
for (int i = 0; i < 30; ++i) items.push_back("t" + std::to_string(i));
out("tags " + j(arr_of(detz::misc::TagNames::normalize_tags(items))));
}
static void sc_skills_data(const std::string&) {
for (const auto& kv : sk::BundledData::files()) {
out("skill " + kv.first + " upstream " + js(sk::BundledData::upstream().at(kv.first)));
for (const auto& f : kv.second) {
out(" file " + f.first + " len " + std::to_string(f.second.size()) + " sha8 " + sk::BundledSkills::bundled_skill_digest(f.second) +
" sha256 " + detz::misc::Sha256::hex(f.second));
}
}
for (const auto& kv : sk::BundledData::blueprints()) {
out("blueprint " + kv.first + " hint " + js(kv.second.hint) + " scope " + kv.second.scope + " brief " + js(kv.second.brief));
}
out("catalog empty " + js(sk::SkillFiles::skill_catalog(sk::SkillSet())));
sk::SkillSet two;
sk::Skill a;
a.name = "deep-research";
a.description = std::string(400, 'x');
sk::Skill b;
b.name = "zeta";
b.description = "short";
two.put(a);
two.put(b);
out("catalog one " + js(sk::SkillFiles::skill_catalog(two)));
out("prompt " + js(sk::SkillFiles::create_skill_prompt("my-skill", "do the thing", "/tmp/x/my-skill")));
for (const std::string& raw : std::vector<std::string>{"a", "ab", "a-b", "-a", "a-", "A", "a_b", std::string(64, 'a'), std::string(65, 'a'), "", "a b", "0x", "a\n"}) {
out("name " + js(raw) + " => " + py_bool(sk::SkillFiles::skill_name_valid(raw)));
}
std::vector<std::vector<std::string>> front_cases = {
{"name: x", "description: y"},
{"name: 'x'", "description: \"y z\""},
{"name: x", "description: >", " line one", " line two", "other: v"},
{"name: x", "description: |", " line one", " line two", "tail: 1"},
{"name: x", "description: >-", " folded"},
{" indented: no", "key: va:lue", "noval", "k2:", "k3: ''quoted''"},
{"a: >", "b: 1"},
{"a: |", "\tt1", " t2", "z: 9"},
};
for (const auto& lines : front_cases) {
auto front = sk::SkillFiles::parse_skill_front(lines);
// json.dumps of a dict keeps the parse (insertion) order; the C++ map is sorted, so compare as sorted key objects
Json o = Json::object();
for (const auto& kv : front) o.set(kv.first, kv.second);
out("front " + j(arr_of(lines)) + " => " + j(o));
}
}
// ------------------------------------------------------------------ filesystem scenarios
static Json skill_json(const std::optional<sk::Skill>& s) {
if (!s) return Json();
Json o = Json::object();
o.set("name", s->name);
o.set("description", s->description);
o.set("body", s->body);
return o;
}
static void sc_skills_files(const std::string& root) {
std::string home = tai_home();
out("== front-matter parse");
std::vector<std::pair<std::string, std::string>> cases = {
{"ok", "---\nname: ok\ndescription: fine one\n---\n\n\nbody here\n"},
{"crlf", "---\r\nname: crlf\r\ndescription: win\r\n---\r\nbody\r\n"},
{"nofront", "just text\n"},
{"noend", "---\nname: noend\ndescription: d\n"},
{"badname", "---\nname: Bad_Name\ndescription: d\n---\nbody\n"},
{"nodesc", "---\nname: nodesc\n---\nbody\n"},
{"quoted", "---\nname: 'quoted'\ndescription: \"has: colon\"\n---\nbody\n"},
{"folded", "---\nname: folded\ndescription: >\n one\n two\n---\nbody\n"},
{"emptybody", "---\nname: emptybody\ndescription: d\n---"},
};
for (const auto& kv : cases) {
std::string path = root + "/parse/" + kv.first + "/SKILL.md";
write(path, kv.second);
out("parse " + kv.first + " => " + j(skill_json(sk::SkillFiles::parse_skill_file(path))));
}
out("== discover with external roots and collisions");
std::string proj = root + "/proj";
std::string uh = PyFs::home();
write(uh + "/.claude/skills/ext-home/SKILL.md", "---\nname: ext-home\ndescription: from claude home\n---\nbody ext-home\n");
write(uh + "/.claude/skills/shared/SKILL.md", "---\nname: shared\ndescription: external copy\n---\nbody external\n");
write(proj + "/.claude/skills/proj-claude/SKILL.md", "---\nname: proj-claude\ndescription: project claude\n---\nbody pc\n");
write(proj + "/.agents/skills/proj-agents/SKILL.md", "---\nname: proj-agents\ndescription: project agents\n---\nbody pa\n");
write(home + "/skills/shared/SKILL.md", "---\nname: shared\ndescription: tai copy\n---\nbody tai\n");
write(home + "/skills/native/SKILL.md", "---\nname: native\ndescription: native home skill\n---\nbody native\n");
write(home + "/skills/native/scripts/run.sh", "echo hi\n");
write(home + "/skills/native/references/ref.md", "ref\n");
write(home + "/skills/native/assets/a.txt", "a\n");
write(proj + "/.tai/skills/proj-tai/SKILL.md", "---\nname: proj-tai\ndescription: project tai skill\n---\nbody ptai\n");
write(home + "/skills/broken/SKILL.md", "no front matter\n");
write(home + "/skills/stray-file.txt", "x\n");
{
Json meta = Json::object();
meta.set("source", "/some/path");
meta.set("mode", "symlink");
meta.set("scope", "home");
meta.set("n", 3);
Json nested = Json::object();
Json arr = Json::array();
arr.push(1).push(Json()).push(true);
nested.set("a", arr);
meta.set("nested", nested);
sk::SkillFiles::save_skill_install_meta(home + "/skills", "native", meta);
}
auto skills = sk::SkillFiles::discover_skills(home, proj);
for (const auto& s : skills.items()) {
out("skill " + s.name + " root=" + s.root + " managed=" + py_bool(s.managed) + " install=" + (s.install ? j(*s.install) : std::string("null")) +
" note=" + js(sk::SkillFiles::skill_provenance_note(s)) + " extras=" + j(arr_of(sk::SkillFiles::skill_extra_files(s.root))));
out(" desc=" + js(s.description) + " body=" + js(s.body));
}
std::vector<std::string> order;
for (const auto& s : skills.items()) order.push_back(s.name);
out("order " + j(arr_of(order)));
out("catalog " + js(sk::SkillFiles::skill_catalog(skills)));
out("== scope helpers");
for (const std::string scope : {"project", "home"}) {
out("scope_dir " + scope + " " + sk::SkillFiles::skill_scope_dir(scope, home, proj, "nm"));
out("scope_dir " + scope + " " + sk::SkillFiles::skill_scope_dir(scope, home, proj));
}
std::string empty = root + "/emptyproj";
PyFs::makedirs(empty);
out("default scope empty " + sk::SkillFiles::default_skill_scope(empty));
for (const std::string marker : {".git", ".tai", ".claude/skills", ".agents/skills"}) {
std::string tag = PyStr::replace_all(marker, "/", "_");
std::string d = root + "/mk-" + tag;
PyFs::makedirs(d + "/" + marker);
out("default scope " + marker + " " + sk::SkillFiles::default_skill_scope(d));
}
for (const std::string path : {"/a/b/.claude/skills/x", "/a/b/.agents/skills/x/", "/a/b/other/x", "/a/.claude/skills/../x", "/a/.claude/skills//x"}) {
out("external " + path + " " + py_bool(sk::SkillFiles::is_external_skill_root(path)));
}
auto none = sk::SkillFiles::load_skill_install_meta(home + "/skills", "nothing");
out("meta none " + (none ? j(*none) : std::string("null")));
write(home + "/skills/.installs/bad.json", "{not json");
auto bad = sk::SkillFiles::load_skill_install_meta(home + "/skills", "bad");
out("meta bad " + (bad ? j(*bad) : std::string("null")));
auto ok = sk::SkillFiles::load_skill_install_meta(home + "/skills", "native");
out("meta ok " + (ok ? j(*ok) : std::string("null")));
}
static int count_files(const std::string& base) { return static_cast<int>(PyFs::walk_files(base).size()); }
static void sc_skills_install_tool(const std::string& root);
static void sc_skills_ops(const std::string& root) {
std::string home = tai_home();
sk::BundledSkills bundled;
auto step = [&](const std::string& label, const std::string& mode) {
out(label + " => " + bundled.op_skills(mode, home));
out(" status: " + sk::BundledSkills::status_line(home));
out(" files: " + std::to_string(count_files(home + "/skills")));
};
out("-- fresh");
step("install", "install");
step("install again", "install");
step("upgrade clean", "upgrade");
{
std::string s = "[";
bool first = true;
for (const auto& n : sk::BundledSkills::names()) {
s += std::string(first ? "" : ", ") + (sk::BundledSkills::bundled_skill_pristine(home, n) ? "true" : "false");
first = false;
}
out("pristine all " + s + "]");
}
std::string taste = home + "/skills/taste/SKILL.md";
write(taste, "user rewrite");
out("-- modified taste");
step("install", "install");
step("upgrade", "upgrade");
std::string extra = home + "/skills/taste/mynotes.md";
write(extra, "mine");
step("reinstall", "reinstall");
out("extra kept " + py_bool(PyFs::isfile(extra)));
out("-- crlf edit counts as equal after universal newlines");
std::string path = home + "/skills/plan/SKILL.md";
write(path, PyStr::replace_all(read(path), "\n", "\r\n"));
step("upgrade crlf", "upgrade");
out("-- uninstall with user content");
std::string gitp = home + "/skills/git/SKILL.md";
write(gitp, "user rewrite git");
std::string gextra = home + "/skills/git/mynotes.md";
write(gextra, "mine");
write(home + "/skills/git/sub/deep.txt", "deep");
step("uninstall", "uninstall");
step("uninstall again", "uninstall");
PyFs::remove(gitp);
PyFs::remove(gextra);
PyFs::remove(home + "/skills/git/sub/deep.txt");
step("uninstall final", "uninstall");
step("uninstall empty", "uninstall");
out("-- sidecar-only uninstall");
{
Json meta = Json::object();
meta.set("files", Json::object());
sk::SkillFiles::save_skill_install_meta(home + "/skills", "taste", meta);
}
step("uninstall sidecar", "uninstall");
out("-- tracked-digest removal of an edited file");
step("install", "install");
auto meta = sk::SkillFiles::load_skill_install_meta(home + "/skills", "taste");
std::vector<std::string> keys;
for (const auto& m : meta->members()) keys.push_back(m.first);
std::sort(keys.begin(), keys.end());
out("meta " + j(arr_of(keys)));
out("meta files " + j(*meta->get("files")));
{
Json fixed = Json::object();
for (const char* k : {"source", "mode", "scope", "upstream", "bundled_version"}) fixed.set(k, *meta->get(k));
out("meta fixed " + j(fixed));
}
chmod((home + "/skills/taste").c_str(), 0500);
step("install readonly", "install");
chmod((home + "/skills/taste").c_str(), 0700);
out("-- error path: skills dir is a file");
std::string other = root + "/home2";
PyFs::makedirs(other);
write(other + "/skills", "file not dir");
out("blocked => " + bundled.op_skills("install", other));
out("blocked uninstall => " + bundled.op_skills("uninstall", other));
sc_skills_install_tool(root);
}
class SkillApp : public sk::SkillHost {
public:
std::string home_, cwd_;
sk::SkillSet skills_;
int depth_{0};
std::vector<std::string> approvals;
int refreshed{0};
bool approve{true};
std::function<std::string(const std::string&, int)> runner;
bool ask_approval(const std::string& what) override { approvals.push_back(what); return approve; }
std::string home() const override { return home_; }
std::string cwd() const override { return cwd_; }
int depth() const override { return depth_; }
const sk::SkillSet& skills() const override { return skills_; }
void refresh_skills() override { ++refreshed; skills_ = sk::SkillFiles::discover_skills(home_, cwd_); }
void audit_skill_install(const std::string&, const std::string&) override {}
std::string run_worker(const std::string& prompt, int timeout, int) override { return runner(prompt, timeout); }
};
static std::string dispatch(sk::SkillTool& tool, const std::string& name, const Json& args) {
try {
if (name == "install_skill") return tool.run_install_skill(args);
if (name == "load_skill") return tool.run_load_skill(args);
if (name == "create_skill") return tool.run_create_skill(args);
} catch (const std::exception& e) {
return "error: " + PyStr::short_error(e.what());
}
return "error: unknown tool " + name;
}
static Json obj(std::initializer_list<std::pair<const char*, Json>> kv) {
Json o = Json::object();
for (const auto& p : kv) o.set(p.first, p.second);
return o;
}
static std::string replace_root(std::string s, const std::string& root) { return PyStr::replace_all(s, root, "<ROOT>"); }
static void sc_skills_install_tool(const std::string& root) {
std::string home = tai_home();
std::string proj = root + "/proj";
if (chdir(proj.c_str()) != 0) return;
SkillApp app;
app.home_ = home;
app.cwd_ = proj;
app.skills_ = sk::SkillFiles::discover_skills(home, proj);
sk::SkillTool tool(app);
out("== install_skill / load_skill / create_skill");
std::string src = root + "/srcskill";
write(src + "/SKILL.md", "---\nname: from-src\ndescription: installed from a dir\n---\nbody src\n");
write(src + "/scripts/x.sh", "echo x\n");
write(src + "/references/r.md", "r\n");
std::string nested = root + "/nested";
write(nested + "/inner/SKILL.md", "---\nname: inner\ndescription: nested one\n---\nbody inner\n");
std::string two = root + "/two";
write(two + "/a/SKILL.md", "---\nname: aa\ndescription: d\n---\nb\n");
write(two + "/b/SKILL.md", "---\nname: bb\ndescription: d\n---\nb\n");
std::string barefile = root + "/bare.md";
write(barefile, "---\nname: bare-one\ndescription: bare md file\n---\nbody bare\n");
std::string badfile = root + "/bad.md";
write(badfile, "no front\n");
std::string ext = proj + "/.claude/skills/extskill";
write(ext + "/SKILL.md", "---\nname: extskill\ndescription: external one\n---\nbody ext\n");
auto call = [&](const std::string& label, const Json& args) { out(label + " => " + js(dispatch(tool, "install_skill", args))); };
call("empty", obj({{"source", ""}}));
call("bad scope", obj({{"source", src}, {"scope", "mars"}}));
call("bad mode", obj({{"source", src}, {"mode", "link"}}));
call("missing", obj({{"source", root + "/nope"}}));
call("dir home", obj({{"source", src}, {"scope", "home"}}));
call("dir home again denied?", obj({{"source", src}, {"scope", "home"}}));
app.approve = false;
call("dir home denied", obj({{"source", src}, {"scope", "home"}}));
app.approve = true;
call("dir home force", obj({{"source", src}, {"scope", "home"}, {"force", true}}));
call("dir project", obj({{"source", src}, {"scope", "project"}, {"name", "renamed"}}));
call("bad name", obj({{"source", src}, {"name", "Bad Name"}}));
call("nested", obj({{"source", nested}, {"scope", "home"}}));
call("two children", obj({{"source", two}, {"scope", "home"}}));
call("bare file", obj({{"source", barefile}, {"scope", "home"}}));
call("bad file", obj({{"source", badfile}}));
call("symlink ext default", obj({{"source", ext}, {"scope", "home"}}));
call("symlink explicit", obj({{"source", src}, {"scope", "home"}, {"mode", "symlink"}, {"name", "linked"}, {"force", true}}));
call("symlink bare", obj({{"source", barefile}, {"mode", "symlink"}}));
call("not a skill source", obj({{"source", "/dev/null"}}));
out("approvals " + j(arr_of(app.approvals)));
out("refreshed " + std::to_string(app.refreshed));
out("skills " + j(arr_of(app.skills_.sorted_names())));
out("== load_skill");
app.skills_ = sk::SkillFiles::discover_skills(home, proj);
for (const std::string name : {"from-src", "deep-research", "extskill", "nonexistent", ""}) {
out("load " + js(name) + " => " + js(dispatch(tool, "load_skill", obj({{"name", name}}))));
}
out("== create_skill");
app.runner = [](const std::string& prompt, int timeout) { return "worker said: " + PyStr::prefix(prompt, 40) + " t=" + std::to_string(timeout); };
auto create = [&](const Json& args) { out("create " + j(args) + " => " + js(dispatch(tool, "create_skill", args))); };
create(obj({{"name", ""}, {"brief", "b"}}));
create(obj({{"name", "ok-name"}, {"brief", ""}}));
create(obj({{"name", "ok-name"}, {"brief", "b"}, {"scope", "mars"}}));
create(obj({{"name", "made"}, {"brief", "make it"}, {"scope", "home"}, {"timeout", 10}}));
create(obj({{"name", "made2"}, {"brief", "make it"}, {"timeout", "abc"}}));
create(obj({{"name", "made3"}, {"brief", "x"}, {"timeout", 99999}}));
app.depth_ = 5;
out("create depth => " + js(dispatch(tool, "create_skill", obj({{"name", "dd"}, {"brief", "b"}}))));
app.depth_ = 0;
app.runner = [&](const std::string& prompt, int) {
std::string marker = "Create a new agent skill named 'wrote' in ";
std::string path = prompt.substr(marker.size());
path = path.substr(0, path.find(".\n"));
write(path + "/SKILL.md", "---\nname: wrote\ndescription: written by worker\n---\nbody wrote\n");
return replace_root("done writing " + path, root);
};
out("create written => " + js(replace_root(dispatch(tool, "create_skill", obj({{"name", "wrote"}, {"brief", "w"}, {"scope", "home"}})), root)));
out("create replaced => " + js(replace_root(dispatch(tool, "create_skill", obj({{"name", "wrote"}, {"brief", "w"}, {"scope", "home"}})), root)));
out("refreshed " + std::to_string(app.refreshed));
}
// ------------------------------------------------------------------ install scenarios
namespace ins = detz::install;
class FakeBox : public ins::ContainerPort {
public:
std::optional<std::string> engine;
std::string state = "running", venv = "venv ready", ensure_error;
std::optional<std::string> container_engine() override { return engine; }
std::string box_state(const std::string&) override { return state; }
std::string box_venv_state(const std::string&) override { return venv; }
std::string ensure_box() override {
if (!ensure_error.empty()) throw detz::misc::BackendError(ensure_error);
return engine ? *engine : std::string();
}
};
static ins::InstallContext install_ctx() {
ins::InstallContext ctx;
const char* py = std::getenv("MISC_TAI_PY");
ctx.script_path = py ? py : "tai.py";
return ctx;
}
static void sc_install_hooks(const std::string& root) {
FakeBox box;
ins::StdIo io;
ins::Installer inst(install_ctx(), box, io);
std::string home = tai_home();
std::string uh = PyFs::home();
std::string rc = uh + "/.bashrc", zrc = uh + "/.zshrc";
write(rc, "# my bashrc\nexport A=1");
write(zrc, "");
auto status = [&] { return PyStr::join(inst.status_lines(home), "\n"); };
std::vector<std::pair<std::string, std::function<std::string()>>> steps = {
{"bash install", [&] { return inst.op_hook("install", home); }},
{"bash install again", [&] { return inst.op_hook("install", home); }},
{"bash upgrade", [&] { return inst.op_hook("upgrade", home); }},
{"plexer install", [&] { return inst.op_plexer_hook("install", home); }},
{"plexer again", [&] { return inst.op_plexer_hook("install", home); }},
{"zsh install", [&] { return inst.op_zsh_hook("install", home); }},
{"zsh again", [&] { return inst.op_zsh_hook("install", home); }},
{"fish install", [&] { return inst.op_fish_hook("install", home); }},
{"zsh plexer install", [&] { return inst.op_zsh_plexer_hook("install", home); }},
{"fish plexer install", [&] { return inst.op_fish_plexer_hook("install", home); }},
{"status", status},
{"bash reinstall", [&] { return inst.op_hook("reinstall", home); }},
{"plexer reinstall", [&] { return inst.op_plexer_hook("reinstall", home); }},
{"zsh uninstall", [&] { return inst.op_zsh_hook("uninstall", home); }},
{"fish uninstall", [&] { return inst.op_fish_hook("uninstall", home); }},
{"zsh uninstall again", [&] { return inst.op_zsh_hook("uninstall", home); }},
{"bash uninstall", [&] { return inst.op_hook("uninstall", home); }},
{"plexer uninstall", [&] { return inst.op_plexer_hook("uninstall", home); }},
{"zsh plexer uninstall", [&] { return inst.op_zsh_plexer_hook("uninstall", home); }},
{"fish plexer uninstall", [&] { return inst.op_fish_plexer_hook("uninstall", home); }},
{"status after", status},
};
for (const auto& st : steps) out(st.first + " => " + js(st.second()));
out("rc " + js(read(rc)));
out("zrc " + js(read(zrc)));
out("== stale and duplicated blocks");
const std::string& begin = ins::InstallConsts::BASHRC_MARK_BEGIN();
const std::string& end = ins::InstallConsts::BASHRC_MARK_END();
write(rc, "head\n" + begin + "\nstale content\n" + end + "\nmid\n" + begin + "\nsecond stale\n" + end + "\ntail\n");
out("install over stale => " + js(inst.op_hook("install", home)));
out("rc " + js(read(rc)));
write(rc, "x\n" + begin + "\nunterminated\n");
out("install over unterminated => " + js(inst.op_hook("install", home)));
out("rc " + js(read(rc)));
out("uninstall unterminated => " + js(inst.op_hook("uninstall", home)));
out("rc " + js(read(rc)));
write(rc, "crlf\r\nline\r\n");
out("install crlf => " + js(inst.op_hook("install", home)));
out("rc " + js(read(rc)));
chmod(rc.c_str(), 0400);
out("readonly install => " + js(inst.op_plexer_hook("install", home)));
chmod(rc.c_str(), 0600);
auto present = [&](const std::string& path) {
Json a = Json::array();
for (bool b : {ins::Installer::hook_present(path), ins::Installer::zsh_hook_present(path), ins::Installer::fish_hook_present(path),
ins::Installer::plexer_hook_present(path), ins::Installer::zsh_plexer_hook_present(path), ins::Installer::fish_plexer_hook_present(path)})
a.push(b);
return a;
};
out("present " + j(present(rc)));
out("present missing " + j(present(root + "/nope")));
for (const std::string shell : {"bash", "zsh", "fish", "other"}) out("rc path " + shell + " " + ins::Install::shell_rc_path(shell));
setenv("XDG_CONFIG_HOME", (root + "/xdg").c_str(), 1);
out("rc path fish xdg " + ins::Install::shell_rc_path("fish"));
unsetenv("XDG_CONFIG_HOME");
Json sp = Json::array();
for (const char* n : {"zsh", "fish", "bash", "nosuchshell"}) sp.push(ins::Install::shell_present(n));
out("shell_present " + j(sp));
}
class InstallApp : public sk::SkillHost {
public:
std::string home_, cwd_;
sk::SkillSet skills_;
std::vector<std::string> approvals;
bool approve{true};
bool ask_approval(const std::string& what) override { approvals.push_back(what); return approve; }
std::string home() const override { return home_; }
std::string cwd() const override { return cwd_; }
const sk::SkillSet& skills() const override { return skills_; }
void refresh_skills() override { skills_ = sk::SkillFiles::discover_skills(home_, cwd_); }
void audit_skill_install(const std::string&, const std::string&) override {}
std::string run_worker(const std::string&, int, int) override { return ""; }
};
static void set_box(FakeBox& box, std::optional<std::string> engine, const std::string& state = "running", const std::string& venv = "venv ready", const std::string& err = "") {
box.engine = engine;
box.state = state;
box.venv = venv;
box.ensure_error = err;
}
static void sc_install_ops(const std::string& root) {
FakeBox box;
set_box(box, std::string("podman"));
ins::StdIo io;
ins::Installer inst(install_ctx(), box, io);
std::string home = tai_home();
std::string uh = PyFs::home();
out("-- binary");
for (const std::string mode : {"install", "install", "upgrade", "reinstall", "uninstall", "uninstall", "install"}) out("binary " + mode + " => " + js(inst.op_binary(mode, home)));
std::string target = uh + "/.local/bin/tai.py";
{
struct stat st;
stat(target.c_str(), &st);
struct stat st2;
stat(install_ctx().script_path.c_str(), &st2);
char buf[16];
std::snprintf(buf, sizeof buf, "0o%o", static_cast<unsigned>(st.st_mode & 0777));
out(std::string("binary mode ") + buf + " size_equal " + py_bool(st.st_size == st2.st_size));
}
write(target, "VERSION = \"0.0.1\"\n");
out("binary refresh => " + js(inst.op_binary("install", home)));
out("binary refresh upgrade => " + js(inst.op_binary("upgrade", home)));
auto opt_js = [](const std::optional<std::string>& v) { return v ? js(*v) : std::string("null"); };
out("script_version " + opt_js(inst.script_version(target)) + " " + opt_js(inst.script_version(root + "/nope")) + " " + opt_js(inst.script_version(install_ctx().script_path)));
write(root + "/v1", "x\nVERSION = \"1.2.3\"\ny\n");
write(root + "/v2", "VERSION = \"1.2\"\n VERSION = \"1.2.3\"\nVERSION = \"a.b.c\"\n");
write(root + "/v3", "VERSION = \"4.5.6\"");
write(root + "/v4", "VERSION = \"4.5.6\" \n");
{
std::string s = "[";
bool first = true;
for (const char* n : {"v1", "v2", "v3", "v4"}) {
s += std::string(first ? "" : ", ") + (inst.script_version(root + "/" + n) ? js(*inst.script_version(root + "/" + n)) : std::string("null"));
first = false;
}
out("script_version v1..v4 " + s + "]");
}
out("-- venv");
for (const std::string mode : {"install", "install", "reinstall", "uninstall", "uninstall", "install"}) out("venv " + mode + " => " + js(inst.op_venv(mode, home)));
{
auto vp = ins::Install::venv_python(home);
out("venv helpers " + js(ins::Install::venv_dir(home)) + " " + js(vp ? *vp : std::string()) + " " + py_bool(ins::Install::venv_available()));
}
out("-- services (no systemctl)");
for (const std::string mode : {"install", "install", "reinstall", "upgrade", "uninstall", "uninstall"}) out("scheduler " + mode + " => " + js(inst.op_scheduler_service(mode, home)));
out("telegram without token => " + js(inst.op_telegram_service("install", home)));
out("service_exec " + js(inst.service_exec(home, "/x/tai.py", {"--a", "b"})));
out("unit_dir " + js(ins::Systemd::unit_dir(home)));
{
auto st = ins::Systemd::service_state("tai-scheduler.service");
Json a = Json::array();
a.push(st.first).push(st.second);
out("service_state " + j(a));
}
out("activate " + js(ins::Systemd::activate_service("tai-scheduler.service")));
{
auto r = ins::Systemd::remove_service_unit(home, "tai-scheduler.service");
Json a = Json::array();
a.push(r.first).push(r.second);
out("remove " + j(a));
}
out("-- container");
for (const std::string mode : {"install", "upgrade", "reinstall", "uninstall"}) out("container " + mode + " => " + js(inst.op_container(mode, home)));
set_box(box, std::string("podman"), "stopped", "no venv (reinstall container to rebuild)");
for (const std::string mode : {"install", "reinstall"}) out("container stopped " + mode + " => " + js(inst.op_container(mode, home)));
set_box(box, std::string("podman"), "running", "venv ready", "engine exploded");
out("container failing => " + js(inst.op_container("install", home)));
set_box(box, std::nullopt);
for (const std::string mode : {"install", "uninstall"}) out("container noengine " + mode + " => " + js(inst.op_container(mode, home)));
set_box(box, std::string("podman"));
out("-- install_report");
struct Rep { std::string action; std::vector<std::string> targets; };
std::vector<Rep> reps = {{"install", {"skills", "bash-hook", "venv"}}, {"status", {"skills"}}, {"upgrade", ins::InstallConsts::INSTALL_TARGETS()},
{"uninstall", {"skills", "bash-hook", "plexer-hook", "binary"}}};
for (const auto& r : reps) out("report " + r.action + " " + PyStr::replace_all(PyStr::replace_all(j(arr_of(r.targets)), "\"", "'"), ",", ",") + " =>\n" + inst.install_report(r.action, r.targets, home));
out("-- install_self");
for (const std::string shell : {"/bin/bash", "/usr/bin/zsh", "/usr/bin/fish", "", "/bin/sh"}) {
setenv("SHELL", shell.c_str(), 1);
out("shell " + js(shell));
std::cout.flush();
inst.install_self();
}
out("-- install tool");
InstallApp app;
app.home_ = home;
app.cwd_ = root;
auto run_tool = [&](const Json& args) {
return inst.run_install(args, home, nullptr, [&](const std::string& w) { return app.ask_approval(w); }, [&] { app.refresh_skills(); });
};
std::vector<Json> tool_args = {
obj({{"action", "bogus"}}), obj({{"action", "status"}}), obj({{"action", "STATUS "}, {"targets", arr_of({"skills"})}}), obj({{"action", "install"}, {"targets", arr_of({"nope"})}}),
obj({{"action", "install"}, {"targets", Json::array()}}), obj({{"action", "install"}, {"targets", "skills"}}), obj({{"action", "install"}, {"targets", arr_of({"skills"})}}),
obj({{"action", "uninstall"}, {"targets", arr_of({"skills", "bash-hook"})}}), Json::object()};
for (const auto& args : tool_args) {
app.skills_ = sk::SkillSet();
std::string res = run_tool(args);
out("tool " + j(args) + " => " + js(res) + " | skills=" + j(arr_of(app.skills_.sorted_names())));
}
app.approve = false;
out("tool denied => " + js(run_tool(obj({{"action", "install"}, {"targets", arr_of({"skills"})}}))));
out("approvals " + j(arr_of(app.approvals)));
}
static void sc_install_noengine(const std::string&) {
FakeBox box;
set_box(box, std::nullopt);
ins::StdIo io;
ins::Installer inst(install_ctx(), box, io);
std::string home = tai_home();
auto lines = inst.status_lines(home);
Json a = Json::array();
for (std::size_t i = lines.size() - 3; i < lines.size(); ++i) a.push(lines[i]);
out(j(a));
for (const std::string mode : {"install", "uninstall"}) out("container " + mode + " => " + js(inst.op_container(mode, home)));
}
static void sc_vault_line(const std::string&) {
std::string home = tai_home();
out("empty " + js(ins::Installer::vault_line(home)));
write(home + "/memory.db", "");
out("kept " + js(ins::Installer::vault_line(home)));
}
// ------------------------------------------------------------------ scheduler scenario
namespace sc = detz::sched;
using detz::misc::DbValue;
using detz::misc::SqliteDb;
static Json db_json(const DbValue& v) {
if (v.is_null()) return Json();
if (v.is_int()) return Json(v.as_int());
return Json(v.as_text());
}
static Json row_json(const detz::misc::DbRow& r) {
Json a = Json::array();
for (const auto& v : r) a.push(db_json(v));
return a;
}
// Seal with passphrase disabled: lock is the identity, unlock follows Seal.unlock's error paths.
class DisabledSeal : public sc::SealPort {
public:
bool enabled() const override { return false; }
std::string lock(const std::string& plain) const override { return plain; }
std::string unlock(const std::string& sealed) const override {
if (!is_sealed(sealed)) throw sc::SealError("not sealed");
std::size_t d1 = sealed.find('$'), d2 = sealed.find('$', d1 + 1), d3 = sealed.find('$', d2 + 1);
if (d2 == std::string::npos || d3 == std::string::npos) throw sc::SealError("sealed value is corrupt");
throw sc::SealError("seal check failed");
}
std::string safe_unlock(const std::string& sealed) const override {
try { return unlock(sealed); } catch (const sc::SealError&) { return "[sealed: decrypt failed]"; }
}
};
// The tag side of Store as the schedule code uses it (no plural folding needed for the test vocabulary)
class SqlTags : public sc::TagPort {
public:
SqlTags(SqliteDb& db) : db_(db) {}
void tag_item(const std::string& item, const std::vector<std::string>& tags, const std::string& profile) override {
for (const auto& t : detz::misc::TagNames::normalize_tags(tags)) db_.execute("INSERT OR IGNORE INTO tags (item, tag, profile) VALUES (?, ?, ?)", {item, t, profile});
}
void set_item_tags(const std::string& item, const std::vector<std::string>& tags, const std::string& profile) override {
db_.execute("DELETE FROM tags WHERE item = ? AND profile = ?", {item, profile});
tag_item(item, tags, profile);
}
std::vector<std::string> item_tags(const std::string& item, const std::string& profile) override {
std::vector<std::string> out_tags;
for (const auto& r : db_.query("SELECT tag FROM tags WHERE item = ? AND profile = ? ORDER BY tag", {item, profile})) out_tags.push_back(r[0].as_text());
return out_tags;
}
private:
SqliteDb& db_;
};
// Registry with spawn_agent/poll_agent/running_agents semantics of tai.py and the Gate runner of the Python twin.
class FakeAgents : public sc::AgentPort {
public:
struct Rec {
std::string task, status = "running", result;
std::thread th;
};
std::mutex m;
std::map<int, std::unique_ptr<Rec>> recs;
int next = 1;
bool report_zero = false;
std::mutex gm;
std::condition_variable gcv;
bool released = false;
~FakeAgents() override {
release();
for (auto& kv : recs) {
if (kv.second->th.joinable()) kv.second->th.join();
}
}
void release() {
std::lock_guard<std::mutex> lock(gm);
released = true;
gcv.notify_all();
}
int running_agents() override {
if (report_zero) return 0;
std::lock_guard<std::mutex> lock(m);
int n = 0;
for (auto& kv : recs) n += kv.second->status == "running";
return n;
}
int spawn_agent(const std::string& task, const std::string&, int64_t) override {
std::lock_guard<std::mutex> lock(m);
int live = 0;
for (auto& kv : recs) live += kv.second->status == "running";
if (live >= 8) throw sc::AgentsFull("too many live agents (" + std::to_string(live) + " of 8)");
int id = next++;
auto rec = std::make_unique<Rec>();
rec->task = task;
Rec* raw = rec.get();
recs[id] = std::move(rec);
raw->th = std::thread([this, raw, task] {
std::string result, status;
try {
if (task.rfind("slow:", 0) == 0) {
std::unique_lock<std::mutex> lock(gm);
gcv.wait_for(lock, std::chrono::seconds(30), [this] { return released; });
result = "slow done " + task;
} else if (task.find("boom") != std::string::npos) {
throw std::runtime_error("boom");
} else {
result = "ok:" + task;
}
status = result.find("[time limit reached]") != std::string::npos ? "timeout" : "done";
} catch (const std::exception& e) {
result = std::string("worker failed: ") + PyStr::short_error(e.what());
status = "error";
}
std::lock_guard<std::mutex> lock(m);
raw->result = result;
raw->status = status;
});
return id;
}
std::pair<std::string, std::string> poll_agent(int id, double wait) override {
Rec* rec;
{
std::lock_guard<std::mutex> lock(m);
auto it = recs.find(id);
if (it == recs.end()) return {"missing", "no agent " + std::to_string(id)};
rec = it->second.get();
}
auto deadline = std::chrono::steady_clock::now() + std::chrono::duration<double>(wait);
while (std::chrono::steady_clock::now() < deadline) {
{
std::lock_guard<std::mutex> lock(m);
if (rec->status != "running") break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
std::lock_guard<std::mutex> lock(m);
if (rec->status == "running") return {rec->status, "agent " + std::to_string(id) + " still running (0s elapsed)"};
if (rec->status == "error") return {rec->status, rec->result};
std::string text = rec->result;
std::size_t n = PyStr::len(text);
if (n > 6000 + 2000 + 50) text = PyStr::prefix(text, 6000) + "\n...[" + std::to_string(n - 6000 - 2000) + " chars truncated]...\n" + PyStr::suffix(text, 2000);
if (text.empty()) text = "(empty result)";
return {rec->status, text};
}
};
class SchedApp : public sc::ScheduleHost {
public:
std::vector<std::string> approvals;
bool approve = true;
sc::ScheduleVault* vault_ = nullptr;
bool ask_approval(const std::string& what) override { approvals.push_back(what); return approve; }
std::string active_profile() const override { return "t"; }
sc::ScheduleVault& vault() override { return *vault_; }
};
static void sched_rows(SqliteDb& db, const std::string& label) {
out("-- " + label);
for (const auto& r : db.query("SELECT id, name, prompt, profile, every_sec, next_run, timeout, status, last_status, last_result, updated FROM schedules ORDER BY id")) out("row " + j(row_json(r)));
Json tags = Json::array();
for (const auto& r : db.query("SELECT item, tag, profile FROM tags WHERE item LIKE 'sched:%' ORDER BY item, tag")) tags.push(row_json(r));
out("tags " + j(tags));
}
static void sc_sched(const std::string&) {
std::string db_path = tai_home() + "/memory.db";
SqliteDb db(db_path, 30, true);
DisabledSeal seal;
SqlTags tags(db);
FakeAgents agents;
sc::Scheduler scheduler(db_path, seal, agents);
sc::ScheduleVault vault(db, seal, tags, "t");
auto quiesce = [&] { scheduler.join_watchers(); };
auto item_json = [](const sc::ScheduleItem& it) {
Json o = Json::object();
o.set("id", it.id).set("name", it.name).set("prompt", it.prompt).set("profile", it.profile).set("every", it.every).set("next_run", it.next_run).set("timeout", it.timeout);
o.set("status", it.status).set("last", it.last).set("result", it.result).set("created", it.created).set("tags", arr_of(it.tags));
return o;
};
const std::string NOW = "2026-10-25T12:00:00+00:00";
out("== list and format");
auto items = vault.list_schedules();
for (const auto& it : items) out("item " + j(item_json(it)));
out("format:\n" + sc::Scheduler::format_schedule_lines(items, detz::misc::DateTime::fromisoformat(NOW)));
out("format empty: " + sc::Scheduler::format_schedule_lines({}, detz::misc::DateTime::fromisoformat(NOW)));
{
Json a = Json::array();
for (const auto& it : vault.list_schedules("other")) a.push(item_json(it));
out("other profile: " + j(a));
}
out("== running leases");
std::string nowiso = detz::misc::DateTime::now_utc().isoformat();
struct Lease { std::string status, updated; int64_t timeout; };
for (const auto& l : std::vector<Lease>{{"running", nowiso, 600}, {"running", "2000-01-01T00:00:00+00:00", 600}, {"done", "", 0}, {"running", "garbage", 10}, {"running", nowiso, 0}}) {
out("running " + l.status + " " + (l.updated.rfind("2026", 0) == 0 ? std::string("now") : l.updated) + " " + std::to_string(l.timeout) + " => " +
py_bool(sc::Scheduler::schedule_running(l.status, l.updated, l.timeout)));
}
out("== sealed rows (added now: Store refuses to open with them present and no passphrase)");
vault.add_schedule("sealed", "tai1$AAAA$BBBB$CCCC", "t", 0, "2026-05-01T00:00:00+00:00", 300);
vault.add_schedule("sealed-corrupt", "tai1$bad", "t", 0, "2026-05-02T00:00:00+00:00", 300);
out("== tick 1");
std::cout.flush();
int fired = scheduler.scheduler_tick();
out("fired " + std::to_string(fired));
quiesce();
sched_rows(db, "after tick 1");
out("== tick 2");
out("fired " + std::to_string(scheduler.scheduler_tick()));
quiesce();
out("== cap phase");
for (int k = 0; k < 9; ++k) vault.add_schedule("slow" + std::to_string(k), "slow:" + std::to_string(k), "t", 315360000, "2025-01-0" + std::to_string(k + 1) + "T00:00:00+00:00", 600);
out("fired " + std::to_string(scheduler.scheduler_tick()));
out("fired " + std::to_string(scheduler.scheduler_tick()));
out("running agents " + std::to_string(agents.running_agents()));
out("fired " + std::to_string(scheduler.scheduler_tick()));
sched_rows(db, "after cap ticks");
out("== AgentsFull restore branch");
vault.add_schedule("full-once", "slow:once", "t", 0, "2025-02-01T00:00:00+00:00", 600);
agents.report_zero = true;
out("fired " + std::to_string(scheduler.scheduler_tick()));
agents.report_zero = false;
sched_rows(db, "after AgentsFull");
agents.release();
quiesce();
sched_rows(db, "after release");
out("== schedule tool");
SchedApp app;
app.vault_ = &vault;
sc::ScheduleTool tool(app);
auto dispatch_tool = [&](const std::string& name, const Json& args) {
try {
if (name == "schedule") return tool.run_schedule(args);
if (name == "unschedule") return tool.run_unschedule(args);
return tool.run_schedules(args);
} catch (const std::exception& e) {
return "error: " + PyStr::short_error(e.what());
}
};
std::vector<Json> calls = {
obj({{"prompt", ""}}), obj({{"prompt", "p"}}), obj({{"prompt", "p"}, {"at", "2099-01-01T10:00"}, {"every", 120}}), obj({{"prompt", "p"}, {"at", "nonsense"}}),
obj({{"prompt", "p"}, {"every", "abc"}}), obj({{"prompt", "p"}, {"every", 30}}),
obj({{"prompt", "tool one"}, {"at", "2099-01-01T10:00"}, {"name", std::string(100, 'N')}, {"timeout", 5}, {"tags", arr_of({"Alpha", "beta!", "", std::string(40, 'x')})}}),
obj({{"prompt", "tool two"}, {"every", 3600}, {"timeout", "abc"}, {"profile", "t"}}), obj({{"prompt", "tool three"}, {"every", "7200"}, {"timeout", 99999}}),
obj({{"prompt", "p"}, {"every", 60}, {"profile", "other"}}), obj({{"prompt", "p"}, {"every", 60}, {"profile", "bad profile!"}}),
obj({{"prompt", "p"}, {"every", 60}, {"tags", "notalist"}}), obj({{"prompt", "p"}, {"every", 60}, {"tags", [] { Json a = Json::array(); a.push(1).push(2); return a; }()}}),
obj({{"prompt", "tool four"}, {"every", 86400}, {"tags", Json::array()}}), obj({{"prompt", " padded "}, {"at", "2099-02-02T02:02:02Z"}, {"timeout", 0}}),
obj({{"prompt", "p"}, {"at", "9999-12-31T24:00"}}), obj({{"prompt", "p"}, {"at", "0001-01-01T00:00"}}), obj({{"prompt", "p"}, {"every", 60.5}}), obj({{"prompt", "p"}, {"every", true}}),
};
for (const auto& args : calls) out("schedule " + j(args) + " => " + js(dispatch_tool("schedule", args)));
app.approve = false;
out("schedule denied => " + js(dispatch_tool("schedule", obj({{"prompt", "denied"}, {"every", 60}}))));
app.approve = true;
out("schedules => " + js(dispatch_tool("schedules", Json::object())));
for (const auto& args : std::vector<Json>{obj({{"id", 999}}), obj({{"id", "abc"}}), obj({{"id", 0}}), Json::object(), obj({{"id", "13"}}), obj({{"id", 14.0}})}) {
out("unschedule " + j(args) + " => " + js(dispatch_tool("unschedule", args)));
}
app.approve = false;
out("unschedule denied => " + js(dispatch_tool("unschedule", obj({{"id", 15}}))));
app.approve = true;
out("approvals " + j(arr_of(app.approvals)));
sched_rows(db, "final");
out("remove nonexistent " + py_bool(vault.remove_schedule(99999)));
}
// ------------------------------------------------------------------ rag scenario
namespace rg = detz::rag;
static Json hit_json(const rg::FtsHit& h) {
Json o = Json::object();
o.set("item", db_json(h.item)).set("kind", h.kind).set("sub", h.sub).set("title", h.title).set("snippet", h.snippet).set("rank", h.rank);
return o;
}
static Json ids_json(const std::vector<DbValue>& v) {
Json a = Json::array();
for (const auto& x : v) a.push(db_json(x));
return a;
}
static Json mean_json(const rg::EvalMean& m) {
Json o = Json::object();
o.set("hit", m.hit).set("mrr", m.mrr).set("ndcg", m.ndcg).set("cp", m.cp);
return o;
}
static Json result_json(const rg::EvalResult& r) {
Json o = Json::object();
o.set("k", r.k).set("goldens", r.goldens).set("overall", mean_json(r.overall));
Json cats = Json::object();
for (const auto& c : r.by_category) {
Json e = Json::object();
e.set("goldens", c.second.goldens).set("mean", mean_json(c.second.mean));
cats.set(c.first, e);
}
o.set("by_category", cats);
Json misses = Json::array();
for (const auto& m : r.misses) {
Json e = Json::object();
e.set("query", m.query).set("category", m.category);
misses.push(e);
}
o.set("misses", misses);
return o;
}
static Json pairs_json(const rg::ImplicitPairs& p) {
Json o = Json::object();
o.set("searches", p.searches).set("read_follow", p.read_follow);
Json pairs = Json::array();
for (const auto& x : p.pairs) {
Json e = Json::object();
e.set("query", x.query).set("read", x.read).set("rank", x.rank ? Json(*x.rank) : Json()).set("shown", x.shown);
pairs.push(e);
}
o.set("pairs", pairs).set("zero_hit", arr_of(p.zero_hit)).set("zero_hit_count", p.zero_hit_count);
return o;
}
class RagApp : public rg::RagHost {
public:
RagApp(SqliteDb& db, rg::FtsSearch& fts, rg::ChunkStore& chunks) : db_(db), fts_(fts), chunks_(chunks) {}
std::string active_profile() const override { return "t"; }
bool sealed() const override { return false; }
std::vector<rg::FtsHit> fts_search(const std::string& q, const std::vector<std::string>& kinds, const std::string& profile, int limit) override { return fts_.fts_search(q, kinds, profile, limit); }
std::vector<rg::EventRow> events_like(const std::string&, const std::string&, int) override { return {}; }
void log_event(const std::string& profile, const std::string& role, const std::string& kind, const std::string& text) override {
std::string value = PyStr::prefix(text, 2000);
db_.execute("INSERT INTO events (profile, ts, role, kind, text, tags) VALUES (?, ?, ?, ?, ?, ?)", {profile, detz::sched::ScheduleTime::now_iso(), role, kind, value, " " + role + " " + kind + " "});
}
std::map<int64_t, rg::ChunkRow> get_chunks_by_ids(const std::vector<int64_t>& ids, const std::string& profile) override { return chunks_.get_chunks_by_ids(ids, profile); }
std::vector<rg::GraphEdge> edges_for(const std::string& item, const std::string& profile) override {
std::vector<rg::GraphEdge> out_edges;
for (const auto& r : db_.query("SELECT src, dst, relation FROM edges WHERE profile = ? AND (src = ? OR dst = ?)", {profile, item, item})) {
out_edges.push_back({r[0].as_text() == item ? r[1].as_text() : r[0].as_text(), r[2].as_text()});
}
return out_edges;
}
std::string node_title(const std::string& node, const std::string& profile) override {
std::size_t colon = node.find(':');
if (colon == std::string::npos) return node;
std::string kind = node.substr(0, colon), key = node.substr(colon + 1);
if (kind == "mem") {
auto rows = db_.query("SELECT title FROM records WHERE profile = ? AND id = ?", {profile, node});
if (rows.empty()) return node;
std::string t = rows[0][0].as_text();
return t.empty() ? node : t;
}
if (kind == "secret") return "secret:" + key;
auto rows = db_.query("SELECT name, prompt FROM schedules WHERE profile = ? AND id = ?", {profile, std::stoll(key)});
if (rows.empty()) return node;
std::string name = rows[0][0].as_text();
return name.empty() ? "schedule #" + key : name;
}
private:
SqliteDb& db_;
rg::FtsSearch& fts_;
rg::ChunkStore& chunks_;
};
static void sc_rag(const std::string&) {
SqliteDb db(tai_home() + "/memory.db", 30, true);
rg::FtsSearch fts(db, "t");
rg::ChunkStore chunks(db, "t");
out("== fts_search");
std::vector<std::string> queries = {"alpha", "alpha bravo", "zebra", "caf\xc3\xa9", "r\xc3\xa9sum\xc3\xa9 na\xc3\xafve", "snake_case", "numbers123", "nothingmatchesthis", "", "!!!", "retrieval index", "wh",
"ALPHA", std::string(50, 'x'), "unique zebra jumps", "message", "file", "ro", "v2beta kernel"};
std::vector<std::vector<std::string>> kind_sets = {{}, {"record"}, {"chunk"}, {"audit"}, {"event"}, {"record", "chunk"}};
for (const auto& q : queries) {
for (const auto& kinds : kind_sets) {
for (int limit : {10, 3}) {
Json a = Json::array();
for (const auto& h : fts.fts_search(q, kinds, "t", limit)) a.push(hit_json(h));
out("search " + js(q) + " " + j(arr_of(kinds)) + " " + std::to_string(limit) + " => " + j(a));
}
}
}
{
Json a = Json::array();
for (const auto& h : fts.fts_search("alpha", {"record"}, "other", 10)) a.push(hit_json(h));
out("other profile " + j(a));
Json l = Json::array();
for (int n : {0, -5, 1, 100, 500}) l.push(static_cast<int64_t>(fts.fts_search("alpha", {}, "t", n).size()));
out("limits " + j(l));
}
out("== eval");
out("tokens " + j(arr_of(rg::RetrievalEval::tokens("The quick Brown fox jumped_over 123456 abcdefg ABCDEFG caf\xc3\xa9s r\xc3\xa9sum\xc3\xa9s"))));
out("tokens4 " + j(arr_of(rg::RetrievalEval::tokens("The quick Brown fox", 4))));
auto goldens = rg::RetrievalEval::goldens_from_vault(db, "t");
out("goldens " + std::to_string(goldens.size()));
for (const auto& g : goldens) {
Json o = Json::object();
o.set("query", g.query).set("expected", ids_json(g.expected)).set("category", g.category).set("source", db_json(g.source));
out("golden " + j(o));
}
auto result = rg::RetrievalEval::score(fts, goldens, "t");
out("score " + j(result_json(result)));
auto pairs = rg::RetrievalEval::implicit_pairs(db, "t", false);
out("pairs " + j(pairs_json(pairs)));
out("report:\n" + rg::RetrievalEval::format_report(result, pairs));
out("empty report:\n" + rg::RetrievalEval::format_report(rg::RetrievalEval::score(fts, {}, "t"), rg::RetrievalEval::implicit_pairs(db, "nobody", false)));
out("small k " + j(result_json(rg::RetrievalEval::score(fts, std::vector<rg::Golden>(goldens.begin(), goldens.begin() + 12), "t", {"record"}, 1, 2))));
{
auto ro = [](std::optional<int> r) { return r ? Json(*r) : Json(); };
out("rank_of " + j(ro(rg::RetrievalEval::rank_of(DbValue("b"), {DbValue("a"), DbValue("b")}))) + " " + j(ro(rg::RetrievalEval::rank_of(DbValue("z"), {DbValue("a")}))) + " " +
j(ro(rg::RetrievalEval::best_rank({DbValue("z"), DbValue("a")}, {DbValue("q"), DbValue("a"), DbValue("z")}))));
Json m = Json::array();
m.push(rg::RetrievalEval::hit_at_k(3)).push(rg::RetrievalEval::hit_at_k(6)).push(rg::RetrievalEval::hit_at_k(std::nullopt)).push(rg::RetrievalEval::reciprocal_rank(4));
m.push(rg::RetrievalEval::ndcg_at_k(3)).push(rg::RetrievalEval::ndcg_at_k(9));
m.push(rg::RetrievalEval::context_precision({DbValue("a"), DbValue("c")}, {DbValue("a"), DbValue("b"), DbValue("c"), DbValue("d")}));
m.push(rg::RetrievalEval::context_precision({}, {DbValue("a")})).push(rg::RetrievalEval::context_precision({DbValue("x")}, {DbValue("a")}));
out("metrics " + j(m));
}
for (const std::string& text : std::vector<std::string>{"q=alpha\thits=record:m1,m2,,chunk:4", "nothing", "q=\thits=a", "a=b=c\tq= spaced \thits= x ,y ", "q=a\tq=b\thits=record:z"}) {
auto parsed = rg::RetrievalEval::parse_search_event(text);
Json v;
if (parsed) {
v = Json::array();
v.push(parsed->first).push(arr_of(parsed->second));
}
out("parse " + js(text) + " => " + j(v));
}
out("== chunks table");
std::string rid = db.query("SELECT id FROM records WHERE profile = 't' ORDER BY id LIMIT 1")[0][0].as_text();
chunks.add_chunks("record", rid, {"one", "two", "three"}, "t");
Json rows_j = Json::array();
std::vector<int64_t> ids;
for (const auto& r : db.query("SELECT id, parent_kind, parent_id, idx, text, profile FROM chunks WHERE parent_id = ? ORDER BY idx", {rid})) {
rows_j.push(row_json(r));
ids.push_back(r[0].as_int());
}
out("chunks " + j(rows_j));
auto chunk_obj = [](const std::map<int64_t, rg::ChunkRow>& m) {
Json o = Json::object();
for (const auto& kv : m) {
Json c = Json::object();
c.set("parent_kind", kv.second.parent_kind).set("parent_id", kv.second.parent_id).set("idx", kv.second.idx).set("text", kv.second.text);
o.set(std::to_string(kv.first), c);
}
return o;
};
std::vector<int64_t> ask = ids;
ask.push_back(999999);
out("by ids " + j(chunk_obj(chunks.get_chunks_by_ids(ask, "t"))));
out("by ids empty " + j(chunk_obj(chunks.get_chunks_by_ids({}, "t"))) + " other " + j(chunk_obj(chunks.get_chunks_by_ids(ids, "other"))));
chunks.add_chunks("record", rid, {}, "t");
out("after clear " + std::to_string(db.query("SELECT COUNT(*) FROM chunks WHERE parent_id = ?", {rid})[0][0].as_int()));
out("== search tool");
RagApp host(db, fts, chunks);
rg::RagTool tool(host);
std::vector<Json> calls = {
obj({{"query", ""}}), obj({{"query", "alpha"}}), obj({{"query", "alpha"}, {"limit", 3}}), obj({{"query", "alpha"}, {"limit", "2"}}), obj({{"query", "alpha"}, {"limit", "x"}}),
obj({{"query", "alpha"}, {"limit", 0}}), obj({{"query", "alpha"}, {"limit", -2}}), obj({{"query", "retrieval"}, {"kinds", arr_of({"chunk"})}}),
obj({{"query", "alpha"}, {"kinds", arr_of({"record"})}, {"expand", true}, {"limit", 20}}), obj({{"query", "alpha"}, {"kinds", arr_of({"bogus"})}}), obj({{"query", "alpha"}, {"kinds", Json::array()}}),
obj({{"query", "alpha"}, {"kinds", "record"}}), obj({{"query", "file"}, {"kinds", arr_of({"audit"})}}), obj({{"query", "message"}, {"kinds", arr_of({"event"})}}),
obj({{"query", "zebra"}, {"kinds", arr_of({"record", "chunk", "audit"})}}), obj({{"query", "nothingmatchesthis"}}), obj({{"query", std::string(200, 'x')}}), obj({{"query", " spaced "}}),
obj({{"query", "caf\xc3\xa9 r\xc3\xa9sum\xc3\xa9"}, {"limit", 50}}),
};
for (const auto& args : calls) {
std::string res;
try {
res = tool.run_search(args);
} catch (const std::exception& e) {
res = "error: " + PyStr::short_error(e.what());
}
out("search " + j(args) + " => " + js(res));
}
Json logged = Json::array();
for (const auto& r : db.query("SELECT profile, role, kind, text FROM events WHERE kind = 'search-query' ORDER BY id DESC LIMIT 25")) logged.push(row_json(r));
out("logged " + j(logged));
}
// ------------------------------------------------------------------ telegram scenarios
namespace tg = detz::telegram;
class LogRec : public detz::misc::LogSink {
public:
void event(const std::string& level, const std::string& component, const std::string& event, const std::string& message, const std::string& profile,
const std::string& bot, const std::string& chat, const Json& meta) override {
Json a = Json::array();
a.push(level).push(component).push(event).push(message).push(profile).push(bot).push(chat).push(meta);
out("LOG " + j(a));
}
void set_context(const std::string& key, const std::string& value) override { out("CTX set " + key + "=" + value); }
void clear_context(const std::string& key) override { out("CTX pop " + key); }
};
class TgAgent : public tg::TelegramAgent {
public:
std::string home_;
std::string profile() const override { return "t"; }
std::string bot() const override { return "main"; }
std::string home() const override { return home_; }
void reset_history() override { out("AGENT reset_history"); }
std::string run_turn(const std::string& text) override {
out("AGENT run_turn " + js(text) + " capture=True");
if (text.find("explode") != std::string::npos) throw std::runtime_error("agent exploded");
if (text == "long") return std::string(9000, 'L');
if (text == "empty reply") return "";
return "echo:" + text;
}
void start_scheduler() override { out("start_scheduler called"); }
};
class TgBox : public tg::BoxPort {
public:
std::string ensure_box() override {
out("BOX ensure_box");
if (std::getenv("MISC_BOX_FAIL")) throw detz::misc::BackendError("box down");
return "eng";
}
std::string box_transcribe(const std::string& engine, const std::string& audio) override {
// Python prints the audio decoded as latin-1: each byte becomes one code point
std::string latin;
for (unsigned char c : audio) {
if (c < 0x80) latin += static_cast<char>(c);
else { latin += static_cast<char>(0xC0 | (c >> 6)); latin += static_cast<char>(0x80 | (c & 0x3F)); }
}
out("BOX transcribe " + engine + " " + js(latin));
if (audio == "AUDIO") return "voice says hi";
return "error: model failed";
}
};
struct StopLoop {};
static std::string tg_api() { return std::getenv("MISC_TG_API"); }
static std::string tg_file() { return std::getenv("MISC_TG_FILE"); }
static void sc_telegram(const std::string& root) {
LogRec log;
TgAgent agent;
TgBox box;
std::string home = tai_home();
agent.home_ = home;
tg::TelegramBot bot(agent, nullptr, &box, log);
bot.set_api(tg_api(), tg_file());
out("== token");
std::string env_path = tg::TelegramBot::telegram_env_path(home);
out("env path " + js(env_path));
unsetenv("TELEGRAM_BOT_TOKEN");
out("no file => " + js(tg::TelegramBot::load_telegram_token(home)));
std::vector<std::pair<std::string, std::string>> bodies = {
{"plain", "TELEGRAM_BOT_TOKEN=abc123\n"}, {"quoted", "TELEGRAM_BOT_TOKEN='abc'\n"}, {"dquoted", "TELEGRAM_BOT_TOKEN=\"x y\"\n"}, {"spaces", "TELEGRAM_BOT_TOKEN= spaced \n"},
{"other first", "OTHER=1\nTELEGRAM_BOT_TOKEN=second\nTELEGRAM_BOT_TOKEN=third\n"}, {"none", "OTHER=1\n"}, {"empty", ""}, {"crlf", "TELEGRAM_BOT_TOKEN=win\r\n"},
{"noeq", "TELEGRAM_BOT_TOKEN\n"}, {"eq in value", "TELEGRAM_BOT_TOKEN=a=b=c\n"}, {"quotes only", "TELEGRAM_BOT_TOKEN=''\n"}, {"indented", " TELEGRAM_BOT_TOKEN=nope\n"}};
for (const auto& b : bodies) {
write(env_path, b.second);
out("file " + b.first + " => " + js(tg::TelegramBot::load_telegram_token(home)));
}
setenv("TELEGRAM_BOT_TOKEN", "fromenv", 1);
out("env wins => " + js(tg::TelegramBot::load_telegram_token(home)));
setenv("TELEGRAM_BOT_TOKEN", "", 1);
out("empty env falls back => " + js(tg::TelegramBot::load_telegram_token(home)));
unsetenv("TELEGRAM_BOT_TOKEN");
PyFs::remove(env_path);
out("== call / send");
tg::Telegram good("good", nullptr, tg_api(), tg_file());
out("getMe " + j(good.call("getMe", Json::object())));
for (const std::pair<std::string, std::string>& c : std::vector<std::pair<std::string, std::string>>{{"bad token", "bad"}, {"not json", "nojson"}}) {
try {
tg::Telegram t(c.second, nullptr, tg_api(), tg_file());
out(c.first + " => " + j(t.call("getMe", Json::object())));
} catch (const std::exception& e) {
out(c.first + " => raised " + js(PyStr::short_error(e.what())));
}
}
std::string e1(4001, 'x');
std::string em;
for (int i = 0; i < 4001; ++i) em += "\xc3\xa9";
std::string smile;
for (int i = 0; i < 4100; ++i) smile += "\xf0\x9f\x98\x80";
std::string lines;
for (int i = 0; i < 2000; ++i) lines += "line\n";
for (const std::string& text : std::vector<std::string>{"", "short", std::string(4000, 'S'), std::string(4001, 'S'), std::string(8000, 'S'), std::string(8001, 'S'), em, smile, lines}) {
tg::TelegramBot::telegram_send(good, Json(42), text);
}
tg::TelegramBot::telegram_send(good, Json("chat-as-string"), "hi");
out("== updates");
auto chat = [](const Json& id) { return obj({{"id", id}}); };
auto msg = [&](const Json& id, std::initializer_list<std::pair<const char*, Json>> extra) {
Json m = Json::object();
m.set("chat", chat(id));
for (const auto& p : extra) m.set(p.first, p.second);
return m;
};
std::vector<Json> updates;
auto upd = [&](int id, const Json& message) { updates.push_back(obj({{"update_id", id}, {"message", message}})); };
upd(1, msg(111, {{"text", "/start"}}));
upd(2, msg(111, {{"text", "/new"}}));
upd(3, msg(111, {{"text", "hello there"}}));
upd(4, msg(111, {{"text", ""}}));
upd(5, msg(111, {{"text", " "}}));
upd(6, msg(0, {{"text", "zero chat"}}));
upd(7, obj({{"text", "no chat"}}));
updates.push_back(obj({{"update_id", 8}}));
upd(9, msg(111, {}));
upd(10, msg(111, {{"text", "explode please"}}));
upd(11, msg(111, {{"text", "long"}}));
upd(12, msg(111, {{"text", "empty reply"}}));
upd(13, msg(222, {{"voice", obj({{"file_id", "f1"}})}}));
upd(14, msg(222, {{"audio", obj({{"file_id", "f1"}})}}));
upd(15, msg(222, {{"voice", obj({{"file_id", "nofile"}})}}));
upd(16, msg(222, {{"voice", obj({{"file_id", "fbad"}})}}));
upd(17, msg(222, {{"voice", obj({{"file_id", "fmodel"}})}}));
upd(18, msg(222, {{"voice", Json::object()}, {"text", "text with empty voice"}}));
upd(19, msg(222, {{"voice", obj({{"file_id", "fmissingfile"}})}}));
upd(20, msg(Json("str-chat"), {{"text", "string chat id"}}));
upd(21, msg(111, {{"text", "unicode caf\xc3\xa9 \xf0\x9f\x98\x80"}}));
for (const auto& update : updates) {
out("-- update " + j(update));
try {
bot.handle_telegram_update(good, update);
} catch (const std::exception& e) {
out("raised " + js(PyStr::short_error(e.what())));
}
}
setenv("MISC_BOX_FAIL", "1", 1);
out("-- box failing");
try {
bot.handle_telegram_update(good, obj({{"update_id", 30}, {"message", msg(5, {{"voice", obj({{"file_id", "f1"}})}})}}));
} catch (const std::exception& e) {
out("raised " + js(PyStr::short_error(e.what())));
}
unsetenv("MISC_BOX_FAIL");
out("== run_telegram_bot");
struct Run { const char* label; const char* token; };
for (const Run& r : {Run{"missing", nullptr}, Run{"bad auth", "bad"}, Run{"not json", "nojson"}, Run{"no username", "noname"}, Run{"good", "good"}}) {
unsetenv("TELEGRAM_BOT_TOKEN");
if (r.token) setenv("TELEGRAM_BOT_TOKEN", r.token, 1);
out(std::string("-- run ") + r.label);
std::cout.flush();
try {
int res = bot.run_telegram_bot(nullptr, [](int seconds) {
out("sleep " + std::to_string(seconds));
throw StopLoop();
});
out("returned " + std::to_string(res));
} catch (const StopLoop&) {
out("stopped by sleep");
}
std::cout.flush();
}
(void)root;
}
static void sc_telegram_install(const std::string& root) {
LogRec log;
TgAgent agent;
TgBox box;
FakeBox fbox;
set_box(fbox, std::string("podman"));
ins::StdIo io;
ins::InstallContext ctx = install_ctx();
ctx.telegram_api = tg_api();
ins::Installer inst(ctx, fbox, io);
std::string home = tai_home();
struct T { const char* label; const char* token; };
for (const T& t : {T{"good", "good"}, T{"bad", "bad"}, T{"nojson", "nojson"}, T{"noname", "noname"}}) {
setenv("TELEGRAM_BOT_TOKEN", t.token, 1);
out(std::string("-- install_telegram ") + t.label);
std::cout.flush();
int res = inst.install_telegram();
out("returned " + std::to_string(res));
out("unit exists " + py_bool(PyFs::exists(PyFs::home() + "/.config/systemd/user/tai-telegram.service")) + " env exists " +
py_bool(PyFs::exists(tg::TelegramBot::telegram_env_path(home))));
}
unsetenv("TELEGRAM_BOT_TOKEN");
out("-- install_telegram without token (stdin closed)");
std::cout.flush();
out("returned " + std::to_string(inst.install_telegram()));
out("-- install_telegram box failing");
set_box(fbox, std::string("podman"), "running", "venv ready", "no engine here");
setenv("TELEGRAM_BOT_TOKEN", "good", 1);
out("returned " + std::to_string(inst.install_telegram()));
set_box(fbox, std::string("podman"));
out("-- op_telegram_service");
for (const char* token : {"good", "bad", "nojson", "noname"}) {
setenv("TELEGRAM_BOT_TOKEN", token, 1);
for (const std::string mode : {"install", "install", "reinstall", "upgrade", "uninstall", "uninstall"}) {
out(std::string("service ") + token + " " + mode + " => " + js(inst.op_telegram_service(mode, home)));
}
}
unsetenv("TELEGRAM_BOT_TOKEN");
write(tg::TelegramBot::telegram_env_path(home), "TELEGRAM_BOT_TOKEN=good\n");
out("service from env file => " + js(inst.op_telegram_service("install", home)));
out("-- uninstall_telegram");
out("returned " + std::to_string(inst.uninstall_telegram()));
set_box(fbox, std::nullopt);
out("returned noengine " + std::to_string(inst.uninstall_telegram()));
(void)root;
}
// ------------------------------------------------------------------ release / audit scenarios
using detz::install::SelfRelease;
class NoRecords : public ins::FileRecordPort {
public:
void upsert_file_record(const std::string&, const std::string&, const std::optional<int64_t>&) override {} // records table: store module
};
class RelHost : public ins::ReleaseHost {
public:
std::vector<std::string> approvals;
bool approve = true;
std::string script, home_;
ins::AuditVault* vault_ = nullptr;
bool ask_approval(const std::string& w) override { approvals.push_back(w); return approve; }
std::string home() const override { return home_; }
std::string audit_actor() const override { return "main"; }
ins::AuditPort& store() override { return *vault_; }
std::string script_file() const override { return script; }
};
static std::string copy_file_bytes(const std::string& from, const std::string& to) {
write(to, PyFs::read_bytes(from));
return to;
}
static void sc_release(const std::string& root) {
SqliteDb db(tai_home() + "/memory.db", 30, true);
ins::AuditVault vault(db, "t");
NoRecords records;
std::string script = copy_file_bytes(install_ctx().script_path, root + "/script.py");
std::string folder = root + "/backups";
out("== versions");
struct V { const char* cur; const char* part; };
for (const V& v : {V{"1.2.3", "patch"}, V{"1.2.3", "minor"}, V{"1.2.3", "major"}, V{"9.9.9", "patch"}, V{"1.2.3", "bogus"}, V{"1.2", "patch"}, V{"a.b.c", "patch"}, V{"1.2.3.4", "patch"}}) {
try {
out(std::string("next ") + v.cur + " " + v.part + " => " + SelfRelease::next_version(v.cur, v.part));
} catch (const std::exception& e) {
out(std::string("next ") + v.cur + " " + v.part + " => raised " + js(e.what()));
}
}
out("== bump");
for (const char* part : {"patch", "minor", "major", "patch"}) {
auto r = SelfRelease::release_bump(script, part);
Json a = Json::array();
a.push(r.first).push(r.second);
out(std::string("bump ") + part + " => " + j(a));
}
{
std::string text = PyFs::read_text(script);
std::vector<std::string> vl;
for (const auto& l : PyStr::splitlines(text)) {
if (l.rfind("VERSION = ", 0) == 0) vl.push_back(l);
}
out("version line " + j(arr_of(vl)));
}
write(root + "/nov.py", "print('x')\n");
for (const std::string& f : {root + "/nov.py", root + "/missing.py"}) {
try {
SelfRelease::release_bump(f, "patch");
} catch (const std::exception& e) {
out("bump error " + js(replace_root(e.what(), root)));
}
}
write(root + "/crlf.py", "a\r\nVERSION = \"1.0.0\"\r\nb\r\n");
{
auto r = SelfRelease::release_bump(root + "/crlf.py", "patch");
Json a = Json::array();
a.push(r.first).push(r.second);
out("crlf bump " + j(a) + " " + js(PyFs::read_bytes(root + "/crlf.py")));
}
out("== snapshots and pruning");
auto snap = SelfRelease::snapshot_self(script, folder, "9.9.9");
out("snapshot " + js(PyFs::basename(snap.first)) + " " + snap.second);
out("has digest " + py_bool(SelfRelease::backup_has_digest(folder, snap.second)) + " " + py_bool(SelfRelease::backup_has_digest(folder, "deadbeef")));
out("has digest nofolder " + py_bool(SelfRelease::backup_has_digest(root + "/nofolder", snap.second)));
for (int i = 0; i < 14; ++i) {
char name[128];
std::snprintf(name, sizeof name, "/tai-0.0.%d-20200101T000000Z-%08d.py", i, i);
write(folder + name, "x");
struct timespec ts[2] = {{1000 + i, 0}, {1000 + i, 0}};
utimensat(AT_FDCWD, (folder + name).c_str(), ts, 0);
}
write(folder + "/notes.txt", "keep");
out("pruned " + std::to_string(SelfRelease::prune_backups(folder)));
out("left " + PyStr::replace_all(j(arr_of(PyFs::listdir_sorted(folder))), PyFs::basename(snap.first), "<SNAP>"));
out("prune missing " + std::to_string(SelfRelease::prune_backups(root + "/nofolder")));
out("== ensure_self_backup");
PyFs::rmtree(folder);
auto first = SelfRelease::ensure_self_backup(tai_home(), &vault, &records, script, "1.44.2");
out("ensure first " + (first ? js(PyFs::basename(*first)) : std::string("null")));
auto second = SelfRelease::ensure_self_backup(tai_home(), &vault, &records, script, "1.44.2");
out("ensure second " + (second ? js(PyFs::basename(*second)) : std::string("null")));
{
Json rows = Json::array();
for (const auto& r : db.query("SELECT actor, action, path, message, old_size, new_size, tags FROM audit ORDER BY id")) rows.push(row_json(r));
out("audit " + j(rows));
}
out("== do_release");
try {
SelfRelease::do_release(vault, script, folder, "patch", " ");
} catch (const std::exception& e) {
out("blank message " + js(e.what()));
}
{
auto r = SelfRelease::do_release(vault, script, folder, "minor", " ship it ", "tester");
Json a = Json::array();
a.push(r.old_version).push(r.new_version).push(PyFs::basename(r.dest)).push(r.row_id);
out("do_release " + j(a));
}
out("== release tool");
RelHost host;
host.script = script;
host.home_ = tai_home();
host.vault_ = &vault;
ins::ReleaseTool tool(host);
auto rel = [&](const Json& args) {
try {
return tool.run_release(args);
} catch (const std::exception& e) {
return "error: " + PyStr::short_error(e.what());
}
};
for (const Json& args : std::vector<Json>{obj({{"part", ""}, {"message", "m"}}), obj({{"part", "patch"}, {"message", ""}}), obj({{"part", "huge"}, {"message", "m"}}),
obj({{"part", "Patch"}, {"message", "tool release"}}), obj({{"part", "major"}, {"message", std::string(300, 'm')}})}) {
out("release " + j(args) + " => " + js(replace_root(rel(args), root)));
}
host.approve = false;
out("release denied => " + js(rel(obj({{"part", "patch"}, {"message", "no"}}))));
host.approve = true;
write(script, "nothing here\n");
out("release no version => " + js(rel(obj({{"part", "patch"}, {"message", "m"}}))));
out("approvals " + j(arr_of(host.approvals)));
out("== capped helpers");
{
auto c0 = SelfRelease::capped_text(std::nullopt);
auto c1 = SelfRelease::capped_text(std::string("abc"));
std::string e;
for (int i = 0; i < 20001; ++i) e += "\xc3\xa9";
auto c2 = SelfRelease::capped_text(e);
auto c3 = SelfRelease::capped_text(std::string(20001, 'x'));
auto pair_json = [](const std::pair<std::optional<std::string>, std::optional<int64_t>>& p) {
Json a = Json::array();
a.push(p.first ? Json(*p.first) : Json()).push(p.second ? Json(*p.second) : Json());
return a;
};
Json all = Json::array();
all.push(pair_json(c0)).push(pair_json(c1)).push(Json(*c2.second));
all.push(Json(PyStr::suffix(*c3.first, 60))).push(SelfRelease::trunc_marker(5));
out("capped " + j(all));
}
std::string big = "line\r\nsecond\rthird\n";
for (int i = 0; i < 30000; ++i) big += "\xc3\xa9";
write(root + "/big.txt", big);
for (int cap : {20000, 50, 100000}) {
auto r = SelfRelease::read_capped(root + "/big.txt", static_cast<std::size_t>(cap));
out("read_capped " + std::to_string(cap) + " => " + (r.text ? js(PyStr::prefix(*r.text, 40)) : std::string("null")) + " " + (r.size ? std::to_string(*r.size) : std::string("null")));
}
write(root + "/bad.bin", std::string("ok \xff\xfe bad \xc3 end"));
{
auto r = SelfRelease::read_capped(root + "/bad.bin");
Json a = Json::array();
a.push(r.text ? Json(*r.text) : Json()).push(r.size ? Json(*r.size) : Json());
out("read_capped invalid " + j(a));
}
{
auto r = SelfRelease::read_capped(root + "/missing");
Json a = Json::array();
a.push(Json()).push(Json());
out("read_capped missing " + j(a));
(void)r;
}
}
class AuditApp : public ins::AuditHost {
public:
std::vector<std::string> approvals;
bool approve = true;
ins::AuditVault* vault_ = nullptr;
rg::FtsSearch* fts_ = nullptr;
bool ask_approval(const std::string& w) override { approvals.push_back(w); return approve; }
std::string active_profile() const override { return "t"; }
ins::AuditVault& vault() override { return *vault_; }
rg::FtsSearch& fts() override { return *fts_; }
void read_files_add(const std::string&) override {}
void audit_file(const std::string& action, const std::string& path, const std::optional<std::string>& o, const std::optional<std::string>& n, const std::string& message) override {
vault_->audit_event_full("main", action, path, message, o, n, {"file", action}, std::nullopt, std::nullopt, "t");
}
void upsert_file_record(const std::string&, const std::string&, const std::string&) override {} // records table: store module
void note_diff(const std::string&, const std::optional<std::string>&, const std::optional<std::string>&) override {}
std::string box_restore(const std::string&, const std::string&, const std::string&) override { return "box restore unsupported in this test"; }
};
static void sc_audit(const std::string& root) {
SqliteDb db(tai_home() + "/memory.db", 30, true);
ins::AuditVault vault(db, "t");
rg::FtsSearch fts(db, "t");
AuditApp app;
app.vault_ = &vault;
app.fts_ = &fts;
ins::AuditTool tool(app);
auto call = [&](const std::string& name, const Json& args) {
std::string res;
try {
res = name == "audit" ? tool.run_audit(args) : name == "tags" ? tool.run_tags(args) : tool.run_restore(args);
} catch (const std::exception& e) {
res = "error: " + PyStr::short_error(e.what());
}
out(name + " " + j(args) + " => " + js(replace_root(res, root)));
};
out("== audit");
for (const Json& a : std::vector<Json>{Json::object(), obj({{"limit", 3}}), obj({{"path", "area/a.txt"}}), obj({{"tag", "file"}}), obj({{"tag", "nomatch"}}), obj({{"query", "alpha"}}),
obj({{"query", "write"}}), obj({{"query", "zzzz"}}), obj({{"limit", "x"}}), obj({{"limit", 500}}), obj({{"query", "unicode"}})})
call("audit", a);
out("== tags");
for (const Json& a : std::vector<Json>{Json::object(), obj({{"prefix", "al"}}), obj({{"prefix", "A_"}}), obj({{"prefix", "a%"}}), obj({{"prefix", "zzz"}}), obj({{"limit", 1}}),
obj({{"limit", "x"}}), obj({{"prefix", " Al "}})})
call("tags", a);
out("== restore");
for (const Json& a : std::vector<Json>{Json::object(), obj({{"id", "x"}}), obj({{"id", 9999}}), obj({{"id", 6}}), obj({{"id", 7}}), obj({{"id", 1}}), obj({{"id", 3}}), obj({{"id", 4}}),
obj({{"id", 5}}), obj({{"id", 12}}), obj({{"id", 10}}), obj({{"id", 11}})})
call("restore", a);
for (const std::string rel : {"a.txt", "sub/deep/r.txt", "u.txt"}) {
std::string path = "area/" + rel;
out("file " + rel + " " + (PyFs::exists(path) ? js(read(path)) : std::string("null")));
}
app.approve = false;
call("restore", obj({{"id", 2}}));
app.approve = true;
Json ap = Json::array();
for (const auto& a : app.approvals) ap.push(replace_root(a, root));
out("approvals " + j(ap));
}
int main(int argc, char** argv) {
if (argc < 3) return 2;
std::string name = argv[1], root = argv[2];
bool prepare = argc > 3 && std::string(argv[3]) == "prepare";
const char* data_dir = std::getenv("MISC_DATA_DIR");
DATA_DIR = data_dir ? data_dir : "tests/misc_data";
if (chdir((root + "/proj").c_str()) != 0) return 2;
if (prepare) return 0; // C++ never prepares: the Python side builds fixtures with the real tai.py
static const std::map<std::string, std::function<void(const std::string&)>> scenarios = {
{"json", sc_json}, {"datetime", sc_datetime}, {"chunk", sc_chunk}, {"fts", sc_fts},
{"skills_data", sc_skills_data}, {"skills_files", sc_skills_files}, {"skills_ops", sc_skills_ops},
{"install_hooks", sc_install_hooks}, {"install_hooks_noshell", sc_install_hooks}, {"install_ops", sc_install_ops},
{"install_noengine", sc_install_noengine}, {"vault_line", sc_vault_line}, {"sched", sc_sched}, {"rag", sc_rag}, {"release", sc_release}, {"audit", sc_audit}, {"telegram", sc_telegram}, {"telegram_install", sc_telegram_install},
};
auto it = scenarios.find(name);
if (it == scenarios.end()) {
std::cerr << "unknown scenario " << name << "\n";
return 2;
}
it->second(root);
return 0;
}