<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: arcker</title>
    <description>The latest articles on DEV Community by arcker (@arcker).</description>
    <link>https://dev.to/arcker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979063%2Fb47f840e-534a-44db-83bd-f4f50d654c30.png</url>
      <title>DEV Community: arcker</title>
      <link>https://dev.to/arcker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arcker"/>
    <language>en</language>
    <item>
      <title>cidx v3.0.0 — deleting the things that lied</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 09 Aug 2026 09:38:22 +0000</pubDate>
      <link>https://dev.to/arcker/cidx-v300-deleting-the-things-that-lied-4fb4</link>
      <guid>https://dev.to/arcker/cidx-v300-deleting-the-things-that-lied-4fb4</guid>
      <description>&lt;p&gt;&lt;strong&gt;cidx is a declarative CI/CD runner I build&lt;/strong&gt; (in Go) — you describe your pipeline once, it runs the same locally and in CI. This post stands on its own.&lt;/p&gt;

&lt;p&gt;Here's how the week started. A public Docker image, on GitHub Container Registry. A clean &lt;code&gt;ENTRYPOINT ["cidx"]&lt;/code&gt;. Release after release, green CI every time.&lt;/p&gt;

&lt;p&gt;And no binary inside. Not once. Ever.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.dockerignore&lt;/code&gt; excluded &lt;code&gt;bin/&lt;/code&gt;. The &lt;code&gt;Dockerfile&lt;/code&gt; did &lt;code&gt;COPY bin/cidx /usr/local/bin/cidx&lt;/code&gt;. So the path was absent from the build context, kaniko matched nothing — &lt;strong&gt;and did not error&lt;/strong&gt;. Green pipeline, published release, &lt;code&gt;ENTRYPOINT&lt;/code&gt; pointing at a file that wasn't there.&lt;/p&gt;

&lt;p&gt;No test and no code review could have caught it. Everything, at every step, looked like it worked.&lt;/p&gt;

&lt;p&gt;That's the best illustration of what &lt;strong&gt;v3.0.0&lt;/strong&gt; turned out to be — cidx's first major since v2.0.0. Five breaking changes, 70 commits, 55 issues closed in seven days. And reading them back cold, they all tell exactly the same story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five breaking changes, one motive
&lt;/h2&gt;

&lt;p&gt;A breaking change usually removes something ugly, or renames, or modernises. Not here. Each of the five removes a feature that &lt;strong&gt;looked like it worked&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;local_behavior = "no-push"&lt;/code&gt; — a dry-run with a promise it could not keep.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The documentation said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;local_behavior = "no-push"&lt;/code&gt; ✅ Recommended for Docker&lt;br&gt;
Build without push — &lt;strong&gt;validates the Dockerfile and build process&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code said otherwise: &lt;code&gt;no-push&lt;/code&gt; set &lt;code&gt;IsDryRun&lt;/code&gt;, exactly as &lt;code&gt;dry-run&lt;/code&gt; did. The command was never handed to a backend. &lt;strong&gt;Nothing was built.&lt;/strong&gt; The two modes were behaviourally identical, and the one promising more was the lie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;[branch] auto_cleanup&lt;/code&gt; — parsed, and read by nobody.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A config key that existed, was accepted, was documented. No code ever consulted it. You'd set it, re-run, nothing happened — and nothing said so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unknown &lt;code&gt;cidx.toml&lt;/code&gt; keys — a typo indistinguishable from a valid setting.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[branch]&lt;/span&gt;
&lt;span class="py"&gt;stale_dayz&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;          &lt;span class="c"&gt;# typo of stale_days&lt;/span&gt;
&lt;span class="py"&gt;typo_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"whatever"&lt;/span&gt;    &lt;span class="c"&gt;# not a key at all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cidx validate
&lt;span class="go"&gt;✓ Configuration is valid
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stale_days&lt;/code&gt; silently kept its default of 30, and nothing said why. You had configured something. The tool confirmed it was valid. It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;go-test&lt;/code&gt; didn't run every package.&lt;/strong&gt; The preset tested less than it appeared to. The green bar described a wider scope than the real one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The &lt;code&gt;cidx action&lt;/code&gt; command tree — removed&lt;/strong&gt; after a 3.5-month deprecation window. The only ordinary one of the five: it removes something &lt;em&gt;announced as leaving&lt;/em&gt;, not something misleading.&lt;/p&gt;

&lt;p&gt;Four out of five, then: not quality defects — &lt;strong&gt;honesty defects&lt;/strong&gt;. A tool that prints &lt;code&gt;✓ valid&lt;/code&gt; over a typo doesn't waste your time by being slow. It wastes it by earning your trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they were findable now
&lt;/h2&gt;

&lt;p&gt;These things don't show up by reading code: everything compiles, everything passes, everything is green. You need a mechanism that actively &lt;em&gt;hunts&lt;/em&gt; the gap between what is promised and what happens.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;no-push&lt;/code&gt; lie was caught by a BDD sweep: a scenario asserted "the Docker image should be built" locally under &lt;code&gt;no-push&lt;/code&gt;… and &lt;strong&gt;that scenario could never have passed&lt;/strong&gt;. It wasn't code indicting code — it was a &lt;em&gt;written promise&lt;/em&gt; held against real behaviour.&lt;/p&gt;

&lt;p&gt;Same for the empty image: found while replacing the kaniko image, and confirmed pre-existing — identical on the old version, so not a regression. Nobody was looking for it; someone went and checked.&lt;/p&gt;

&lt;p&gt;This is the direct sequel to &lt;a href="https://arcker.org/blog/2026-07-19-cidx-v2-1-0/" rel="noopener noreferrer"&gt;what I wrote in July&lt;/a&gt;: cutting a release with the tool's own release command produced seven papercuts, all filed the same day. That was the thesis; this is the demonstration at major-release scale. Those seven are closed, along with forty-eight more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The load-bearing one: &lt;code&gt;/v3&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One breaking change wasn't a deletion. The module path moved to &lt;code&gt;github.com/cidx-org/cidx/v3&lt;/code&gt; — without it, v3 tags simply aren't &lt;code&gt;go install&lt;/code&gt;able. That's the Go modules rule: from v2 on, the major version is part of the path. Skip it and you publish a major nobody can install.&lt;/p&gt;

&lt;p&gt;And the guardrail that came with it, in the same spirit as the rest: &lt;strong&gt;&lt;code&gt;release create&lt;/code&gt; now refuses to cut a major the current module path cannot publish&lt;/strong&gt; — rather than letting the tool produce a dead tag with a smile.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it teaches
&lt;/h2&gt;

&lt;p&gt;The same pattern showed up elsewhere the same week, in another project. In &lt;a href="https://arcker.org/blog/2026-08-02-943-octets-qui-repondent-en-http/" rel="noopener noreferrer"&gt;Verbose&lt;/a&gt;, the worst defect closed this week: the self-hosted compiler &lt;strong&gt;silently dropped every rule&lt;/strong&gt; after any top-level item it didn't recognise — &lt;strong&gt;and exited 0&lt;/strong&gt;. Your code wasn't compiled, and you were told everything was fine.&lt;/p&gt;

&lt;p&gt;Two projects, two domains, one week, the same underlying defect: &lt;strong&gt;success reported while the work never happened.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A tool doesn't become reliable by accumulating features. It becomes reliable when you remove the ones that pretend. A deleted &lt;code&gt;no-push&lt;/code&gt; is more honest than a &lt;code&gt;no-push&lt;/code&gt; claiming to validate a build; a &lt;code&gt;cidx.toml&lt;/code&gt; that rejects an unknown key is more useful than an agreeable &lt;code&gt;✓ valid&lt;/code&gt;; a missing image would have been less harmful than a published, empty one.&lt;/p&gt;

