<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Theteia — the Discord ocbot</title>
		<link>https://theteia.lexbytes.org/</link>
		<description>Recent content on Theteia — the Discord ocbot</description>
		<generator>Hugo</generator>
		<language>en-US</language>
		
		
		
		
			<lastBuildDate>Fri, 30 Oct 2026 03:35:24 +0000</lastBuildDate>
		
			<atom:link href="https://theteia.lexbytes.org/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Getting started</title>
				<link>https://theteia.lexbytes.org/docs/latest/getting-started/</link>
				<pubDate>Thu, 15 Oct 2026 21:30:54 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/getting-started/</guid>
				<description>&lt;h1 id=&#34;getting-started&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#getting-started&#34; aria-label=&#34;Link to Getting started&#34;&gt;#&lt;/a&gt;&#xA;  Getting started&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; a new operator or contributor who has never run ocbot.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; get from a clean machine to a verified running bot.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Linux is the supported host (the production units are systemd); the bot is built for a &lt;strong&gt;single operator&lt;/strong&gt; — you need a chat surface to talk on (Discord, Signal, or both) and a host you control.&lt;/p&gt;&#xA;&lt;h2 id=&#34;prerequisites&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#prerequisites&#34; aria-label=&#34;Link to Prerequisites&#34;&gt;#&lt;/a&gt;&#xA;  Prerequisites&#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;table-wrap&#34;&gt;&#xA;  &lt;table&gt;&#xA;    &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;th&gt;Requirement&lt;/th&gt;&#xA;        &lt;th&gt;Version&lt;/th&gt;&#xA;        &lt;th&gt;How you get it&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/thead&gt;&#xA;    &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;rustup + Rust&lt;/td&gt;&#xA;        &lt;td&gt;1.98.1, pinned via &lt;code&gt;rust-toolchain.toml&lt;/code&gt;&lt;/td&gt;&#xA;        &lt;td&gt;&lt;a href=&#34;https://rustup.rs&#34; class=&#34;docs-external-link&#34; rel=&#34;noreferrer noopener&#34;&gt;rustup&lt;span class=&#34;docs-external-link__icon&#34; aria-hidden=&#34;true&#34;&gt;↗&lt;/span&gt;&lt;/a&gt;&#xA;; the pinned toolchain is fetched automatically on first build&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;opencode server&lt;/td&gt;&#xA;        &lt;td&gt;v2, current pin 2.0.24&lt;/td&gt;&#xA;        &lt;td&gt;install opencode; see &lt;a href=&#34;https://github.com/lexbity/theteia-discord-ocbot/blob/main/crates/opencode-client/COMPAT.md&#34; class=&#34;docs-external-link&#34; rel=&#34;noreferrer noopener&#34;&gt;client COMPAT&lt;span class=&#34;docs-external-link__icon&#34; aria-hidden=&#34;true&#34;&gt;↗&lt;/span&gt;&lt;/a&gt;&#xA; for the version policy&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;git&lt;/td&gt;&#xA;        &lt;td&gt;any recent&lt;/td&gt;&#xA;        &lt;td&gt;your package manager; &lt;code&gt;git --version&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Discord account&lt;/td&gt;&#xA;        &lt;td&gt;—&lt;/td&gt;&#xA;        &lt;td&gt;yours, plus a bot application (step 1) — only if &lt;code&gt;channel&lt;/code&gt; selects &lt;code&gt;discord&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;A phone number&lt;/td&gt;&#xA;        &lt;td&gt;—&lt;/td&gt;&#xA;        &lt;td&gt;for &lt;code&gt;channel = &amp;quot;signal&amp;quot;&lt;/code&gt;: the bot&amp;rsquo;s own number (registered or linked on the daemon host) plus yours as the allowlisted sender — see &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/deployment/#the-signal-surface&#34;&gt;DEPLOYMENT&lt;/a&gt;&#xA;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/tbody&gt;&#xA;  &lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;step-1--create-the-discord-bot&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#step-1--create-the-discord-bot&#34; aria-label=&#34;Link to Step 1 — Create the Discord bot&#34;&gt;#&lt;/a&gt;&#xA;  Step 1 — Create the Discord bot&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create an application in the &lt;a href=&#34;https://discord.com/developers/applications&#34; class=&#34;docs-external-link&#34; rel=&#34;noreferrer noopener&#34;&gt;Discord developer portal&lt;span class=&#34;docs-external-link__icon&#34; aria-hidden=&#34;true&#34;&gt;↗&lt;/span&gt;&lt;/a&gt;&#xA;.&lt;/li&gt;&#xA;&lt;li&gt;Open the &lt;strong&gt;Bot&lt;/strong&gt; tab, reset the token, and copy it — this is &lt;code&gt;DISCORD_TOKEN&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Enable the &lt;strong&gt;Message Content&lt;/strong&gt; privileged intent.&#xA;The bot needs it to map replies to inbox reports back to sessions.&lt;/li&gt;&#xA;&lt;li&gt;Under &lt;strong&gt;OAuth2 → URL generator&lt;/strong&gt;, select the scopes &lt;code&gt;bot&lt;/code&gt; and &lt;code&gt;applications.commands&lt;/code&gt;, and at minimum the permissions Send Messages, Attach Files, and Add Reactions.&#xA;Open the generated URL to invite the bot to a private server you own.&lt;/li&gt;&#xA;&lt;li&gt;In Discord, enable Developer Mode (user settings → Appearance), right-click your own name, and Copy User ID — this is &lt;code&gt;ALLOWED_USER_ID&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;step-2--start-the-opencode-server&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#step-2--start-the-opencode-server&#34; aria-label=&#34;Link to Step 2 — Start the opencode server&#34;&gt;#&lt;/a&gt;&#xA;  Step 2 — Start the opencode server&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;ocbot expects the server on loopback with a password.&#xA;The minimal manual start:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Architecture</title>
				<link>https://theteia.lexbytes.org/docs/latest/architecture/</link>
				<pubDate>Thu, 15 Oct 2026 21:24:30 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/architecture/</guid>
				<description>&lt;h1 id=&#34;architecture&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#architecture&#34; aria-label=&#34;Link to Architecture&#34;&gt;#&lt;/a&gt;&#xA;  Architecture&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; contributors who want to change ocbot and reviewers who want to judge it.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; show how the system is built, where state lives, and why the load-bearing decisions were made.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; &lt;a href=&#34;https://github.com/lexbity/theteia-discord-ocbot/blob/main/README.md#what-is-ocbot&#34; class=&#34;docs-external-link&#34; rel=&#34;noreferrer noopener&#34;&gt;What is ocbot?&lt;span class=&#34;docs-external-link__icon&#34; aria-hidden=&#34;true&#34;&gt;↗&lt;/span&gt;&lt;/a&gt;&#xA; and the &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/#glossary&#34;&gt;glossary&lt;/a&gt;&#xA;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;system-overview&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#system-overview&#34; aria-label=&#34;Link to System overview&#34;&gt;#&lt;/a&gt;&#xA;  System overview&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;ocbot runs as one process on the same host as the opencode server.&#xA;It talks to exactly five things: two chat surfaces (Discord and Signal), the opencode server, git, and SQLite.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Deployment</title>
				<link>https://theteia.lexbytes.org/docs/latest/deployment/</link>
				<pubDate>Thu, 15 Oct 2026 21:26:46 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/deployment/</guid>
				<description>&lt;h1 id=&#34;deployment&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#deployment&#34; aria-label=&#34;Link to Deployment&#34;&gt;#&lt;/a&gt;&#xA;  Deployment&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; the operator installing ocbot on a host.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; take a built binary to a running, self-healing, backed-up production deployment.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/getting-started/&#34;&gt;Getting started&lt;/a&gt;&#xA; (the concepts: config, secrets, the chat surfaces); a Linux host with systemd and root.&lt;/p&gt;&#xA;&lt;h2 id=&#34;environments&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#environments&#34; aria-label=&#34;Link to Environments&#34;&gt;#&lt;/a&gt;&#xA;  Environments&#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;table-wrap&#34;&gt;&#xA;  &lt;table&gt;&#xA;    &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;th&gt;&lt;/th&gt;&#xA;        &lt;th&gt;Development&lt;/th&gt;&#xA;        &lt;th&gt;Production&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/thead&gt;&#xA;    &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Runs as&lt;/td&gt;&#xA;        &lt;td&gt;your user&lt;/td&gt;&#xA;        &lt;td&gt;dedicated &lt;code&gt;ocbot&lt;/code&gt; / &lt;code&gt;opencode&lt;/code&gt; users&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Command&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo run --release&lt;/code&gt; from the repo root&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;/usr/local/bin/ocbot&lt;/code&gt; via systemd&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Config&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;config/config.toml&lt;/code&gt; in the repo&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;/srv/ocbot/config/config.toml&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Secrets&lt;/td&gt;&#xA;        &lt;td&gt;shell environment&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;/etc/ocbot/ocbot.env&lt;/code&gt;, mode &lt;code&gt;0600&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Restarts&lt;/td&gt;&#xA;        &lt;td&gt;manual&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;Restart=on-failure&lt;/code&gt; + SIGTERM drain&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Backups&lt;/td&gt;&#xA;        &lt;td&gt;none expected&lt;/td&gt;&#xA;        &lt;td&gt;nightly timer, 14-day retention&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;Chat surfaces&lt;/td&gt;&#xA;        &lt;td&gt;whatever &lt;code&gt;channel&lt;/code&gt; selects&lt;/td&gt;&#xA;        &lt;td&gt;the same, plus &lt;code&gt;signal-cli.service&lt;/code&gt; when &lt;code&gt;signal&lt;/code&gt; is selected&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/tbody&gt;&#xA;  &lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;deployment-diagram&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#deployment-diagram&#34; aria-label=&#34;Link to Deployment diagram&#34;&gt;#&lt;/a&gt;&#xA;  Deployment diagram&#xA;&lt;/h2&gt;&#xA;&lt;figure class=&#34;docs-codeblock docs-codeblock--mermaid&#34;&gt;&#xA;  &lt;div&#xA;    class=&#34;docs-codeblock__body&#34;&#xA;    data-codeblock&#xA;    data-copy-label=&#34;Copy code&#34;&#xA;    data-copied-label=&#34;Copied&#34;&#xA;    data-copy-failed-label=&#34;Copy failed&#34;&#xA;  &gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph systemd[&amp;#34;systemd&amp;#34;]&#xA;        u1[&amp;#34;opencode.service&amp;lt;br/&amp;gt;user: opencode, 1500M cap&amp;#34;]&#xA;        u4[&amp;#34;signal-cli.service&amp;lt;br/&amp;gt;user: ocbot, 512M cap&amp;lt;br/&amp;gt;socket /run/signal-cli/socket&amp;#34;]&#xA;        u2[&amp;#34;ocbot.service&amp;lt;br/&amp;gt;user: ocbot, 200M cap&amp;#34;]&#xA;        u3[&amp;#34;ocbot-backup.timer + .service&amp;lt;br/&amp;gt;nightly 03:30&amp;#34;]&#xA;    end&#xA;    u1 -- &amp;#34;serve 127.0.0.1:8080&amp;#34; --&amp;gt; oc[&amp;#34;opencode v2&amp;#34;]&#xA;    u2 -- &amp;#34;REST + SSE (loopback)&amp;#34; --&amp;gt; oc&#xA;    u2 -- &amp;#34;JSON-RPC (unix socket)&amp;#34; --&amp;gt; u4&#xA;    u4 -- &amp;#34;E2E&amp;#34; --&amp;gt; phone[&amp;#34;operator phone&amp;#34;]&#xA;    u2 --&amp;gt; repos[&amp;#34;operator git repositories&amp;#34;]&#xA;    u2 --&amp;gt; db[(&amp;#34;/srv/ocbot/ocbot.db&amp;lt;br/&amp;gt;WAL&amp;#34;)]&#xA;    u3 -- &amp;#34;VACUUM INTO nightly&amp;#34; --&amp;gt; db&#xA;    u3 -- &amp;#34;identity keys nightly&amp;#34; --&amp;gt; u4&#xA;    u3 --&amp;gt; bk[(&amp;#34;/srv/ocbot/backups&amp;lt;br/&amp;gt;14-day retention&amp;#34;)]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/figure&gt;&lt;p&gt;&lt;code&gt;ocbot.service&lt;/code&gt; wants &lt;code&gt;opencode.service&lt;/code&gt;, but the order is a soft preference: if the server is not up yet, the bot starts in degraded mode and recovers on its own.&#xA;&lt;code&gt;signal-cli.service&lt;/code&gt; is optional: install it only when &lt;code&gt;channel&lt;/code&gt; selects &lt;code&gt;signal&lt;/code&gt;. &lt;code&gt;ocbot.service&lt;/code&gt; orders itself after it when it exists, but does not require it — a dead daemon refuses the Signal surface and nothing else (D-10).&lt;/p&gt;</description>
			</item>
			<item>
				<title>Operations</title>
				<link>https://theteia.lexbytes.org/docs/latest/operations/</link>
				<pubDate>Thu, 15 Oct 2026 21:29:33 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/operations/</guid>
				<description>&lt;h1 id=&#34;operations&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#operations&#34; aria-label=&#34;Link to Operations&#34;&gt;#&lt;/a&gt;&#xA;  Operations&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; the operator running a deployed bot.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; know what to watch, what each signal means, and what to do when something breaks.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; a running deployment (&lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/deployment/&#34;&gt;DEPLOYMENT&lt;/a&gt;&#xA;); the &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/#glossary&#34;&gt;glossary&lt;/a&gt;&#xA;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-three-health-surfaces&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#the-three-health-surfaces&#34; aria-label=&#34;Link to The three health surfaces&#34;&gt;#&lt;/a&gt;&#xA;  The three health surfaces&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;The chat surfaces&lt;/strong&gt; — &lt;code&gt;/status&lt;/code&gt; in Discourse server reachability and version, active actions, held locks, and pending gates. Announcements (server down, stream down) arrive as posts, once each.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The metrics endpoint&lt;/strong&gt; — &lt;code&gt;curl http://127.0.0.1:9464/metrics&lt;/code&gt; (loopback only, gate G18).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The logs&lt;/strong&gt; — one JSON line per event via &lt;code&gt;journalctl -u ocbot -o cat&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;metrics-reference&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#metrics-reference&#34; aria-label=&#34;Link to Metrics reference&#34;&gt;#&lt;/a&gt;&#xA;  Metrics reference&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Every family exposed on &lt;code&gt;127.0.0.1:9464&lt;/code&gt; (names are load-bearing — see &lt;code&gt;crates/ocbot/src/app/metrics.rs&lt;/code&gt;):&lt;/p&gt;</description>
			</item>
			<item>
				<title>Backup &amp; restore runbook (NFR-12 / P15)</title>
				<link>https://theteia.lexbytes.org/docs/latest/restore/</link>
				<pubDate>Thu, 15 Oct 2026 21:28:03 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/restore/</guid>
				<description>&lt;h1 id=&#34;backup--restore-runbook-nfr-12--p15&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#backup--restore-runbook-nfr-12--p15&#34; aria-label=&#34;Link to Backup &amp;amp;amp; restore runbook (NFR-12 / P15)&#34;&gt;#&lt;/a&gt;&#xA;  Backup &amp;amp; restore runbook (NFR-12 / P15)&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Git is the source of truth for accepted work; the SQLite file is the operator&amp;rsquo;s&#xA;diary (labels, prompts, session mappings, decisions). Losing it loses &lt;strong&gt;no&#xA;accepted work&lt;/strong&gt; (P2) — but restoring it is cheap, so do it.&lt;/p&gt;&#xA;&lt;p&gt;The nightly backup also archives the &lt;strong&gt;signal-cli identity keys&lt;/strong&gt;, because&#xA;those are the one thing no git history can rebuild: losing them means&#xA;re-linking the number and re-verifying the safety number.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Testing</title>
				<link>https://theteia.lexbytes.org/docs/latest/testing/</link>
				<pubDate>Fri, 30 Oct 2026 03:35:24 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/testing/</guid>
				<description>&lt;h1 id=&#34;testing&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#testing&#34; aria-label=&#34;Link to Testing&#34;&gt;#&lt;/a&gt;&#xA;  Testing&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; contributors writing or running tests.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; know which suite proves what, how to run it, and what a change must add.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; a building workspace (&lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/contributing/#dev-setup&#34;&gt;CONTRIBUTING&lt;/a&gt;&#xA;); test crates are named there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-testing-estate&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#the-testing-estate&#34; aria-label=&#34;Link to The testing estate&#34;&gt;#&lt;/a&gt;&#xA;  The testing estate&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;ocbot&amp;rsquo;s tests are layered by how much of the world is real.&#xA;Lower layers are fast and run on every PR; the top layers need a live server or a nightly runner.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Contributing</title>
				<link>https://theteia.lexbytes.org/docs/latest/contributing/</link>
				<pubDate>Tue, 13 Oct 2026 04:27:31 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/contributing/</guid>
				<description>&lt;h1 id=&#34;contributing&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#contributing&#34; aria-label=&#34;Link to Contributing&#34;&gt;#&lt;/a&gt;&#xA;  Contributing&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; anyone who wants to change ocbot&amp;rsquo;s code or docs.&#xA;&lt;strong&gt;Purpose:&lt;/strong&gt; set up a dev environment, meet the quality floor, and get a PR merged.&#xA;&lt;strong&gt;Prerequisites:&lt;/strong&gt; &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/getting-started/&#34;&gt;Getting started&lt;/a&gt;&#xA; through Step 3 (a building workspace); &lt;a href=&#34;https://theteia.lexbytes.org/docs/latest/architecture/&#34;&gt;Architecture&lt;/a&gt;&#xA; for the mental model.&lt;/p&gt;&#xA;&lt;h2 id=&#34;dev-setup&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#dev-setup&#34; aria-label=&#34;Link to Dev setup&#34;&gt;#&lt;/a&gt;&#xA;  Dev setup&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Install &lt;a href=&#34;https://rustup.rs&#34; class=&#34;docs-external-link&#34; rel=&#34;noreferrer noopener&#34;&gt;rustup&lt;span class=&#34;docs-external-link__icon&#34; aria-hidden=&#34;true&#34;&gt;↗&lt;/span&gt;&lt;/a&gt;&#xA;.&#xA;The pinned toolchain (1.98.1) is selected automatically from &lt;code&gt;rust-toolchain.toml&lt;/code&gt; — do not override it.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Clone and build:&lt;/p&gt;&#xA;&lt;figure class=&#34;docs-codeblock docs-codeblock--sh&#34;&gt;&lt;div&#xA;    class=&#34;docs-codeblock__body&#34;&#xA;    data-codeblock&#xA;    data-copy-label=&#34;Copy code&#34;&#xA;    data-copied-label=&#34;Copied&#34;&#xA;    data-copy-failed-label=&#34;Copy failed&#34;&#xA;  &gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone &amp;lt;TODO: repo URL&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ocbot&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo &lt;span class=&#34;nb&#34;&gt;test&lt;/span&gt; --workspace&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/figure&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Install the auxiliary checkers CI uses (optional locally, required to reproduce CI):&lt;/p&gt;&#xA;&lt;div class=&#34;table-wrap&#34;&gt;&#xA;  &lt;table&gt;&#xA;    &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;th&gt;Tool&lt;/th&gt;&#xA;        &lt;th&gt;What it does&lt;/th&gt;&#xA;        &lt;th&gt;Install&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/thead&gt;&#xA;    &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;cargo-nextest&lt;/td&gt;&#xA;        &lt;td&gt;Faster test runner&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install cargo-nextest --locked&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;cargo-deny&lt;/td&gt;&#xA;        &lt;td&gt;Advisory/license/ban checks&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install cargo-deny --locked&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;cargo-machete&lt;/td&gt;&#xA;        &lt;td&gt;Unused dependency check&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install cargo-machete&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;cargo-llvm-cov&lt;/td&gt;&#xA;        &lt;td&gt;Coverage&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install cargo-llvm-cov&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;cargo-mutants&lt;/td&gt;&#xA;        &lt;td&gt;Mutation testing&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install cargo-mutants&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;typos-cli&lt;/td&gt;&#xA;        &lt;td&gt;Docs spelling&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo install typos-cli&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/tbody&gt;&#xA;  &lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;</description>
			</item>
			<item>
				<title>NFR measurements &amp; enforcement</title>
				<link>https://theteia.lexbytes.org/docs/latest/nfr/</link>
				<pubDate>Thu, 08 Oct 2026 08:42:20 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/nfr/</guid>
				<description>&lt;h1 id=&#34;nfr-measurements--enforcement&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#nfr-measurements--enforcement&#34; aria-label=&#34;Link to NFR measurements &amp;amp;amp; enforcement&#34;&gt;#&lt;/a&gt;&#xA;  NFR measurements &amp;amp; enforcement&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Measured on the development host (Linux, sqlite 3.53.4, toolchain 1.98.1) on&#xA;2026-10-08. Each row names the mechanism that &lt;em&gt;enforces&lt;/em&gt; the budget and, where&#xA;it is a runtime number, the command that produced it.&lt;/p&gt;&#xA;&lt;div class=&#34;table-wrap&#34;&gt;&#xA;  &lt;table&gt;&#xA;    &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;th&gt;NFR&lt;/th&gt;&#xA;        &lt;th&gt;Budget&lt;/th&gt;&#xA;        &lt;th&gt;Mechanism / measurement&lt;/th&gt;&#xA;        &lt;th&gt;Recorded&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/thead&gt;&#xA;    &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-1 interaction ack&lt;/td&gt;&#xA;        &lt;td&gt;p99 &amp;lt; 1 s to defer; never &amp;gt; 3 s&lt;/td&gt;&#xA;        &lt;td&gt;The adapter defers &lt;strong&gt;before&lt;/strong&gt; any handler work (ack discipline, §6.6), and the pre-defer plan phase is one SQLite read with no port call (&lt;code&gt;guard.rs&lt;/code&gt; AC-1). &lt;code&gt;e2e_report_latency::plan_phase_stays_under_the_ack_budget&lt;/code&gt; measures it over 1,000 button interactions and holds p99 &amp;lt; 50 ms.&lt;/td&gt;&#xA;        &lt;td&gt;p99 &amp;lt; 50 ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-2 idle→report&lt;/td&gt;&#xA;        &lt;td&gt;p95 &amp;lt; 5 s (polling fallback ≤ interval + 2 s)&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo test -p ocbot --test e2e_report_latency&lt;/code&gt;: 100 cycles through the real client + adapter against wiremock; the test asserts p95 &amp;lt; 5 s. Suite wall time 0.24 s for 100 cycles ≈ &lt;strong&gt;2.4 ms/cycle&lt;/strong&gt; (≈3 orders under budget).&lt;/td&gt;&#xA;        &lt;td&gt;0.24 s / 100 cycles&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-3 report delivery&lt;/td&gt;&#xA;        &lt;td&gt;at-least-once; &amp;lt; 1 duplicate / 200&lt;/td&gt;&#xA;        &lt;td&gt;P9: the &lt;code&gt;report_msg_id IS NULL&lt;/code&gt; CAS plus &lt;code&gt;notified&lt;/code&gt; dedupe keys make detection exactly-once; only the Discord POST↔txn crash gap can duplicate (once per crash, never steady-state). &lt;code&gt;deliver_report&lt;/code&gt; is covered by &lt;code&gt;store_cas::deliver_report_is_exactly_once&lt;/code&gt; and &lt;code&gt;reconcile_boot&lt;/code&gt;.&lt;/td&gt;&#xA;        &lt;td&gt;design + tests&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-4 decisions exactly-once&lt;/td&gt;&#xA;        &lt;td&gt;0 duplicate commits / 1,000 double-taps&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;cargo test -p ocbot --test double_tap&lt;/code&gt;: 1,000 randomized interleavings, exactly one commit (AC-5).&lt;/td&gt;&#xA;        &lt;td&gt;1,000 trials green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-5 crash recovery&lt;/td&gt;&#xA;        &lt;td&gt;SIGKILL anywhere ⇒ consistent &amp;lt; 10 s, no lost work&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;chaos_full&lt;/code&gt; (11 labelled CP points, snapshot half) + &lt;code&gt;chaos_sigkill&lt;/code&gt; (real SIGKILL at accept/park/reset). Boot reconcile is synchronous; the suites finish in &amp;lt; 0.1 s.&lt;/td&gt;&#xA;        &lt;td&gt;14 chaos tests green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-6 memory&lt;/td&gt;&#xA;        &lt;td&gt;&amp;lt; 200 MB RSS steady&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;scripts/measure-rss.sh&lt;/code&gt; releases the binary against a dead opencode and reads &lt;code&gt;VmRSS&lt;/code&gt;. &lt;strong&gt;19,988 kB&lt;/strong&gt; at 3 s. &lt;code&gt;deploy/ocbot.service&lt;/code&gt; enforces &lt;code&gt;MemoryMax=200M&lt;/code&gt; as the ceiling.&lt;/td&gt;&#xA;        &lt;td&gt;~20 MB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-7 availability&lt;/td&gt;&#xA;        &lt;td&gt;single process, systemd &lt;code&gt;Restart=on-failure&lt;/code&gt;, 99%/month&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;deploy/ocbot.service&lt;/code&gt;, &lt;code&gt;deploy/opencode.service&lt;/code&gt;; no HA (self-hosted, single operator).&lt;/td&gt;&#xA;        &lt;td&gt;units committed&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-8 coverage/mutation&lt;/td&gt;&#xA;        &lt;td&gt;core ≥ 90% line+branch; proptest; scoped mutants&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;scripts/coverage.sh&lt;/code&gt; gates &lt;strong&gt;core ≥ 90, client ≥ 90, shell ≥ 70&lt;/strong&gt; (per-crate line coverage). Core is 94.72% by its own tests; mutation: &lt;code&gt;plan.rs&lt;/code&gt; 52 mutants &lt;strong&gt;0 missed&lt;/strong&gt;, &lt;code&gt;paths.rs&lt;/code&gt; 95/100, &lt;code&gt;stall.rs&lt;/code&gt; 10/10 (residuals in COMPAT/PHASE-7).&lt;/td&gt;&#xA;        &lt;td&gt;gates green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-9 observability&lt;/td&gt;&#xA;        &lt;td&gt;JSON logs; metrics on &lt;code&gt;127.0.0.1:9464&lt;/code&gt;; every transition logged&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;metrics.rs&lt;/code&gt; asserts the HTTP endpoint exposes every §6.8 family; &lt;code&gt;logging.rs&lt;/code&gt; writes JSON with a secret-scrubbing writer (&lt;code&gt;redaction.rs&lt;/code&gt;).&lt;/td&gt;&#xA;        &lt;td&gt;endpoint test green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-10 security gates&lt;/td&gt;&#xA;        &lt;td&gt;zero secrets in logs; spawn/exit confined; no &lt;code&gt;unwrap&lt;/code&gt; in core; &lt;code&gt;clippy -D warnings&lt;/code&gt;&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;scripts/gates.sh&lt;/code&gt; G3–G19; clippy restriction lints; &lt;code&gt;logging&lt;/code&gt; scrub layer.&lt;/td&gt;&#xA;        &lt;td&gt;gates green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-11 layering&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;ocbot-core&lt;/code&gt; declares no I/O deps&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;gates.sh&lt;/code&gt; G2: &lt;code&gt;cargo tree -e normal -p ocbot-core&lt;/code&gt; fails on serenity/tokio/reqwest/rusqlite/opencode-v2-client.&lt;/td&gt;&#xA;        &lt;td&gt;gap 2 green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;        &lt;td&gt;NFR-12 backup&lt;/td&gt;&#xA;        &lt;td&gt;nightly &lt;code&gt;VACUUM INTO&lt;/code&gt;, 14-day retention, restore drilled&lt;/td&gt;&#xA;        &lt;td&gt;&lt;code&gt;deploy/ocbot-backup.{service,timer}&lt;/code&gt; + &lt;code&gt;scripts/backup.sh&lt;/code&gt;; &lt;code&gt;scripts/restore-drill.sh&lt;/code&gt; run recorded in &lt;code&gt;docs/RESTORE.md&lt;/code&gt;.&lt;/td&gt;&#xA;        &lt;td&gt;drill green&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;    &lt;/tbody&gt;&#xA;  &lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;coverage-by-crate&#34;&gt;&#xA;  &lt;a class=&#34;docs-heading-anchor&#34; href=&#34;#coverage-by-crate&#34; aria-label=&#34;Link to Coverage by crate&#34;&gt;#&lt;/a&gt;&#xA;  Coverage by crate&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Measured per crate (each runner&amp;rsquo;s own tests) and gated by &lt;code&gt;scripts/coverage.sh&lt;/code&gt;&#xA;(2026-10-08). The gate floor is per crate: a crate&amp;rsquo;s gate reflects its own tests,&#xA;not coverage donated by another crate&amp;rsquo;s integration suite.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Search</title>
				<link>https://theteia.lexbytes.org/docs/latest/search/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://theteia.lexbytes.org/docs/latest/search/</guid>
				<description>&lt;p&gt;Type to search the full documentation index. Results are ranked by title and&#xA;content matches. Without JavaScript, the complete page list is rendered below&#xA;the search box — use your browser&amp;rsquo;s find-in-page.&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
