Verbose was built on a single bet: the source can describe the entire behavior of the running binary. Not “approximately.” Not “the business logic plus whatever the framework does.” Everything that touches the world.
Most languages defer parts of behavior to runtime libraries, framework code, or operating system facilities. Those parts work, but they’re not in the source. If you want to know what the program actually does on the wire, on disk, in memory — you have to read the framework too. Often you don’t, and your source ends up describing a fraction of what runs.
Phase 8 closed one of those fractions: the audit trail.
The standard pattern
A typical service that needs to log decisions has two code paths that need to agree.
The first is the response path: receive a request, run the rules, build a response, send it. The user sees the result.
The second is the audit path: write a record somewhere durable, with a timestamp, the rationale, the inputs, and what was returned. This is usually a middleware, a side effect, or a goroutine firing off a log message. The auditor sees this record.
Both paths are usually written separately. In a code review, both look correct. In production, six months later, after a couple of refactors, they often disagree on small things. A field gets renamed in the response and not in the log. A new code path skips the audit middleware. The user sees one thing; the audit record describes another.
Most of the time this divergence is innocent. It’s still divergence — and it’s the gap that matters when someone later asks “what did the system actually return to this user?”
What I tried
verbose’s Phase 8 makes the audit a property of the service declaration, not a separate path. The log block sees the response the handler built, by name:
service audit_service
listen:
protocol : http_1_0
port : 18895
max_request: 2048
handler: decision_handler
log:
append_file "/tmp/audit.jsonl"
concat("{\"reason\":\"", resp.body, "\",\"status\":", resp.status,
",\"ts\":", req.timestamp, "}")
on_error: abort
When the handler returns, the log fires before the response leaves the socket. The reason field in the JSONL line is the same value the client gets in the response body. Not a copy. Not a derivation. The same value, read from the same place.
If the audit write fails — disk full, permissions wrong, anything — the service exits. on_error: abort is fail-closed: when the audit can’t be written, the service refuses to serve. The audit gap and the service gap collapse into the same gap.
Three requests, three lines:
$ curl http://localhost:18895/allowed # 200 "access granted"
$ curl http://localhost:18895/refused # 403 "access denied"
$ curl -X DELETE http://localhost:18895/x # 405 "method not allowed"
$ cat /tmp/audit.jsonl
{"reason":"access granted","status":200,"ts":1761480123}
{"reason":"access denied","status":403,"ts":1761480127}
{"reason":"method not allowed","status":405,"ts":1761480131}
Each audit line matches its request bit-for-bit. Not because anyone was careful. Because there is only one place those values exist.
Where this property pays off
One specific case is regulatory: the EU AI Act, in Article 86, gives individuals the right to an explanation when an automated decision affects them. The current standard for satisfying that requirement is some flavor of structured audit log, often shipped through a separate pipeline. As long as the response and the audit are two code paths, divergence is possible — and a regulator who looks closely will find it.
The Phase 8 pattern collapses those two paths into one declaration. The audit record is, by construction, what the user got. The compiler enforces it. The regulator, reading the source, doesn’t have to take anyone’s word for what was returned.
But the property isn’t only about compliance. Anywhere you need to answer “what did the system return to this user?” with confidence — billing systems, safety-critical decisions, content moderation outcomes, customer support traceability — the same shape applies.
Why this matters to me
The original verbose pitch is to remove layers between intent and machine. Each phase shrinks the surface where things can be true in the source but ambiguous in the binary. Phase 7 covered the request-response loop. Phase 8 covers the side effect.
Every time the source covers more, “what does this service do” gets a cleaner answer. Not because anyone is more careful, but because there’s less material left where carelessness can hide.
What this is not
It’s not “the AI Act is solved.” It’s not. Bias detection, dataset documentation, explainability of model decisions — those need different tools.
It’s not “every service should write its compliance behavior in verbose.” That’s a wild ask. Most teams should keep their existing stack.
It’s: when divergence between “what the user got” and “what was logged” is the failure mode you actually care about, source-level enforcement closes that specific gap.