&lt;p&gt;And the limit, named as usual: &lt;strong&gt;five issues remain open&lt;/strong&gt;, all design questions rather than bugs. v3.1.0 landed the next morning (tag-rebuild detection, digest-pinned scanners, refusing to merge onto a commit the remote doesn't have). The pace will drop — two back-to-back sprint weeks is a window, not a cadence.&lt;/p&gt;

&lt;p&gt;What's left is a simple triage rule going forward: when a feature and its documentation disagree, the documentation isn't the first thing to fix. The first question is how long you've been believing it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;French original on &lt;a href="https://arcker.org/blog/2026-08-09-cidx-v3-supprimer-ce-qui-mentait/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>go</category>
      <category>cicd</category>
      <category>opensource</category>
    </item>
    <item>
      <title>943 bytes that answer HTTP</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 02 Aug 2026 07:51:58 +0000</pubDate>
      <link>https://dev.to/arcker/943-bytes-that-answer-http-2f3f</link>
      <guid>https://dev.to/arcker/943-bytes-that-answer-http-2f3f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I build&lt;/strong&gt; — its compiler proves properties about your code (termination, sound types, declared effects) and emits tiny x86-64 machine code: no runtime, no GC, no libc. This post stands on its own.&lt;/p&gt;

&lt;p&gt;Picture a 943-byte file. Smaller than a long text message. You run it and send this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET / HTTP/1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.0 200 OK
Content-Length: 29

Hello from Verbose over HTTP!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Node, no Python, no nginx in front. No runtime, no garbage collector, not even the C library. &lt;strong&gt;943 bytes, alone against the network.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that's not even the interesting part. That binary was written by a compiler which is itself written in Verbose — the one that, &lt;a href="https://arcker.org/blog/2026-07-12-le-point-fixe/" rel="noopener noreferrer"&gt;last month, reproduced itself byte for byte&lt;/a&gt;. The language no longer merely compiles itself: it now produces the kind of program people actually write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving HTTP when you have nothing
&lt;/h2&gt;

&lt;p&gt;The analogy first, before any jargon.&lt;/p&gt;

&lt;p&gt;You open an office. To receive clients you need four things, &lt;strong&gt;in this order&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get a phone line installed.&lt;/strong&gt; No number yet, just the handset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a number&lt;/strong&gt; — 18893. Now people can reach you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lift the handset and put it on the desk&lt;/strong&gt;: you're reachable, calls can queue up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take the calls, one at a time.&lt;/strong&gt; Each one: listen, answer, hang up. Then take the next.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is &lt;em&gt;exactly&lt;/em&gt; what a server does, with four calls into the kernel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  socket()      →  "install me a line"                → returns a file number (fd)
  setsockopt()  →  "when I hang up, free the line      (SO_REUSEADDR)
                    immediately, don't hold it
                    blocked for a minute"
  bind(18893)   →  "my number is 18893,                (INADDR_ANY = on every
                    on every line"                      network interface)
  listen()      →  "handset off the hook: queue
                    incoming calls"

  ┌── loop, forever ──────────────────────────────────┐
  │  accept()   →  take the next call                 │
  │                (waits if there is none)           │
  │  read()     →  listen to what the client says     │
  │  write()    →  answer                             │
  │  close()    →  hang up THIS call                  │
  └──────────────── and start over ───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. There is nothing else in an HTTP server. Everything you know — Express, Flask, Spring — is a comfort stack &lt;strong&gt;on top of those four lines&lt;/strong&gt;. The comfort isn't useless, but it isn't &lt;em&gt;necessary&lt;/em&gt;: that's why 943 bytes are enough.&lt;/p&gt;

&lt;p&gt;The first slice (S1) does precisely that and no more: the handler returns a response written ahead of time, the same for everyone. It's the smallest object still deserving the name "server".&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot: reading what the client said
&lt;/h2&gt;

&lt;p&gt;A server that always answers the same thing is an answering machine. To go further you must &lt;strong&gt;understand the request&lt;/strong&gt;. That's slice S2 — the pivot of the whole arc: nothing after it exists without it.&lt;/p&gt;

&lt;p&gt;The client sends a run of bytes. Raw. Two pieces of information have to be cut out of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  What arrives on the wire (bytes):

    G  E  T     /  h  e  l  l  o     H  T  T  P  /  1  .  0  \r \n \r \n
    └──┬──┘  ▲  └────┬────┘       ▲
       │     │       │            │
       │   space     │          space
       │             │
    method          path

  What the parser extracts, into two "slots":

    req.method  →  pointer to "GET"     + length 3
    req.path    →  pointer to "/hello"  + length 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail that matters: nothing is &lt;strong&gt;copied&lt;/strong&gt;. We only note &lt;em&gt;where it starts&lt;/em&gt; and &lt;em&gt;how long it is&lt;/em&gt;. The bytes stay where the kernel dropped them. An address and a length — two numbers — not a string copied somewhere else.&lt;/p&gt;

&lt;p&gt;Those two slots, &lt;code&gt;req.method&lt;/code&gt; and &lt;code&gt;req.path&lt;/code&gt;, are the raw material for everything that follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Answering differently per request
&lt;/h2&gt;

&lt;p&gt;Slice S3: routing. Once you know &lt;em&gt;what&lt;/em&gt; was asked, you can answer differently. Then S4: build the response body &lt;strong&gt;from&lt;/strong&gt; the request.&lt;/p&gt;

&lt;p&gt;Here is a real example from the repo (&lt;code&gt;examples/echo_path.verbose&lt;/code&gt;), trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rule echo_handler
  input:
    req : HttpRequest
  output:
    resp : HttpResponse
  logic:
    resp = if req.method == "GET"
             then HttpResponse { status: 200, body: concat("got GET on ", req.path) }
           else if req.method == "POST"
             then HttpResponse { status: 200, body: concat("got POST on ", req.path) }
           else
             HttpResponse { status: 404, body: concat("unsupported method ", req.method) }
  proofs:
    purity:
      reads : [req.method, req.path]

service echo_server
  listen:
    protocol    : http_1_0
    port        : 18893
    max_request : 4096
  handler: echo_handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk one call through, concrete values at every step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1. Client sends     :  "GET /hello HTTP/1.0\r\n\r\n"
  2. Parser fills     :  req.method = "GET"   req.path = "/hello"
  3. Rule evaluates   :  req.method == "GET"  →  true, first branch
  4. concat assembles :  "got GET on " + "/hello"  →  "got GET on /hello"
  5. Binary writes    :  HTTP/1.0 200 OK
                         Content-Length: 17

                         got GET on /hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;proofs:&lt;/code&gt; block. It declares: &lt;em&gt;this rule reads &lt;code&gt;req.method&lt;/code&gt; and &lt;code&gt;req.path&lt;/code&gt;, and nothing else.&lt;/em&gt; That's not a comment — the compiler checks it. If the logic read a third field without declaring it, it wouldn't compile. The language's standing promise: &lt;strong&gt;the author declares, the binary doesn't drift.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;concat&lt;/code&gt; in step 4 deserves a note. Until S4, the response body had to be written ahead of time. From S4 on it is &lt;em&gt;assembled at call time&lt;/em&gt; — and its buffer lives on the iteration's stack. The loop frees it in one instruction before the next call, whichever branch ran. No allocation, no deallocation to track, no possible leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detail that lands: one log line, one syscall
&lt;/h2&gt;

&lt;p&gt;Slices S5a and S5b: the server now writes its &lt;strong&gt;access log&lt;/strong&gt;. And that's where the most instructive part hides.&lt;/p&gt;

&lt;p&gt;A log block looks like this (real example, &lt;code&gt;examples/audit_strict.verbose&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log:
  append_file "/tmp/audit.jsonl"
    concat("{\"method\":\"", req.method, "\",\"path\":\"", req.path, "\"}\n")
  on_error: abort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first implementation did the obvious thing: &lt;strong&gt;one &lt;code&gt;write()&lt;/code&gt; per piece of the &lt;code&gt;concat&lt;/code&gt;&lt;/strong&gt;. Disassembling the compiled binary showed it plainly — four successive writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  0x202  open      ← open the file
  0x217  write     ← "{\"method\":\""
  0x23a  write     ← "GET"
  0x24c  write     ← "\",\"path\":\""
  0x26f  write     ← "/hello\"}\n"
  0x279  close     ← close
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. As long as there is exactly one client.&lt;/p&gt;

&lt;p&gt;Now two requests arrive at once. The kernel may interleave their writes — it has no reason not to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  What you wanted:
    {"method":"GET","path":"/hello"}
    {"method":"POST","path":"/submit"}

  What you can get:
    {"method":"GET","{"method":"POST","path":"/hello"}
    path":"/submit"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two corrupted lines. Neither is true. And it's &lt;strong&gt;the worst kind of bug&lt;/strong&gt;: it doesn't show up in development (one client), it doesn't crash anything, it only appears under load now and then — and it damages precisely the file you read to understand what happened. An audit log that lies under load is worse than no log at all.&lt;/p&gt;

&lt;p&gt;The fix (PR #138): replace the N &lt;code&gt;write&lt;/code&gt;s with &lt;strong&gt;one &lt;code&gt;writev&lt;/code&gt;&lt;/strong&gt;. Same idea as an envelope. Instead of mailing four sheets separately and hoping they arrive in order, you put them all in one envelope and mail that. The postman can no longer shuffle them with the neighbour's.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  BEFORE — 4 calls, interleavable          AFTER — 1 call, atomic

  write(fd, "{\"method\":\"", 11)          iov[0] = ("{\"method\":\"", 11)
  write(fd, "GET",             3)          iov[1] = ("GET",             3)
  write(fd, "\",\"path\":\"",  10)         iov[2] = ("\",\"path\":\"",  10)
  write(fd, "/hello\"}\n",      9)         iov[3] = ("/hello\"}\n",      9)
                                           writev(fd, iov, 4)
  open → write → write → write → write     open → writev → close
       → close
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;iov&lt;/code&gt; array (four &lt;em&gt;address + length&lt;/em&gt; pairs) is built on the stack, and syscall 20 consumes it in one go. All or nothing: the whole line is written, or none of it is. Never half.&lt;/p&gt;

&lt;p&gt;It looks like an optimization detail. It is one — six syscalls down to three. But above all, &lt;strong&gt;it's a change of guarantee&lt;/strong&gt;: the log file becomes a sequence of complete lines, by construction, under any concurrency. And it's the prerequisite for the next slice, &lt;code&gt;on_error: abort&lt;/code&gt; — "if I can't write the audit trail, stop everything". That promise means nothing if a line can already come out cut in half.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it means
&lt;/h2&gt;

&lt;p&gt;The seven slices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  S1   a server that always answers the same        → 943-byte ELF
  S2   parse the request (method, path)             → the pivot
  S3   route on method and path
  S4   build the body from the request
  S5a  write log lines
  S5b  logs containing request fields
  5b.5 one line = one writev = atomic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In six days the self-hosted compiler went from "it can reproduce itself" to "it can produce a web service that parses, routes, answers and traces". Those are very different things. The first proves the language stands up; the second proves it's &lt;strong&gt;good for something&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the limit, named as usual: &lt;strong&gt;v0.10.0 still isn't tagged.&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt; is 54 commits ahead of v0.9.0 — the fixed point, the verifying bootstrap, the effects tier, and now the service arc. There's a release-shaped moment there, ripe for weeks. It's waiting on a decision, not on code.&lt;/p&gt;

&lt;p&gt;943 bytes that answer HTTP. Written by a compiler that wrote itself. No runtime, no libc, and logs you can believe.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;French original on &lt;a href="https://arcker.org/blog/2026-08-02-943-octets-qui-repondent-en-http/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>rust</category>
      <category>linux</category>
      <category>programming</category>
    </item>
    <item>
      <title>probatum — your tests pass. does it actually boot?</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:58:47 +0000</pubDate>
      <link>https://dev.to/arcker/probatum-your-tests-pass-does-it-actually-boot-kcp</link>
      <guid>https://dev.to/arcker/probatum-your-tests-pass-does-it-actually-boot-kcp</guid>
      <description>&lt;p&gt;&lt;strong&gt;probatum is a test-oriented check runner I build&lt;/strong&gt; (in Rust) — one config file, embedded checks (curl, grep and process supervision built in), and only the failures that matter surfaced. This post stands on its own: it's about &lt;em&gt;why&lt;/em&gt; I wrote it next to &lt;a href="https://github.com/cidx-org/cidx" rel="noopener noreferrer"&gt;cidx&lt;/a&gt;, my CI/CD runner.&lt;/p&gt;

&lt;p&gt;Here's the itch. Your test suite is green. 142 tests pass. You tag, you deploy. And the app doesn't boot — because the tests mocked the store, and the real boot replays the WAL, and one segment is missing. No test ever touched that path.&lt;/p&gt;

&lt;p&gt;That gap — between &lt;em&gt;the tests pass&lt;/em&gt; and &lt;em&gt;it actually stands up&lt;/em&gt; — is the whole reason probatum exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What probatum checks (that tests don't look at)
&lt;/h2&gt;

&lt;p&gt;One file, &lt;code&gt;probatum.yaml&lt;/code&gt;. Flat checks, no logic. The curl, the grep, the process supervision are &lt;strong&gt;embedded&lt;/strong&gt; — you just declare the rules that make a check pass or fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# start the real service, wait until it answers, keep it alive for what follows&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api boots&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./target/debug/myapp --port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8080/healthz&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;

&lt;span class="c1"&gt;# embedded curl&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:8080/api/version&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
  &lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;"version"'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# embedded grep — only the lines written DURING this run&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app log is clean&lt;/span&gt;
  &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/log/myapp/app.log&lt;/span&gt;
  &lt;span class="na"&gt;absent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;panic"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;probatum starts your real system, interrogates it, reads its logs — then &lt;strong&gt;kills the whole process tree on every exit path&lt;/strong&gt;: normal end, probatum's own panic, Ctrl-C, SIGTERM. If probatum dies, nothing it started survives. No zombie port between runs, no orphan server hanging around.&lt;/p&gt;

&lt;p&gt;And the contract that every hand-rolled bash script is missing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;failed (exit 1) ≠ couldn't observe (exit 2).&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A missing binary, an unreachable URL, a dirty environment — that's not a bug in your code, that's "I couldn't look." Conflating the two makes you chase ghosts. probatum refuses an already-dirty environment (the port answers &lt;em&gt;before&lt;/em&gt; the service starts) instead of destroying it: it never purges what it doesn't own.&lt;/p&gt;

&lt;p&gt;The rest is the noise filter: panic, traceback, FATAL caught by default; and humble when unsure — it shows the tail of the output and says "exit 1", it never invents a cause. A false cause is worse than no cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just a cidx stage?
&lt;/h2&gt;

&lt;p&gt;The real question. I already maintain cidx, a declarative CI/CD runner: it runs &lt;em&gt;tools&lt;/em&gt; against my code (trivy, gitleaks, go-test), identically local and in CI, grouped into phases and pipelines. So why a second tool?&lt;/p&gt;

&lt;p&gt;The honest admission first: both can run an arbitrary command. A cidx stage &lt;em&gt;could&lt;/em&gt; script the same checks. But they don't verify the same &lt;strong&gt;object&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  cidx  ──▶  the CODE, statically
            (which scanners/linters/tests run, local == CI)

  probatum ──▶ the RUNNING SYSTEM, dynamically
            (boot, readiness, HTTP behavior, log window, teardown)
            ← cidx has none of that machinery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sharing rule is one line: &lt;em&gt;run a tool against my sources&lt;/em&gt; → cidx stage. &lt;em&gt;Start my system and observe its behavior&lt;/em&gt; → probatum.&lt;/p&gt;

&lt;p&gt;So no merge. probatum joins the roster of tools cidx orchestrates — like trivy, like go-test — linked by a preset. One source of truth, two launchers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  probatum.yaml  ← one file
      │
      ├── inner loop (a dev or an agent, in seconds) : probatum run
      └── outer loop (CI, pipeline)                  : cidx run test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the guardrail — the actual design decision: &lt;strong&gt;if probatum grows phases and pipelines, it's becoming cidx → refuse. If cidx grows readiness and process ownership, it's becoming probatum → same.&lt;/strong&gt; The boundary isn't an accident, it's held on purpose. Two small tools that each do one thing and keep their edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proof engine with nothing to prove
&lt;/h2&gt;

&lt;p&gt;Confession: probatum didn't start like this. It was born a "proof engine" — &lt;em&gt;promises&lt;/em&gt;, &lt;em&gt;oracles&lt;/em&gt;, an &lt;em&gt;evidence&lt;/em&gt; registry, the whole vocabulary of formal verification. Impressive on paper.&lt;/p&gt;

&lt;p&gt;I cut it. That vocabulary was overhead, not value — grandiosity around a simple need: &lt;em&gt;one line, one file, run my checks, show me only what matters.&lt;/em&gt; A proof engine that, in the end, had nothing to prove — just something to make useful. (Yes, the pun is cheap. It's also exactly what happened.)&lt;/p&gt;

&lt;p&gt;Dropping the word "proof" is the same discipline as everywhere in these projects: name the limit, throw out the vocabulary that inflates. A tool earns its words; it doesn't borrow them from theory to look impressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real first reader is the agent
&lt;/h2&gt;

&lt;p&gt;What drove the cut: probatum's first reader isn't a human scanning a terminal. It's an AI agent (and the human over its shoulder).&lt;/p&gt;

&lt;p&gt;An agent chaining fifty ad-hoc bash runs drowns in output and piles up orphan servers on port 8080. probatum hands it &lt;strong&gt;one&lt;/strong&gt; verdict (&lt;code&gt;probatum run --json&lt;/code&gt;) and owns everything it starts — so you can loop it tight without cleaning up behind it. That's the differentiator versus "just run bash": process ownership, the collapsed verdict, and the evidence trail to replay. A misspelled YAML key is an &lt;strong&gt;error&lt;/strong&gt;, never a silently skipped check — because an agent believing it tested something it didn't is the worst lie of all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually stands
&lt;/h2&gt;

&lt;p&gt;probatum is twelve days old. &lt;code&gt;v0.1.0&lt;/code&gt;, 21 commits, a ~1 MB static binary, a 14 MB image, zero external contributors, zero users besides me. It's not on crates.io — I haven't decided to claim the name yet.&lt;/p&gt;

&lt;p&gt;What's solid today: the contract is frozen (sources × flat rules, exit 0/1/2, process ownership on every exit path), it tests itself — the root &lt;code&gt;probatum.yaml&lt;/code&gt; builds, lints, runs the demo end-to-end and asserts the negative scenarios are actually &lt;em&gt;caught&lt;/em&gt; — and it runs inside cidx 2.1.0 as a test-phase preset. The two dogfood each other: cidx's pipeline runs inside probatum, probatum ships as a cidx preset. Each has already found bugs in the other.&lt;/p&gt;

&lt;p&gt;What's not done: everything else. This is a v0.1.0, and this post is about its boundary, not its maturity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ecosystem I'm aiming at
&lt;/h2&gt;

&lt;p&gt;The map, then: cidx orchestrates the outer loop, probatum verifies the running system in the inner loop. They dogfood each other — cidx's pipeline runs inside probatum, probatum ships as a cidx preset — both are built for the agent, and each refuses to become the other.&lt;/p&gt;

&lt;p&gt;It's not a platform. It's a toolchain where every link does one thing, stays replaceable, and is honest about its edge. The bet isn't in the size of each tool — it's in the boundaries between them, held on purpose. probatum is one more link: the one that answers, once the tests are green, the only question left — &lt;em&gt;sure, but does it actually stand up?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;French original on &lt;a href="https://arcker.org/blog/2026-07-26-probatum/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>testing</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
    <item>
      <title>cidx v2.1.0 — the most useful output of a release was its seven bug reports</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 19 Jul 2026 08:56:20 +0000</pubDate>
      <link>https://dev.to/arcker/cidx-v210-the-most-useful-output-of-a-release-was-its-seven-bug-reports-43lf</link>
      <guid>https://dev.to/arcker/cidx-v210-the-most-useful-output-of-a-release-was-its-seven-bug-reports-43lf</guid>
      <description>&lt;p&gt;&lt;strong&gt;cidx is a declarative CI/CD runner I build&lt;/strong&gt; (in Go) — you describe your pipeline once, it runs the same locally and in CI, exit-code parity guaranteed. v2.1.0 just shipped. And the most useful thing it produced wasn't the tag.&lt;/p&gt;

&lt;p&gt;The interesting part of a release is almost never the changelog. It's what &lt;em&gt;cutting&lt;/em&gt; the release teaches you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short part (the diff)
&lt;/h2&gt;

&lt;p&gt;11 commits since v2.0.0, after 59 quiet days. A &lt;code&gt;probatum&lt;/code&gt; test-phase preset, a &lt;code&gt;pr edit&lt;/code&gt; action (retitle a PR without dropping to the platform UI), and six fixes: prebuilt &lt;code&gt;cargo-audit&lt;/code&gt; (faster security phase), anonymous-pull fallback when a registry 401s on stale creds, pinning the bootstrapped cidx to the version that generated it (no more &lt;code&gt;@latest&lt;/code&gt; drift), &lt;code&gt;cpw&lt;/code&gt; waiting for the CI workflow to actually start, branch prefixes derived from commit type, and &lt;code&gt;check drift&lt;/code&gt; &lt;em&gt;resolving&lt;/em&gt; the workflow file instead of guessing &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;. Correct, useful, boring. On to the real story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cutting a release with your own tool
&lt;/h2&gt;

&lt;p&gt;cidx can cut a release: &lt;code&gt;cidx release create&lt;/code&gt;. So I used it to cut its own. And that's exactly where it gets interesting — a tool on a tidy demo never shows its rough edges; a tool on a &lt;strong&gt;real&lt;/strong&gt;, high-stakes task (tag it, push a version) shows them all.&lt;/p&gt;

&lt;p&gt;Seven, that day. Filed same-day, tag still warm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;#184&lt;/strong&gt; — &lt;code&gt;release create&lt;/code&gt; pushes the version-bump commit straight to &lt;code&gt;main&lt;/code&gt;… and gets rejected by branch protection. Recovery is a hand-rolled PR-then-cherry-pick-then-tag. The tool doesn't know the rules of the repo it runs on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#185&lt;/strong&gt; — &lt;code&gt;release preview&lt;/code&gt; computes the version from &lt;strong&gt;three sources&lt;/strong&gt; (VERSION, &lt;code&gt;.cz.toml&lt;/code&gt;, latest tag) with no reconciliation. First run: it suggested &lt;code&gt;v1.8.0&lt;/code&gt; when the latest tag was &lt;code&gt;v2.0.0&lt;/code&gt;. A single source of truth was missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#186&lt;/strong&gt; — &lt;code&gt;release tag prepare&lt;/code&gt; waits on an editor with no TTY detection. It hangs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#180&lt;/strong&gt; — &lt;code&gt;cpw&lt;/code&gt; reports "nothing to commit" for untracked files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#178&lt;/strong&gt; — cidx's own &lt;code&gt;cidx.toml&lt;/code&gt; now has &lt;em&gt;real&lt;/em&gt; drift — precisely because fix #177 finally lets &lt;code&gt;check drift&lt;/code&gt; see it: &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;release&lt;/code&gt; phases declared, no matching jobs in &lt;code&gt;ci.yml&lt;/code&gt;. The tool finding its own mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#175&lt;/strong&gt; — breaking-change detection reads &lt;code&gt;type!:&lt;/code&gt; from parsed commits, but the parser regex can't capture the &lt;code&gt;!&lt;/code&gt;. Latent bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;#174&lt;/strong&gt; — ~20 CLI hints still point at the deprecated &lt;code&gt;cidx action …&lt;/code&gt; namespace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those show up reading the code. Each is a knot you only feel by &lt;em&gt;pulling on the rope for real&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop working, not the loop breaking
&lt;/h2&gt;

&lt;p&gt;cidx's &lt;code&gt;CLAUDE.md&lt;/code&gt; states a rule: &lt;em&gt;"we eat our own cooking; if a command has bad UX, that becomes the next priority."&lt;/em&gt; v2.1.0 is that sentence in action. The release ships, &lt;strong&gt;and&lt;/strong&gt; the release-tooling backlog grows the same day. That's not a release failure — it's the only honest way to know where the tool hurts: take it seriously on its own most sensitive task.&lt;/p&gt;

&lt;p&gt;And here's the nuance. Everyone says "we dogfood — we use our own tool." The honest version is different: &lt;strong&gt;you use it on the scary thing, and you write down, publicly and same-day, every place it bit.&lt;/strong&gt; The rough edges aren't swept under the rug waiting for a user to find them. They're the roadmap — dated, numbered.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The v2.1.0 → v2.2.0 backlog is already those seven issues plus the #178 drift call. The next release will be quieter on features, heavier on release plumbing: TTY detection, one source of truth for "current version", protection-aware push routing, the deprecated-hint sweep. Small releases, tight loop — and each turn, the tool knows a little better where it hurts, because we dared to use it where it counts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;French version on &lt;a href="https://arcker.org/blog/2026-07-19-cidx-v2-1-0/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>The fixed point: a compiler written in its own language just reproduced itself, byte for byte</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 12 Jul 2026 09:08:49 +0000</pubDate>
      <link>https://dev.to/arcker/the-fixed-point-a-compiler-written-in-its-own-language-just-reproduced-itself-byte-for-byte-2bf0</link>
      <guid>https://dev.to/arcker/the-fixed-point-a-compiler-written-in-its-own-language-just-reproduced-itself-byte-for-byte-2bf0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building&lt;/strong&gt; — its compiler proves properties about your code (termination, sound types) and emits tiny x86-64 machine code, no runtime, no GC, no libc. This post stands on its own. The news: the compiler, written in Verbose itself, just reached the classic self-hosting &lt;strong&gt;fixed point&lt;/strong&gt; — it reproduced its entire self, byte for byte.&lt;/p&gt;

&lt;p&gt;Photocopy a photocopy of a photocopy and each generation degrades. Except there's a magic case where the copy is &lt;em&gt;perfect&lt;/em&gt; — identical to the original, forever. That's a &lt;strong&gt;fixed point&lt;/strong&gt;: applying the operation once more changes nothing. Every language whose compiler is written in itself eventually faces one question: &lt;em&gt;the version of me I just produced, when asked to produce me, does it produce exactly me?&lt;/em&gt; On 2026-07-11, Verbose answered yes, to the byte. And it's on &lt;code&gt;main&lt;/code&gt; now — not a branch — 18 commits closing the arc opened June 13.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three generations
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  gen0  ← the Verbose emitter, compiled by the Rust host compiler.
          (what a fresh checkout builds)
            │  feed it the 855 KB of Verbose source
            ▼
  gen1  ← the emitted ELF: the Verbose emitter, but compiled BY Verbose.
            │  feed it the SAME 855 KB source
            ▼
  gen2  ← the ELF gen1 emits in turn.

  The gate:   gen1  ==  gen2   (byte for byte)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gen0&lt;/code&gt; is the Rust-built bootstrap. &lt;code&gt;gen1&lt;/code&gt; is Verbose compiled by itself once. &lt;code&gt;gen2&lt;/code&gt; is Verbose-compiled-by-Verbose compiling itself again. Verdict: &lt;strong&gt;&lt;code&gt;gen1 == gen2 == 1,302,980 bytes&lt;/code&gt;, same sha256 &lt;code&gt;001481eb…&lt;/code&gt;.&lt;/strong&gt; Not "it works" — the &lt;em&gt;same binary, bit for bit, two generations running&lt;/em&gt;. Verified by an &lt;code&gt;#[ignore]&lt;/code&gt;d regression test (needs ~9 GB RAM, ~30 s).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why byte-identical, not just "it compiles"
&lt;/h2&gt;

&lt;p&gt;"It compiles" only asks for a binary that &lt;em&gt;runs&lt;/em&gt;. The fixed point asks more: that the compiler, reproducing itself, doesn't drift by a single byte. If it drifted — even one bit — &lt;code&gt;gen1&lt;/code&gt; and &lt;code&gt;gen0&lt;/code&gt; wouldn't be &lt;em&gt;the same compiler&lt;/em&gt;; one would emit something the other doesn't. Byte-identity proves the language stands on its own floor: from here, Verbose no longer needs Rust to rebuild itself identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What almost stopped it — and why the diagnosis is the real story
&lt;/h2&gt;

&lt;p&gt;A first attempt crashed. Hypothesis: a 32-bit integer overflow in the emitter's counters. Reasonable, given the input size. &lt;strong&gt;It was wrong&lt;/strong&gt; — and it's written up as wrong in the commit, not quietly dropped. &lt;code&gt;ptrace&lt;/code&gt; + &lt;code&gt;objdump&lt;/code&gt; at the actual faulting address gave the real cause: &lt;strong&gt;arena exhaustion&lt;/strong&gt;. The faulting instruction wrote a node 40 bytes &lt;em&gt;past&lt;/em&gt; the 1 GiB arena. 10.3M nodes × 104 bytes = exactly the limit, crossed by 40 bytes. Not a wild pointer — a clean overflow at the boundary.&lt;/p&gt;

&lt;p&gt;Why does &lt;code&gt;gen1&lt;/code&gt; overflow when &lt;code&gt;gen0&lt;/code&gt; holds, on the same input? Two different memory models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  gen0 (Rust backend)          gen1 (self-hosted emitter)
  stack-passes records         arena-stores EVERY record AND variant
  reuses the space             never reclaims
  ~1.5M nodes at peak          ~84M nodes, ~8.7 GB RSS at peak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gen0&lt;/code&gt; works on a whiteboard it wipes as it goes; &lt;code&gt;gen1&lt;/code&gt; writes in a notebook it never tears a page from. Fine for a factorial; it overflows compiling the &lt;em&gt;whole compiler&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix — and the fix that waits, on purpose
&lt;/h2&gt;

&lt;p&gt;PR #102's fix is small: the emitter's ELF prologue now &lt;code&gt;mmap&lt;/code&gt;s 16 GiB with &lt;code&gt;MAP_NORESERVE&lt;/code&gt;. Reserving is free — like reserving a whole parking lot but paying only for the spots you actually use; only the ~8.7 GB actually touched costs RAM, and small programs touch almost none.&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;real&lt;/strong&gt; memory fix (dedupe an O(sites × program) recompute, reclaim the arena between procedures) is documented and &lt;strong&gt;explicitly deferred&lt;/strong&gt;. It would melt the 8.7 GB peak toward &lt;code&gt;gen0&lt;/code&gt;'s footprint. It would &lt;strong&gt;not&lt;/strong&gt; change the byte-identical output.&lt;/p&gt;

&lt;p&gt;That decoupling is the point. A classic trap in self-hosted-compiler milestones is coupling the &lt;em&gt;correctness proof&lt;/em&gt; to a &lt;em&gt;specific optimization&lt;/em&gt;. Here they're separate: &lt;strong&gt;byte-identity is a semantic property of the emitter, and it holds today; the memory footprint is a resource property, and it improves later.&lt;/strong&gt; The proof doesn't depend on the perf. Same discipline as everywhere in these projects: name the limit instead of hiding it — the 8.7 GB peak, the O(sites × program) recompute, all written on the same commit that ships the fixed point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it means
&lt;/h2&gt;

&lt;p&gt;The full chain the project was built toward is now demonstrated &lt;em&gt;in itself&lt;/em&gt;: &lt;code&gt;parse → verify (four proofs: lints, sound types, purity, termination) → interpret → emit → self-compile byte-identical → reproduce its entire self&lt;/code&gt;. The self-hosted checker verifying its own proofs, the self-hosted emitter emitting its own ELF, and the byte-identity gate between generations — three claims holding at once. A perfect photocopy of itself, twice over. Verbose stands on its own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full version (in French) on &lt;a href="https://arcker.org/blog/2026-07-12-le-point-fixe/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>rust</category>
      <category>programminglanguages</category>
      <category>assembly</category>
    </item>
    <item>
      <title>lithair 1.0 — I'm here, and yet</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:00:48 +0000</pubDate>
      <link>https://dev.to/arcker/lithair-10-im-here-and-yet-3jb6</link>
      <guid>https://dev.to/arcker/lithair-10-im-here-and-yet-3jb6</guid>
      <description>&lt;p&gt;&lt;strong&gt;lithair is a memory-first Rust web framework I'm building&lt;/strong&gt; — one compiled binary serves the frontend, runs the cluster, and &lt;em&gt;is&lt;/em&gt; the database, event-sourced, no external services when the working set fits. This post stands on its own. Eight weeks after v0.2, it hit &lt;strong&gt;1.0&lt;/strong&gt;. And I want to be honest about how that actually feels, because it isn't a story about code.&lt;/p&gt;

&lt;p&gt;The tag is cut. &lt;code&gt;v1.0.0&lt;/code&gt;. I should be celebrating, and instead I'm looking at the screen thinking there's still stuff left. That it isn't quite finished — even though it's tested end-to-end, soaked in production under real traffic, and survived its own upgrade without losing a single record. Objectively: solid. Subjectively: I don't quite believe it yet.&lt;/p&gt;

&lt;p&gt;It took me a while to understand why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the distrust is actually rating
&lt;/h2&gt;

&lt;p&gt;It isn't rating lithair. The numbers are there: the measured cluster envelope, backup proven by &lt;em&gt;actually restoring&lt;/em&gt;, eight roadmap gates closed one by one. The distrust is rating something else — what it is to put something you care about out there under your name.&lt;/p&gt;

&lt;p&gt;There's the small voice: &lt;em&gt;"an AI wrote it anyway."&lt;/em&gt; And it's true an AI generated a lot of the lines. But a framework isn't its lines — it's its decisions. Choosing to publish the cluster's envelope instead of claiming infinite scale. Making the admin secure by default. Naming the limits instead of burying them. Deciding, after two years, what to build and — more importantly — what to refuse. The AI typed. The idea, the calls, the standard — those are mine. Typing isn't authorship.&lt;/p&gt;

&lt;p&gt;And there's the question of whether it gets seen. But I have to be precise here, because this is where I could misrepresent myself: I didn't build it to break through. I built it for myself — to propose my vision and see it hold. People showing up would please me; it was never the goal, and I'm not waiting on it. Traction is a lottery anyway — timing, luck, network. Pinning two years of work to a draw I don't control would be handing my peace to chance, when the real reward is already here: the vision exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually there
&lt;/h2&gt;

&lt;p&gt;So I strip the feeling and look at the thing.&lt;/p&gt;

&lt;p&gt;Two years ago, lithair was an idea. One stubborn idea: &lt;em&gt;what if a single binary could serve the site, run the cluster, and be the database?&lt;/em&gt; Not three services, not six layers, not an infra to operate. A compiled binary, state in memory when it fits, history in an event store, security close to the runtime.&lt;/p&gt;

&lt;p&gt;Today that idea compiles. It runs. It's tested. And it holds this very site on its own — three sites, one binary, secure admin, hot-reload. Website, cluster, database, in the same executable. The all-in-one isn't a line on a slide anymore; it's a process with a PID that replays its log on boot and serves pages. That's not nothing. That's the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest about what remains
&lt;/h2&gt;

&lt;p&gt;A 1.0 isn't a finish line. It's "the floor is solid enough to build on." Optimizations remain — compute throughput, true zero-downtime on a &lt;em&gt;binary&lt;/em&gt; upgrade (today it's a brief graceful restart, which I wrote in the docs instead of pretending otherwise). But the starting base is the right one: a stable API contract, on-disk format unchanged since 0.13, and what's there is measured and documented — limits included. That's what I always meant by "production-ready": not "it does everything," but "the envelope is measured and written down." The load plate, bolted to the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no one can take
&lt;/h2&gt;

&lt;p&gt;The AI codes at the start. Then the rest — the standards, the judgment, the ecosystem built around it, the two years. lithair is just one exhibit of that, next to my other projects. And the idea from two years ago — an all-in-one that holds — is here. Really here.&lt;/p&gt;

&lt;p&gt;Maybe I don't quite believe it yet. But it exists now. And that, no one can take from me.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>A compiler written in its own language just emitted a standalone Linux executable</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:47:45 +0000</pubDate>
      <link>https://dev.to/arcker/a-compiler-written-in-its-own-language-just-emitted-a-standalone-linux-executable-3lld</link>
      <guid>https://dev.to/arcker/a-compiler-written-in-its-own-language-just-emitted-a-standalone-linux-executable-3lld</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building.&lt;/strong&gt; Its compiler proves properties about your code — like termination — and emits tiny, readable x86-64 machine code: no runtime, no GC, no libc. This post stands on its own (you don't need the rest of the series). What it's about: a compiler &lt;em&gt;written in Verbose itself&lt;/em&gt; reached the point where a single rule emits a &lt;strong&gt;standalone Linux ELF&lt;/strong&gt; that runs and prints its answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Honesty first: this lives on a working branch — &lt;strong&gt;not merged, not tagged.&lt;/strong&gt; It's verified (green test suite, real in-memory execution, a measured benchmark), but it isn't in a released version, and it covers a &lt;em&gt;sub-language&lt;/em&gt; (scalar arithmetic), not all of Verbose. More on that at the end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Two halves of a compiler
&lt;/h2&gt;

&lt;p&gt;A compiler has a half that &lt;strong&gt;reads&lt;/strong&gt; (text → tokens → tree) and a half that &lt;strong&gt;writes&lt;/strong&gt; (tree → machine code). The reading half is the usual story. The new part is the writing half — and the thing that makes it interesting here is that the code generator &lt;strong&gt;is itself a Verbose rule&lt;/strong&gt;. The Rust compiler underneath (&lt;code&gt;verbosec&lt;/code&gt;) only &lt;em&gt;runs&lt;/em&gt; it; the machine code is emitted by rules written in the language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow factorial all the way down
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@proof termination decreasing : n
rule fact (n : number [0, 20]) -&amp;gt; number {
  if n == 0 then 1 else n * fact(n - 1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the guardrails that survive all the way to the bytes: &lt;code&gt;@proof termination decreasing : n&lt;/code&gt; (the compiler &lt;em&gt;requires&lt;/em&gt; proof the recursion ends) and &lt;code&gt;[0, 20]&lt;/code&gt; (the declared domain of &lt;code&gt;n&lt;/code&gt;). The generator walks this tree and emits, per node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  n == 0              cmp ; sete            → boolean in a register
  if … then … else …  jz over_then ; &amp;lt;then&amp;gt; ; jmp end ; over_then: &amp;lt;else&amp;gt; ; end:
  n * fact(n - 1)     &amp;lt;compute fact(n-1)&amp;gt; ; imul
  fact(n - 1)         call &amp;lt;offset&amp;gt;         → it calls itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jumps carry &lt;strong&gt;computed offsets&lt;/strong&gt;: you only know a jump's distance once both branches are emitted, so the generator runs twice — measure, then write — the way you number a book's pages only after laying it out.&lt;/p&gt;

&lt;p&gt;The milestone is that &lt;code&gt;call&lt;/code&gt;/&lt;code&gt;ret&lt;/code&gt;. When &lt;code&gt;fact&lt;/code&gt; calls itself, it's a stack of plates you build up, then unwind while multiplying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  build up                   unwind (ret)
  fact(3) n=3                3 × 2 = 6   ◄── final
   └ fact(2) n=2             2 × 1 = 2
      └ fact(1) n=1          1 × 1 = 1
         └ fact(0) n=0 ─► 1  (base case)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first time a rule written in Verbose emits machine code that &lt;strong&gt;references itself&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a byte blob to a real executable
&lt;/h2&gt;

&lt;p&gt;Bytes in a buffer don't run on their own — you have to wrap them so Linux will launch them. That wrapper is the &lt;strong&gt;ELF&lt;/strong&gt; format, and the rule now generates the whole thing, like a ready-to-ship parcel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ELF64 header         ← "this is a Linux executable"
  LOAD program header  ← "map this into memory, r+x"
  _start               ← call main ; rax → itoa ; sys_write ; sys_exit
  code for main, fact  ← the computation itself
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No sections, no symbols, no linked library. A few hundred bytes. The last brick added &lt;code&gt;itoa&lt;/code&gt; (int → decimal ASCII) &lt;strong&gt;in machine code&lt;/strong&gt; — without it the binary computed &lt;code&gt;120&lt;/code&gt; but couldn't show it. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./verbosec examples/factorial.verbose &lt;span class="nt"&gt;--run&lt;/span&gt; lower factorial 5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; a.out
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x a.out
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./a.out
&lt;span class="go"&gt;120
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standalone — no &lt;code&gt;verbosec&lt;/code&gt; needed to &lt;em&gt;run&lt;/em&gt; the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest benchmark
&lt;/h2&gt;

&lt;p&gt;The long-standing question — "what does a binary built this way actually buy versus C/Rust/Go?" — finally has a &lt;em&gt;measured&lt;/em&gt; answer (Ubuntu 24.04, gcc 13.3, rustc 1.90, go 1.25.4):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  trivial (print a constant, exit)
              size        wall(ms)  RSS(kB)  syscalls
  verbose        512       0.19      352        2
  gcc        706 584       0.24      664       17
  rustc    3 871 984       0.57    2 112       62
  go       2 254 335       1.28    2 132      228

  fib(40) = 102334155  (deep recursion — raw compute)
              size        wall(ms)  RSS(kB)  syscalls
  verbose        635      ~713       352        2
  gcc        706 584      ~135       664       17
  rustc    3 872 832      ~224     2 216       62
  go       2 254 624      ~380     2 232      406
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it both ways. &lt;strong&gt;Wins, by construction:&lt;/strong&gt; ~1100–1380× smaller than &lt;code&gt;gcc -static&lt;/code&gt;, ~6000× vs rustc; two syscalls vs 17–228; RSS down 2–6×. Because there's no runtime — nothing to initialize, no global allocator, no dynamic library. These are &lt;em&gt;audit-defensible&lt;/em&gt;: there's almost nothing to audit. &lt;strong&gt;Loss, by design:&lt;/strong&gt; on &lt;code&gt;fib(40)&lt;/code&gt;, Verbose is ~5× gcc's wall time. The emitted blob has seen &lt;em&gt;no&lt;/em&gt; optimizer — no register allocation, no inlining, no constant folding past the AST. That was a decision (a direct, line-by-line auditable emitter, not an LLVM backend), and the compute cost is the price of that legibility — published, not hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't there yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's a &lt;strong&gt;sub-language&lt;/strong&gt;: scalar arithmetic (&lt;code&gt;+ − × ÷ %&lt;/code&gt;, comparisons, &lt;code&gt;and/or&lt;/code&gt;, &lt;code&gt;if/else&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, recursive calls). No strings, no I/O, no services, no TLS.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;branch-only&lt;/strong&gt;, not merged, not tagged.&lt;/li&gt;
&lt;li&gt;No optimization — deliberately deferred to keep every byte auditable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't "Verbose beats gcc." It's "here's exactly what Verbose buys, and what it costs, measured." The &lt;em&gt;read&lt;/em&gt; half and the &lt;em&gt;write&lt;/em&gt; half of a self-hosting compiler just met one notch closer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full walk-through (in French) on &lt;a href="https://arcker.org/blog/2026-06-21-une-regle-qui-ecrit-un-executable/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>rust</category>
      <category>assembly</category>
      <category>programminglanguages</category>
    </item>
    <item>
      <title>Every elevator has a load plate. Tests are supposed to kill fear, not feed it.</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 14 Jun 2026 14:37:31 +0000</pubDate>
      <link>https://dev.to/arcker/every-elevator-has-a-load-plate-tests-are-supposed-to-kill-fear-not-feed-it-56mc</link>
      <guid>https://dev.to/arcker/every-elevator-has-a-load-plate-tests-are-supposed-to-kill-fear-not-feed-it-56mc</guid>
      <description>&lt;p&gt;Every elevator has a small metal plate near the doors: &lt;strong&gt;8 persons — 630 kg&lt;/strong&gt;. It doesn't mean the car snaps at the ninth person. It means an engineer loaded it, measured it, and signed an envelope: &lt;em&gt;this is what I guarantee, within these limits, with margin.&lt;/em&gt; The plate isn't a confession of weakness. It's the exact thing that lets you step in without a second thought.&lt;/p&gt;

&lt;p&gt;I build &lt;strong&gt;Lithair — a small, memory-first Rust web framework&lt;/strong&gt;. This post isn't about its API; it stands on its own. It's about a sentence I've come to believe — &lt;strong&gt;DevOps without the test isn't DevOps&lt;/strong&gt; — and the reason for it is probably not the one you expect. It's not about laziness. It's about fear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plate is permission to stop worrying
&lt;/h2&gt;

&lt;p&gt;Look around and the load-bearing things in the physical world all carry their envelope stamped on the outside. A bridge has a weight rating. A cable has an amperage. A climbing carabiner has a kN number etched into the spine. None of those numbers are bureaucracy. Each one is permission: &lt;em&gt;you don't have to be afraid of this, here's exactly how far it's been taken.&lt;/em&gt; The plate converts a private unknown into a public, re-checkable fact, and the reward for that conversion is that nobody has to feel the unknown anymore.&lt;/p&gt;

&lt;p&gt;That is what a test is for. Not a gate. Not a ritual. A way to stop being afraid of your own system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I've actually seen
&lt;/h2&gt;

&lt;p&gt;I've spent years around enterprise software, and when real testing existed, it usually wore one of two shapes — and both, underneath, were shaped by fear.&lt;/p&gt;

&lt;p&gt;There were the &lt;strong&gt;dedicated performance teams&lt;/strong&gt;: a silo you handed the question to, where the schedule was measured in quarters, and where sometimes the software itself had to be bent to fit the benchmark rather than the other way around. Real rigor, but distant — the envelope lived in someone else's backlog.&lt;/p&gt;

&lt;p&gt;And there were the &lt;strong&gt;functional-test teams&lt;/strong&gt; who panicked the moment anyone touched anything. Change the color of a logo and you'd get a straight face and the words "non-regression testing." Not because they were unreasonable — because they had no fast, trustworthy way to know whether your one-line change broke something three modules away. So they did the only thing fear leaves you: they said &lt;em&gt;don't touch it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to be careful here, because it's easy to read this as contempt and it isn't. That fear is &lt;strong&gt;completely rational&lt;/strong&gt; when you can't verify. If I couldn't check, I'd guard the gate too. The tragedy isn't the fear. It's that the one thing built to dissolve it — automated tests, ideally backed by a dedicated tool — so often gets turned into another thing to be afraid of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests are the cure for that fear
&lt;/h2&gt;

&lt;p&gt;A test you actually trust does something quietly radical: it gives you back the right to change things. Change the logo color and the suite tells you, in seconds, that the checkout flow still works. Change the storage engine and the harness tells you the throughput still holds. The fear doesn't get managed or scheduled or escalated — it just &lt;em&gt;leaves&lt;/em&gt;, because the unknown it fed on is now a number on a screen.&lt;/p&gt;

&lt;p&gt;That's the whole point of the plate. You can step into the elevator because someone already took it to the edge and wrote down where the edge is. Automated tooling is how you stamp that plate cheaply enough to do it for everything, not just the one system important enough to rent a perf team. And &lt;strong&gt;DevOps — the actual discipline, not the job title — is made for exactly this&lt;/strong&gt;: turning "don't touch it" into "touch it freely, the tooling will tell you the truth."&lt;/p&gt;

&lt;h2&gt;
  
  
  So we built the bench
&lt;/h2&gt;

&lt;p&gt;Lithair's cluster had no performance team behind it and no QA gate in front of it. There was just the question — &lt;em&gt;what does it hold?&lt;/em&gt; — and nobody to hand it to. So the test became the work: we wrote our own stress harness and ran it until the numbers stopped being opinions.&lt;/p&gt;

&lt;p&gt;A three-node cluster. &lt;strong&gt;170,200 writes. Zero drops, zero panics, zero replication divergence&lt;/strong&gt; across all three nodes. Good numbers — but the one that actually matters isn't a success, it's a ceiling: &lt;strong&gt;~210–240 single-writes per second, per leader.&lt;/strong&gt; Past that wall, the latency you measure isn't instability — it's pure queueing against the ceiling. The mild decay over a long run (241 → 206 ops/s) traces to in-memory state growth, which is exactly what a memory-first design predicts — not a leak.&lt;/p&gt;

&lt;p&gt;We wrote all of it down. That's the plate. And the real payoff isn't the throughput number — it's that I can now change the cluster code and &lt;em&gt;not be afraid of it&lt;/em&gt;, because the harness will tell me the moment I leave the envelope.&lt;/p&gt;

&lt;h2&gt;
  
  
  A plate only kills fear if it's honest
&lt;/h2&gt;

&lt;p&gt;This is the part that's tempting to skip. A load rating that hides its caveats is worse than none, because it manufactures fresh fear later — the kind that shows up at 3 a.m. So the runbook names the ugly bits out loud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The leader election &lt;strong&gt;isn't Raft&lt;/strong&gt; — it's static, lowest live node ID wins. Always keep a node 0.&lt;/li&gt;
&lt;li&gt;There's a brief &lt;strong&gt;two-leaders window&lt;/strong&gt; when an old leader rejoins after a partition. Documented and non-blocking — committed split-brain is prevented elsewhere, by majority-ack on writes — but it's real, so it's on the page.&lt;/li&gt;
&lt;li&gt;A couple of endpoints are still stubs in this version. Named, not buried.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is comfortable to publish under your own name. All of it belongs on the plate. A caveat you wrote down is one nobody has to discover in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the test is the deliverable, not the feature
&lt;/h2&gt;

&lt;p&gt;The cluster code already worked before any of this started — replication, election, failover, all running. What it lacked wasn't capability. It lacked a plate, and you cannot stamp a plate you didn't measure.&lt;/p&gt;

&lt;p&gt;"Production-ready" without an envelope test is an adjective: you believe it or you don't, and belief is just fear wearing optimism. &lt;em&gt;With&lt;/em&gt; the test it becomes a falsifiable claim anyone can re-run — and a falsifiable claim is the only kind that lets a whole team relax. Strip the test out and what's left isn't speed or risk, it's the fear: the "don't touch it," the non-regression theater over a logo color, the silo nobody wants to disturb. DevOps was supposed to be the discipline that retires all of that. &lt;strong&gt;Without the test, you've kept the dashboards and the dread, and quietly thrown away the engineering.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You can put a plate on almost anything
&lt;/h2&gt;

&lt;p&gt;The format generalizes to nearly every load-bearing thing you ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an API's requests/sec before tail latency cliffs,&lt;/li&gt;
&lt;li&gt;a queue's depth before backpressure kicks in,&lt;/li&gt;
&lt;li&gt;a batch job's row count before it OOMs,&lt;/li&gt;
&lt;li&gt;a cluster's writes/sec before it's just queueing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's always the same shape: &lt;em&gt;here's what I hold, here's why it moves, here's where you leave the tested region.&lt;/em&gt; And the reward is always the same too — not bragging rights, but the simple ability for you and the people around you to stop being afraid of your own system. Sometimes the entire tax is one afternoon with a stress harness and the nerve to write down what you actually find.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full numbers, the cluster runbook, and the v0.13.0 release that earned the plate are &lt;a href="https://arcker.org/blog/2026-06-14-lithair-cinq-piliers/" rel="noopener noreferrer"&gt;in the companion post on arcker.org&lt;/a&gt; (in French).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>testing</category>
      <category>performance</category>
      <category>rust</category>
    </item>
    <item>
      <title>A program is a tree — building a Verbose compiler in Verbose</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Sun, 14 Jun 2026 09:11:25 +0000</pubDate>
      <link>https://dev.to/arcker/a-program-is-a-tree-building-a-verbose-compiler-in-verbose-4927</link>
      <guid>https://dev.to/arcker/a-program-is-a-tree-building-a-verbose-compiler-in-verbose-4927</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building.&lt;/strong&gt; Its compiler proves properties about your code — like termination — and emits tiny, readable x86-64 machine code: no runtime, no GC, no libc. This post stands on its own (you don't need the rest of the series). What it's about: I'm now writing a Verbose compiler &lt;em&gt;in Verbose itself&lt;/em&gt;, and this is the foundation brick — how you represent a program as data so a compiler can work on it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(English version of an article from my French series, originally on &lt;a href="https://arcker.org/blog/2026-06-07-un-programme-cest-un-arbre/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After cryptography, we take on something more vertiginous: &lt;strong&gt;a Verbose compiler written in Verbose&lt;/strong&gt;. The language starting to describe itself.&lt;/p&gt;

&lt;p&gt;Let's be honest up front — it matters. This is not (yet) verbosec compiling the entirety of its own source. What exists today is a &lt;strong&gt;complete front end&lt;/strong&gt; — tokenizer, parser, analyses, interpreter, type checker — written &lt;em&gt;in Verbose&lt;/em&gt;, for a &lt;strong&gt;toy subset&lt;/strong&gt; of the language. The whole thing compiled by verbosec to native machine code. Not an interpreted demo: a ~60 KB ELF binary that reads your program and tells you what's wrong with it. That's &lt;code&gt;examples/vexprparse.verbose&lt;/code&gt;: 102 concepts, 219 rules.&lt;/p&gt;

&lt;p&gt;We'll walk through it brick by brick. This chapter lays the foundation without which nothing else exists: &lt;strong&gt;how to represent a program&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why write the compiler in Verbose?
&lt;/h2&gt;

&lt;p&gt;The question deserves an answer, because it isn't just an exercise — it touches Verbose's whole thesis.&lt;/p&gt;

&lt;p&gt;Today, the compiler (verbosec) is written in &lt;strong&gt;Rust&lt;/strong&gt;. And some of the logic — certain primitives — is Rust that emits x86-64 directly, &lt;em&gt;with no Verbose source&lt;/em&gt;. The concrete consequence: to audit a Verbose binary, to &lt;em&gt;really&lt;/em&gt; understand what it does, at some point you have to read Rust. And trust that Rust — and whoever, or whatever, wrote it.&lt;/p&gt;

&lt;p&gt;That's precisely what Verbose refuses. The whole series rests on four words: &lt;em&gt;you don't trust, you verify&lt;/em&gt;. You read the source, declared and proven. If the path from source to binary runs through unverifiable Rust, trust leaks out there.&lt;/p&gt;

&lt;p&gt;Writing the front end &lt;em&gt;in Verbose&lt;/em&gt; moves that logic into the language itself: the tokenizer, the parser, the analyses become a &lt;code&gt;.verbose&lt;/code&gt; file, verified under Verbose's proof regime, then compiled native. The auditor reads Verbose, not Rust. The remaining Rust shrinks to a small, stable, &lt;strong&gt;trusted-once&lt;/strong&gt; base (the verifier). Per-binary trust moves from Rust to the proven source.&lt;/p&gt;

&lt;p&gt;And it's the ultimate dogfooding: a compiler is the hardest thing to express. If Verbose can describe its own front end, under its own proof regime, then the language isn't a toy — it holds up on the most demanding task there is.&lt;/p&gt;




&lt;h2&gt;
  
  
  From text to a tree
&lt;/h2&gt;

&lt;p&gt;A compiler can do nothing with flat text. &lt;code&gt;x + y * 2&lt;/code&gt;, to a human, is a string of characters; to a compiler, it's a &lt;strong&gt;structure&lt;/strong&gt; — a tree, where the multiplication nests under the addition (operator precedence):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  The text  "x + y * 2"  is really a tree:

            ( + )
           /     \
         x      ( * )
               /     \
             y         2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything starts there. Before evaluating, type-checking, or catching an undefined variable — you first have to turn the text into that tree. That's the AST (&lt;em&gt;Abstract Syntax Tree&lt;/em&gt;). And to build it, you need a way to represent a tree &lt;strong&gt;as data&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A tree is a recursive sum type
&lt;/h2&gt;

&lt;p&gt;This is where the earlier chapters pay off. A tree is declared in Verbose as a &lt;strong&gt;sum type&lt;/strong&gt; — a type that can take several shapes — some of whose shapes &lt;strong&gt;reference themselves&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;concept Ast
  variants:
    AstNum  of (value : number)
    AstVar  of (start : number, len : number)
    AstBin  of (op : number, lhs : Ast, rhs : Ast)
    AstNeg  of (inner : Ast)
    AstIf   of (cond : Ast, thn : Ast, els : Ast)
    AstCall of (callee_start : number, callee_len : number, args : ArgList)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;AstBin&lt;/code&gt;: a binary operation holds an operator, &lt;strong&gt;a left subtree &lt;code&gt;Ast&lt;/code&gt;, and a right subtree &lt;code&gt;Ast&lt;/code&gt;&lt;/strong&gt;. The type contains itself. That's the recursion of a tree: an addition whose two sides are, themselves, expressions. &lt;code&gt;AstIf&lt;/code&gt; holds three (condition, &lt;em&gt;then&lt;/em&gt; branch, &lt;em&gt;else&lt;/em&gt; branch). &lt;code&gt;AstNum&lt;/code&gt; and &lt;code&gt;AstVar&lt;/code&gt; are &lt;strong&gt;leaves&lt;/strong&gt; — they hold no other &lt;code&gt;Ast&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our example then becomes, exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  AstBin( + ,
          AstVar(x),
          AstBin( * , AstVar(y), AstNum(2)) )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tree drawn above, written as a value. And &lt;code&gt;a.b.c&lt;/code&gt;? &lt;code&gt;AstField(AstField(AstVar(a), b), c)&lt;/code&gt; — the nesting follows the structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  No pointers: an index arena
&lt;/h2&gt;

&lt;p&gt;One problem remains. Verbose has no heap and no pointers — one of the reasons its binaries are so small and so verifiable. So how do you build a tree of arbitrary size?&lt;/p&gt;

&lt;p&gt;The answer: an &lt;strong&gt;arena&lt;/strong&gt;. All nodes live in a single bounded space, and a node points to its children by their &lt;strong&gt;index&lt;/strong&gt;, not by a pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  concept_group VExpr [max_depth: 4096, max_nodes: 65535]

  arena:  [0]  AstVar(x)
          [1]  AstVar(y)
          [2]  AstNum(2)
          [3]  AstBin( * , lhs=1, rhs=2)    ← references indices 1 and 2
          [4]  AstBin( + , lhs=0, rhs=3)    ← the root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tree is built bottom-up: leaves first, then the nodes that link them. &lt;code&gt;max_depth: 4096, max_nodes: 65535&lt;/code&gt; aren't decorative — they're the &lt;strong&gt;static bounds&lt;/strong&gt; the verifier needs to prove everything stays finite. No dynamic allocation, no possible overflow, and yet a tree of any shape.&lt;/p&gt;

&lt;p&gt;In the same group live the tokens, the environments, and the diagnostics — all variants of &lt;code&gt;VExpr&lt;/code&gt;, all linked by index. One arena for the whole front end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this brick first
&lt;/h2&gt;

&lt;p&gt;Because everything else plugs into it. The tokenizer will produce &lt;code&gt;Token&lt;/code&gt;s in this arena. The parser will consume them to build &lt;code&gt;Ast&lt;/code&gt;s. The analyses will walk the tree to find your mistakes. The interpreter will descend it to compute a result. Without a way to represent the tree — recursive, bounded, verifiable — there's no compiler at all.&lt;/p&gt;

&lt;p&gt;And it's the direct payoff of what we built before: the recursion of &lt;a href="https://arcker.org/blog/2026-05-25-from-idea-to-binary/" rel="noopener noreferrer"&gt;chapter 1&lt;/a&gt;, the termination proofs of &lt;a href="https://arcker.org/blog/2026-05-26-proving-termination/" rel="noopener noreferrer"&gt;chapter 3&lt;/a&gt;. An AST is &lt;em&gt;the&lt;/em&gt; recursive structure par excellence — and Verbose represents it under the same guarantees as everything else: bounded, pointerless, proven finite.&lt;/p&gt;

&lt;p&gt;The program has become data. The next chapter builds it from raw text: the tokenizer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arcker.org/blog/2026-06-07-un-programme-cest-un-arbre/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;, where the full series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>programminglanguages</category>
      <category>ast</category>
      <category>rust</category>
    </item>
    <item>
      <title>TLS 1.3 without a library — a real browser does the handshake against Verbose machine code</title>
      <dc:creator>arcker</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:44:22 +0000</pubDate>
      <link>https://dev.to/arcker/tls-13-without-a-library-a-real-browser-does-the-handshake-against-verbose-machine-code-5c1f</link>
      <guid>https://dev.to/arcker/tls-13-without-a-library-a-real-browser-does-the-handshake-against-verbose-machine-code-5c1f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Verbose is a small experimental language I'm building&lt;/strong&gt; — its compiler proves properties about your code (like termination) and emits tiny, readable x86-64 machine code, with no runtime, no GC, no libc. This post stands on its own; you don't need any prior context. And it describes the wildest thing Verbose has done so far.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(English version of an article from my French series, originally on &lt;a href="https://arcker.org/blog/2026-06-04-tls-sans-bibliotheque/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When your browser shows a little padlock next to a URL, it has just held a cryptographic conversation with the server — the TLS &lt;em&gt;handshake&lt;/em&gt;. That conversation is usually handled by enormous C libraries: OpenSSL, BoringSSL, hundreds of thousands of lines nobody ever reads in full.&lt;/p&gt;

&lt;p&gt;This article is the &lt;strong&gt;capstone of the crypto arc&lt;/strong&gt; of the series. It shows the payoff: a real browser opens an HTTPS page served by a binary whose &lt;em&gt;every&lt;/em&gt; cryptographic transform — the key exchange, the identity signature, the bulk encryption, the hash — is &lt;strong&gt;machine code emitted by Verbose&lt;/strong&gt;. Not one line of OpenSSL. And the browser, the most demanding TLS client there is, can't tell the difference: it completes the handshake and renders the page.&lt;/p&gt;

&lt;p&gt;We build on &lt;a href="https://arcker.org/blog/2026-06-01-sha256-from-nothing/" rel="noopener noreferrer"&gt;SHA-256&lt;/a&gt; (chapter 1 of the arc) — it shows up everywhere in TLS. The details of each brick (AES, Ed25519) get their own chapters; here we pull up and watch the whole thing work.&lt;/p&gt;




&lt;h2&gt;
  
  
  A handshake, in three ideas
&lt;/h2&gt;

&lt;p&gt;Before any encrypted exchange, the browser and the server have to settle three things. That's all of TLS, one sentence each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agree on a secret&lt;/strong&gt; that no eavesdropper can guess, even after hearing the entire conversation (the key exchange).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove identity&lt;/strong&gt; — the server shows a certificate &lt;em&gt;and&lt;/em&gt; signs, so you know you're talking to the right party (the signature).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk encrypted&lt;/strong&gt; — once the shared secret exists, everything else is encrypted with a fast symmetric algorithm (the bulk encryption).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the wire, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  BROWSER                                 SERVER (Verbose binary)
      │                                          │
      │ ─── ClientHello ───────────────────────► │  "here are my algos + my key"
      │ ◄── ServerHello ─────────────────────── │  "here's mine"
      │            (encrypted from here on)       │
      │ ◄── Certificate + signature ──────────── │  "here's who I am, signed"
      │ ─── Finished ──────────────────────────► │
      │ ◄══ HTML page (AES-GCM encrypted) ══════ │  "Hello from Verbose TLS"
      │                                          │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of the three ideas needs a cryptographic ingredient. And that's where Verbose comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ingredients — and where the boundary runs
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters for Verbose's thesis. &lt;strong&gt;All&lt;/strong&gt; the cryptography is Verbose machine code. The host (in Python) only does plumbing: open the socket, frame the TLS messages, and draw the one random secret (&lt;code&gt;os.urandom&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ┌──────────────────────────────────────────────────────────┐
  │  HOST (Python)  : socket, TLS record framing,              │
  │                   os.urandom (the only secret input)       │
  ├──────────────────────────────────────────────────────────┤
  │  VERBOSE (x86-64 machine code) :                           │
  │     X25519          the key exchange                       │
  │     Ed25519 / P-256 the identity signature                 │
  │     AES-128-GCM     the bulk encryption                    │
  │     SHA-256 + HKDF  the hash and key derivation             │
  └──────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every line of the Verbose block was validated &lt;strong&gt;byte-for-byte&lt;/strong&gt; against a reference before being assembled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AES-128-GCM&lt;/strong&gt; against the NIST GCM Test Case 2 vector;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X25519&lt;/strong&gt; against RFC 7748;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ed25519&lt;/strong&gt; against the three RFC 8032 §7.1 vectors;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HKDF&lt;/strong&gt; against RFC 5869;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256&lt;/strong&gt;, as we saw in chapter 1, against &lt;code&gt;sha256sum&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing is &lt;em&gt;taken on faith&lt;/em&gt; because "it worked once." Each brick is checked against the official spec, in isolation, before it's stacked into the next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The only loop in all of TLS: the X25519 ladder
&lt;/h2&gt;

&lt;p&gt;Almost all the cryptography here is &lt;em&gt;unrolled&lt;/em&gt;: each operation finishes in a fixed number of steps the verifier can follow statically. There's exactly &lt;strong&gt;one&lt;/strong&gt; real loop — the X25519 key exchange, which climbs a "ladder" (the &lt;em&gt;Montgomery ladder&lt;/em&gt;) over 255 rungs.&lt;/p&gt;

&lt;p&gt;In Verbose, a loop is written as recursion, and — like in &lt;a href="https://arcker.org/blog/2026-05-26-proving-termination/" rel="noopener noreferrer"&gt;chapter 3&lt;/a&gt; — it has to &lt;strong&gt;prove&lt;/strong&gt; it terminates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rule ladder
  logic:
    ...
    out = if s.i == 0 then &amp;lt;the ladder result&amp;gt;
          else ladder(LadderState { ..., i: s.i - 1, ... })
  proofs:
    termination:
      decreasing : i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The real state carries ~50 fields — the field elements over 10 limbs — elided here.) The key point: &lt;code&gt;decreasing : i&lt;/code&gt; is a &lt;em&gt;compile-time-verified promise&lt;/em&gt;. The rung &lt;code&gt;i&lt;/code&gt; strictly decreases on every call, so the compiler proves the ladder stops. The 255 iterations run at runtime; the guarantee that they finish is established before the binary even exists. No general loop construct was added to do TLS — the proof tools from chapter 3 are enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The catch: a real browser doesn't compromise
&lt;/h2&gt;

&lt;p&gt;The first server signed its identity with &lt;strong&gt;Ed25519&lt;/strong&gt;. Against &lt;code&gt;openssl s_client&lt;/code&gt; — the usual test client — everything passed: &lt;code&gt;Verify return code: 0 (ok)&lt;/code&gt;, signature &lt;code&gt;ed25519&lt;/code&gt;. We could have declared victory there.&lt;/p&gt;

&lt;p&gt;Except a real browser refused. Flat: &lt;code&gt;illegal_parameter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why? In its &lt;code&gt;ClientHello&lt;/code&gt;, the browser announces the signature schemes it accepts (the &lt;code&gt;signature_algorithms&lt;/code&gt; extension). And browsers offer &lt;code&gt;ecdsa_secp256r1_sha256&lt;/code&gt; (P-256), &lt;strong&gt;not&lt;/strong&gt; Ed25519. RFC 8446 requires the server to sign with an offered scheme. Ed25519 wasn't offered → immediate rejection.&lt;/p&gt;

&lt;p&gt;That's &lt;em&gt;exactly&lt;/em&gt; the value of a real browser as a target: it imposes constraints a test client lets slide. So we had to build the whole &lt;strong&gt;ECDSA P-256&lt;/strong&gt; stack — GF(p256) field arithmetic, point add/double, scalar multiplication, the modular inverse, and ECDSA-P256-SHA256 signing (RFC 6979 deterministic nonce, DER encoding, &lt;em&gt;low-s&lt;/em&gt;) — validated against the RFC 6979 §A.2.5 vectors and &lt;code&gt;openssl dgst -verify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With a P-256 certificate, the browser stops saying &lt;code&gt;illegal_parameter&lt;/code&gt;. It completes the handshake. It renders the page: &lt;strong&gt;"Hello from Verbose TLS"&lt;/strong&gt;. The only remaining warning is the expected self-signed-certificate one. Not a crypto failure. The page renders.&lt;/p&gt;




&lt;h2&gt;
  
  
  The detail that's fun: smaller, for free
&lt;/h2&gt;

&lt;p&gt;Two recursive rewrites, the same week, at zero compute cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p256_ninv&lt;/code&gt;: &lt;strong&gt;11.2 MB → 84 KB&lt;/strong&gt; of native code (131× smaller), 8/8 byte-for-byte vs the unrolled version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x25519_finish&lt;/code&gt;: &lt;strong&gt;1.3 MB → 42 KB&lt;/strong&gt; (31× smaller), 266 field multiplications identical to the unrolled version. openssl handshake re-validated after the cure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same algorithm, same exact output, zero CPU overhead. The recursive path introduced for self-hosting (Phase A) turned out to be the right tool for collapsing huge unrolled cryptographic chains into compact machine code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 is not a toy: it's a stack of cryptographic primitives, inside a state machine, inside a wire format, with a public client — your browser — that won't compromise on any of it. Making it work end-to-end is empirical proof that the language and its compiler can &lt;strong&gt;express, verify, and run&lt;/strong&gt; a real protocol, not a demo.&lt;/p&gt;

&lt;p&gt;And the method is the message. The cryptography here isn't credible because a tool produced it — it's credible because it's confronted, brick by brick, with the RFC vectors, then with OpenSSL, then with a browser that forgives nothing. You don't &lt;em&gt;trust&lt;/em&gt;, you &lt;em&gt;verify&lt;/em&gt;. That's the whole difference, and that's all of Verbose: a binary small enough to read, proofs declared in source, and output checked against reality at every step.&lt;/p&gt;

&lt;p&gt;The arc began with a 12 KB hash in chapter 1. It ends with a browser rendering a page encrypted by a binary you can read line by line. In between, each brick still needs telling in detail — AES, Ed25519, the Montgomery ladder. Those are the next chapters.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arcker.org/blog/2026-06-04-tls-sans-bibliotheque/" rel="noopener noreferrer"&gt;arcker.org&lt;/a&gt;, where the full series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>cryptography</category>
      <category>programming</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
