Bots, unified FTS5 search, and sealed memory-only event index (v1.14.0)

Per-profile bots (system plus history, shared vault): create_bot with
nicknames, /bots and /bot with resume, @mention one-turn routing filed
in both histories.

Unified retrieval: one trigger-synced FTS5 index over records, events,
and audit with safe tokenized MATCH, porter stemming, BM25 rank, and
marked snippets. New search tool with kind filters and graph expansion;
recall, record_search, and audit rank through the same index.

Sealed search without weakening the seal: each boot decrypts events
into a :memory: FTS5 index (newest 10000, incrementally synced, never
on disk), so sealed stores rank like plaintext ones. LIKE fallback kept
for partial tokens. Documented in README under Sealed search with the
researched alternatives (SQLCipher, blind indexes, SSE) and rejections.

Also in this batch: sysinfo, create_skill, markdown renderer with
colors, scheduler with subagent rule, --yolo/--auto, denial-loop fix,
read-before-write guard, WAL plus timeouts, records/graph/spillover,
audit time travel, self-backup and releases, lazy skill blueprints,
smart tagging, profile isolation, lazy tool payload, install
subsystem, and multi-profile rotation fix. 176 + 16 tests green.
This commit is contained in:
2026-10-07 05:41:44 +02:00
parent b1a22cd2dd
commit 99d7cb21b1
5 changed files with 5946 additions and 133 deletions
+46
View File
@@ -152,5 +152,51 @@ class SealTests(unittest.TestCase):
store.close()
def test_rotate_moves_secrets(self):
os.environ.pop("TAI_PASSPHRASE", None)
config = tai.Config(FakeArgs())
old = tai.Seal(config.home, tai.DEFAULT_PASSPHRASE)
store = tai.Store(config, old)
store.save_secret("api", "rotate-me-1")
store.close()
fresh = tai.rotate_seal(config, old, "personal-3")
reopened = tai.Store(config, tai.Seal(config.home, "personal-3"))
self.assertEqual(reopened.load_secret("api"), "rotate-me-1")
raw = reopened.db.execute("SELECT value FROM secrets WHERE name = 'api'").fetchone()[0]
self.assertTrue(raw.startswith("tai1$"))
reopened.close()
def test_sealed_secrets_require_passphrase(self):
sealed = self.make_store()
sealed.save_secret("k", "locked-value")
sealed.close()
config = tai.Config(FakeArgs())
with self.assertRaises(tai.SealError):
tai.Store(config, tai.Seal(config.home, ""))
def test_schedule_prompt_sealed(self):
store = self.make_store()
store.add_schedule("job", "prompt-secret-3", "t", 0, "2026-01-01T00:00:00+00:00", 60)
raw = store.db.execute("SELECT prompt FROM schedules").fetchone()[0]
self.assertTrue(raw.startswith("tai1$"))
self.assertNotIn("prompt-secret", raw)
self.assertEqual(store.list_schedules("t")[0]["prompt"], "prompt-secret-3")
store.close()
def test_rotate_moves_schedules(self):
os.environ.pop("TAI_PASSPHRASE", None)
config = tai.Config(FakeArgs())
old = tai.Seal(config.home, tai.DEFAULT_PASSPHRASE)
store = tai.Store(config, old)
store.add_schedule("job", "rotate-prompt-4", "t", 3600, "2026-01-01T00:00:00+00:00", 60)
store.close()
fresh = tai.rotate_seal(config, old, "personal-4")
reopened = tai.Store(config, tai.Seal(config.home, "personal-4"))
self.assertEqual(reopened.list_schedules("t")[0]["prompt"], "rotate-prompt-4")
raw = reopened.db.execute("SELECT prompt FROM schedules").fetchone()[0]
self.assertTrue(raw.startswith("tai1$"))
reopened.close()
if __name__ == "__main__":
unittest.main()