<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://tunahanaliozturk.dev/blog</id>
    <title>Tunahan Ali Öztürk Blog</title>
    <updated>2026-09-30T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://tunahanaliozturk.dev/blog"/>
    <subtitle>Tunahan Ali Öztürk Blog</subtitle>
    <icon>https://tunahanaliozturk.dev/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Why I built the Orion family]]></title>
        <id>https://tunahanaliozturk.dev/blog/the-orion-family</id>
        <link href="https://tunahanaliozturk.dev/blog/the-orion-family"/>
        <updated>2026-09-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Most .NET backends end up needing the same dozen things: validation, a distributed lock, an audit trail,]]></summary>
        <content type="html"><![CDATA[<p>Most .NET backends end up needing the same dozen things: validation, a distributed lock, an audit trail,
an outbox, encrypted columns, typed IDs, idempotent endpoints, rate limits, retries. Each team builds them
again, a little differently, and each version is wrong in its own small way.</p>
<p>The Orion family is my answer to that: small, focused libraries for those problems, built to one quality
bar and published on NuGet.</p>
<p>Today it is 24 libraries in 89 packages, downloaded more than 350,000 times. This post is about why they
exist, what holds them together, and what each one is for.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="primitives-not-a-framework">Primitives, not a framework<a href="https://tunahanaliozturk.dev/blog/the-orion-family#primitives-not-a-framework" class="hash-link" aria-label="Direct link to Primitives, not a framework" title="Direct link to Primitives, not a framework" translate="no">​</a></h2>
<p>Every Orion library solves one problem and stands on its own. You add OrionLock because you need a lock,
not because you signed up for a platform. There is no deep dependency web between them; pick only what
you need.</p>
<p>That also means saying no. OrionPatch is a transactional outbox, and its documentation says plainly that
sagas are out of scope: if you need a process manager, reach for MassTransit or Wolverine instead. A
library that knows where it stops is easier to trust than one that wants to be everything.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-holds-the-family-together">What holds the family together<a href="https://tunahanaliozturk.dev/blog/the-orion-family#what-holds-the-family-together" class="hash-link" aria-label="Direct link to What holds the family together" title="Direct link to What holds the family together" translate="no">​</a></h2>
<p>Building the libraries one after another, I noticed five things being implemented again and again, and
drifting apart each time:</p>
<ul>
<li class="">calling observers safely, so an observability outage can never break the code that does the real work;</li>
<li class="">OpenTelemetry naming, so every library's traces and metrics look alike on a dashboard;</li>
<li class="">a clock that tests can control;</li>
<li class="">the shape of options and DI registration;</li>
<li class="">the vocabulary for errors.</li>
</ul>
<p>They now live once, in <code>Orion.Abstractions</code>. Its surface is frozen at 1.0: every contract stays source- and
binary-compatible across the whole 1.x line, so one library can't break under a sibling's upgrade. The
rules that go with it are written down and apply to every package in the family.</p>
<p>Two libraries grew out of that spine. <strong>OrionClock</strong> is a <code>TimeProvider</code>, so it drops into any .NET API that
takes one, and it lets a test move time forward instead of waiting. <strong>OrionResult</strong> gives expected failures a
shape: a zero-allocation <code>Result&lt;T&gt;</code>, an <code>Option&lt;T&gt;</code>, and a structured <code>Error</code> with a code, a kind and field
errors.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-in-the-family">What is in the family<a href="https://tunahanaliozturk.dev/blog/the-orion-family#what-is-in-the-family" class="hash-link" aria-label="Direct link to What is in the family" title="Direct link to What is in the family" translate="no">​</a></h2>
<p><strong>Input and the domain</strong></p>
<ul>
<li class=""><strong>OrionGuard</strong>: validation, guard clauses and DDD primitives, with integrations for ASP.NET Core,
MediatR, Blazor, gRPC, SignalR and OpenTelemetry, source generators, and messages in 14 languages. The
most used of the family.</li>
<li class=""><strong>OrionKey</strong>: strongly-typed IDs from one attribute, with equality, EF Core and JSON converters generated
for you.</li>
</ul>
<p><strong>Data</strong></p>
<ul>
<li class=""><strong>OrionAudit</strong>: an automatic EF Core audit trail with JSON Patch diffs, and time travel to rebuild an
entity as it was at any moment.</li>
<li class=""><strong>OrionVault</strong>: column-level encryption at rest for EF Core: AES-256-GCM, key rotation, and a blind
index for searching encrypted values.</li>
<li class=""><strong>OrionPage</strong>: keyset pagination that stays fast on page 10,000, because <code>OFFSET</code> is a table scan.</li>
</ul>
<p><strong>Messaging and consistency</strong></p>
<ul>
<li class=""><strong>OrionPatch</strong> and <strong>OrionInbox</strong>: the two halves of reliable messaging. Enqueue inside the same
<code>SaveChanges</code> transaction and dispatch at least once; on the other side, apply each message's effect
exactly once.</li>
<li class=""><strong>OrionSaga</strong>: in-process saga orchestration; when a step fails, the completed steps are compensated.</li>
<li class=""><strong>OrionRelay</strong>: outbound webhooks, signed with HMAC-SHA256, retried with backoff and jitter.</li>
</ul>
<p><strong>Coordination</strong></p>
<ul>
<li class=""><strong>OrionLock</strong>: distributed locks with lease renewal and fencing tokens, over Redis, Postgres, SQL Server
or EF Core.</li>
<li class=""><strong>OrionBeacon</strong>: leader election, so exactly one instance runs the job that must run once.</li>
</ul>
<p><strong>HTTP and APIs</strong></p>
<ul>
<li class=""><strong>OrionOnce</strong>: idempotency keys, so a retried request gets the stored response instead of running twice.</li>
<li class=""><strong>OrionEnvelope</strong>: one HTTP contract for the whole API: typed <code>{ data, meta }</code> for success, RFC 9457
problem details for failure.</li>
<li class=""><strong>OrionRate</strong>: token-bucket and sliding-window rate limiting.</li>
<li class=""><strong>OrionGrant</strong> and <strong>OrionLedger</strong>: permissions and policies, and the full lifecycle of API keys.</li>
<li class=""><strong>OrionStream</strong>: Server-Sent Events with bounded buffers and heartbeats.</li>
</ul>
<p><strong>Cross-cutting</strong></p>
<ul>
<li class=""><strong>OrionLens</strong> carries a correlation id through async calls and across HTTP; <strong>OrionShade</strong> masks
secrets and personal data before they reach a log; <strong>OrionCache</strong> does cache-aside without a stampede;
<strong>OrionResilience</strong> retries with jitter over OrionClock, so a test of a retry takes no time.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="seeing-them-together">Seeing them together<a href="https://tunahanaliozturk.dev/blog/the-orion-family#seeing-them-together" class="hash-link" aria-label="Direct link to Seeing them together" title="Direct link to Seeing them together" translate="no">​</a></h2>
<p>Package pages show one library at a time. <strong>OrionShowcase</strong> shows how they work together: a
production-shaped banking sample in which one money transfer passes through OrionGuard's validation,
OrionLock's distributed locks, OrionAudit's change capture, OrionPatch's outbox, OrionKey's typed IDs and
OrionVault's encrypted personal data. Assembling that story is much harder than any single package looks,
and that is the point of the sample.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-share-them">Why share them<a href="https://tunahanaliozturk.dev/blog/the-orion-family#why-share-them" class="hash-link" aria-label="Direct link to Why share them" title="Direct link to Why share them" translate="no">​</a></h2>
<p>I wrote these because I needed them, and I publish them so that nobody else has to write them again. Every
library is MIT-licensed, has its documentation on this site, and takes issues on GitHub. The ones that need no database or broker can be tried right here in the
<a class="" href="https://tunahanaliozturk.dev/playground">playground</a>.</p>
<p>If you use one of them, or tried one and it didn't fit, I would like to know why. That is the most useful
thing anyone can tell me.</p>]]></content>
        <author>
            <name>Tunahan Ali Öztürk</name>
            <uri>https://github.com/tunahanaliozturk</uri>
        </author>
        <category label="dotnet" term="dotnet"/>
        <category label="orion" term="orion"/>
        <category label="open-source" term="open-source"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why I built Derbent]]></title>
        <id>https://tunahanaliozturk.dev/blog/why-i-built-derbent</id>
        <link href="https://tunahanaliozturk.dev/blog/why-i-built-derbent"/>
        <updated>2026-09-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I use more than one coding agent on the same repositories. Claude Code works on one thing, Codex on]]></summary>
        <content type="html"><![CDATA[<p>I use more than one coding agent on the same repositories. Claude Code works on one thing, Codex on
another, and now and then I ask Copilot CLI something. Each of them is good at its job, and none of them
knows what the others did.</p>
<p>What Claude Code learned an hour ago about why a table looks the way it does, Codex has no idea about.
There is no single record of which agent ran which command. Every CLI has its own list of MCP servers, so
I connect GitHub four times. And if I want an agent to ask me before it runs <code>git push</code>, I have to set
that up in four places, four different ways.</p>
<p>Derbent is the tool I wrote to pull that together. It is free and open source.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-does">What it does<a href="https://tunahanaliozturk.dev/blog/why-i-built-derbent#what-it-does" class="hash-link" aria-label="Direct link to What it does" title="Direct link to What it does" translate="no">​</a></h2>
<p>Derbent is a gate between coding agents and their tools. Claude Code, Codex, GitHub Copilot CLI and
Antigravity CLI connect to it as one ordinary MCP server, and your own MCP servers sit behind it. Every
tool call passes through the gate, and four things happen there.</p>
<p><strong>Shared memory.</strong> The agents get <code>memory_write</code>, <code>memory_search</code> and <code>memory_read</code>. A note one agent
writes in a repository, another agent working in the same repository can find. Search is full-text, and
a git worktree shares the memory of the repository it was added from.</p>
<p><strong>Rules.</strong> Allow, deny or ask, by agent, tool and argument. Rules are tried in order and the first match
wins. A tool an agent may not use is not even listed to it, and calling it by name is refused anyway.</p>
<p><strong>Approvals.</strong> A call that hits an <code>ask</code> rule waits until you decide. Run <code>derbent</code> in a terminal of its
own and the waiting calls are at the top: approve once, approve for the rest of that agent's session, or
deny. The same works from any shell with <code>derbent approve 12</code>. If nobody answers, the call is denied when
the timeout runs out, and the agent is told why.</p>
<p><strong>Receipts.</strong> Every call that passes through the gate leaves a receipt: which agent, which tool, which
arguments, what was decided and what came of it. The receipts form a hash chain, and <code>derbent verify</code>
tells you whether one was edited or removed. Secrets are masked before anything is stored.</p>
<p>The agents' own tools, their shell commands and file edits, don't go through MCP. But all four CLIs can
run a command of your choice before each tool call. Make that command <code>derbent gate</code>, and a <code>git push</code>
goes through the same rules, the same approval and the same receipt chain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-name">The name<a href="https://tunahanaliozturk.dev/blog/why-i-built-derbent#the-name" class="hash-link" aria-label="Direct link to The name" title="Direct link to The name" translate="no">​</a></h2>
<p>In Turkish history, a derbent was a guarded post on a mountain pass or a dangerous road. The men who kept it were
responsible for the pass, and in return were spared some taxes. They decided who went through and kept a
record of everyone who did.</p>
<p>Every English word I tried (gatekeeper, warden, checkpoint, bastion) was either another security
product's name or too general. Derbent says exactly what the tool does, and nobody else uses it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-is-built">How it is built<a href="https://tunahanaliozturk.dev/blog/why-i-built-derbent#how-it-is-built" class="hash-link" aria-label="Direct link to How it is built" title="Direct link to How it is built" translate="no">​</a></h2>
<p>The most important decision is that there is no daemon. The obvious shape is a hub every agent connects
to, but that hub has to start before any agent, stay alive, restart after a crash, and guard the port it
listens on. In Derbent every agent session starts its own gate process, and they all open the same SQLite
file. Nothing has to run first, there is no open port, and a crash takes down one agent's gate, not
everyone's.</p>
<p>That has a price, and the documentation says so: every agent session starts its own copy of the servers
behind the gate, and approvals are noticed by polling rather than instantly.</p>
<p>It is written in Go. Claude Code starts <code>derbent gate</code> once for every tool call, so every shell command
waits for a process to start, decide and exit. A Go binary starts without loading an interpreter, and it
is one file for Windows, macOS and Linux that asks for nothing else on the machine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-does-not-do">What it does not do<a href="https://tunahanaliozturk.dev/blog/why-i-built-derbent#what-it-does-not-do" class="hash-link" aria-label="Direct link to What it does not do" title="Direct link to What it does not do" translate="no">​</a></h2>
<p>Writing down what Derbent doesn't do mattered as much as what it does.</p>
<ul>
<li class=""><strong>The agent name is a label, not authentication.</strong> Any process running as you can call itself anything.</li>
<li class=""><strong>It is not a boundary against an agent that already has a shell as you.</strong> Such an agent could run
<code>derbent approve</code> itself, or write the database. Approvals guard against mistakes and against prompt
injection that stays inside MCP.</li>
<li class=""><strong>The chain doesn't catch everything on its own.</strong> It finds changes in the middle; a cut-off tail or a
chain rewritten from the start only shows against a copy of the head hash you keep somewhere else.</li>
<li class=""><strong>Glob rules don't understand shell syntax.</strong> <code>git push*</code> doesn't match <code>cd repo &amp;&amp; git push</code>.</li>
</ul>
<p>The worst thing a security tool can do is promise more than it does. Used knowing its limits, Derbent
helps a lot; trusted without knowing them, it would do harm.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it">Try it<a href="https://tunahanaliozturk.dev/blog/why-i-built-derbent#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">go </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> github.com/tunahanaliozturk/derbent/cmd/derbent@latest</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">claude mcp </span><span class="token function" style="color:#d73a49">add</span><span class="token plain"> derbent -- derbent mcp </span><span class="token parameter variable" style="color:#36acaa">--agent</span><span class="token plain"> claude</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">codex mcp </span><span class="token function" style="color:#d73a49">add</span><span class="token plain"> derbent -- derbent mcp </span><span class="token parameter variable" style="color:#36acaa">--agent</span><span class="token plain"> codex</span><br></div></code></pre></div></div>
<p>With just that, the two agents share one memory and every call is recorded. The
<a href="https://derbent.dev/" target="_blank" rel="noopener noreferrer" class="">documentation</a> covers rules, approvals and the hooks.</p>
<p>If you use more than one coding agent, I would like to hear from you, especially about which calls you
would want held for approval. Issues and ideas are welcome on
<a href="https://github.com/tunahanaliozturk/derbent" target="_blank" rel="noopener noreferrer" class="">GitHub</a>.</p>]]></content>
        <author>
            <name>Tunahan Ali Öztürk</name>
            <uri>https://github.com/tunahanaliozturk</uri>
        </author>
        <category label="derbent" term="derbent"/>
        <category label="ai-agents" term="ai-agents"/>
        <category label="mcp" term="mcp"/>
    </entry>
</feed>