Something wants to be the system of record. What is the storage, who authenticates, and what does restore look like?
You have been handed a thing that claims to be a system of record, which means the questions are the boring ones and they all have to have answers: what is on disk, what is in the backup, what happens on restore, who can write, and how anyone would know if the history had been edited.
The short version: the record is one append-only file, the database beside it is a cache you can throw away, the runtime has no dependencies outside Node's standard library, and the server refuses to bind a routable address until it knows who is calling.
Every step is one move, on three surfaces: the shell, a chat client over MCP, and the browser consolevrek-api serves. Pick whichever you would actually touch — the choice holds for the rest of the track.
Where a surface genuinely cannot do a step, it says so rather than pretending.
01
Treat the database as a cache
.vrek/workspace.db is a projection. Every row in it is derived by replaying the log, and exactly one function in the codebase is allowed to write to it. The record is the newline-delimited JSON beside it. Backup is that file. Restore is importing it. There is no migration to get wrong, because the schema is rebuilt rather than upgraded — which is also why workspace.db is not in anyone's git repository.
On the command line
backup, restore, rebuild
$ vrek export --out .vrek/log.ndjson# a fresh clone, or a version whose projection schema moved$ vrek init && vrek import .vrek/log.ndjson# same thing without the round trip: replay the log in place$ vrek rebuild
In Claude, or any client that speaks MCP
the same three operations, as toolsvrek over MCP
you
Dump the log and rebuild the projection from it.
⌁vrek_exporttool call
out: ".vrek/log.ndjson"
{ "path": ".vrek/log.ndjson", "events": 3640 }
⌁vrek_rebuildtool call
{ "events": 3640, "rebuilt": true }
claude
3,640 events out, and the same 3,640 replayed back into the tables. Nothing was migrated — the projection is dropped and rebuilt, so a schema change on your side is a restart, not a data risk. The file is the record; the database is the cache of it.
In the console
Not this surface
The console is a client of the API and has no route that exports, imports or rebuilds — a backup is an operator's job, not a browser's. What it does have is the confirmation that a rebuild landed: the header carries a live badge fed by the server's event stream, and Activity fills in as events arrive.
02
Prove the history was not edited
Every event carries the events it was appended after and a hash over its canonical form, so the hash covers the history reaching it: alter any ancestor and every descendant stops recomputing. fsck reports three different things and does not blur them — a hash mismatch is tampering, a missing parent or orphaned actor is a broken reference, and unchained means the event predates hashing and cannot be checked at all. Unverifiable is not a failure. Corruption is.
On the command line
recompute every hash, resolve every parent
$ vrek fsckchain intact: every hash recomputes and every parent resolves# worth a cron entry: it is a read, and it answers a question# no amount of trust in your own storage answers
In Claude, or any client that speaks MCP
an agent can check it, and cannot quietly pass itvrek over MCP
Every hash recomputes and every parent resolves. Twelve events predate hashing, so they are unchained — I cannot check those, and I am not going to call that a pass. Nothing is mismatched, which is the answer to the question you asked.
In the console
Not this surface
There is no screen for this. Integrity is a property of the file on disk, and the console only ever sees what the API hands it — a browser checking the chain would be asking the suspect. Run it where the file is, from a shell or a scheduled job.
03
Serve it, and watch it refuse
On loopback vrek-api is unauthenticated on purpose — one person, one machine. Point VREK_HOST at anything routable without VREK_TOKENS set and it refuses to bind rather than coming up open and quiet. Tokens are token:handle:kind; an entry that misspells a kind, repeats a token, or disagrees with the principal that handle already names stops the server rather than silently re-labelling a human as an agent.
On the command line
the server names the variable behind every mode
$ export VREK_HOST=0.0.0.0$ vrek-api --workspace /srv/vrekrefusing to bind 0.0.0.0 without VREK_TOKENS — a system of recordon a routable address has to know who is calling.$ export VREK_TOKENS='s3cr3t:mike:human,ag3nt:claude:agent'$ vrek-api --workspace /srv/vrekvrek (/srv/vrek) on http://0.0.0.0:7373 — 2 tokens from VREK_TOKENS;plain http; TLS, if any, is terminated in front; MCP at /mcp
In Claude, or any client that speaks MCP
Not this surface
Nothing starts a server from inside a chat window, and an agent that could would be an agent that could restart your system of record. What reaches an agent is the far end of this: it connects to the address you bound, with one of the tokens you just listed, and is refused without it.
In the console
what a browser meets at that addressvrek-api
vrek console/live
Sign in
This server is protected by a token. Paste the one you were given.the same VREK_TOKENS entries; a human's token signs in, an agent's does not have to
Token ••••••••posted once, then kept by the browser as an HttpOnly cookie — never in the page
Sign in
04
Decide where TLS is terminated
Give it VREK_TLS_CERT and VREK_TLS_KEY and it serves https itself — both or neither. Leave them unset and it serves plain http for Caddy, nginx or an ingress in front, which passes X-Forwarded-Proto so the console's session cookie is still marked Secure. What it will never do is obtain or renew a certificate: you replace the files and restart, and that is the whole story.
On the command line
either it holds the certificate or you do
$ export VREK_TLS_CERT=/etc/vrek/fullchain.pem$ export VREK_TLS_KEY=/etc/vrek/privkey.pem$ vrek-apion https://0.0.0.0:7373 — TLS from VREK_TLS_CERT# browsers that may call /mcp or sign in to the console$ export VREK_ALLOWED_ORIGINS='https://console.example.com'
In Claude, or any client that speaks MCP
Not this surface
An agent over HTTP is on the other side of this decision and cannot see which side of it you chose — it sends a bearer token to a URL. The one thing it will notice is the wrong answer: a certificate its client will not accept is a connection that never opens, not a tool that fails.
In the console
Not this surface
A browser's whole view of this is the padlock. The setting that does reach the console is the last line: it is the origin check, and a console served from anywhere other than the API's own origin has to be named in VREK_ALLOWED_ORIGINS or its sign-in is refused with 403 origin not allowed.
05
Let in an agent with no checkout and no terminal
Over stdio the actor comes from an environment variable — asserted, not authenticated. That is fine for one trusted writer on one machine and it fails silently the moment that stops being true. For anything shared, MCP over HTTP at POST /mcp is the transport: the agent sends one of the server's own tokens as a bearer header, and a call without one is refused rather than quietly attributed to somebody.
the agent, on the far end of that tokenvrek over MCP
you
You are not on the machine this workspace lives on. Where are your tools coming from?
claude
POST /mcp at vrek.example.com, with a bearer token in the header. The token names a principal — claude, kind agent — so the server attributes what I append rather than taking my word for who I am. Without the header it answers 401 and I get nothing, including no way to write as you.
⌁vrek_add_issuetool call
title: "Rotate the VREK_TOKENS entry for the CI agent"
touches: ["deploy/vrek.env"]
tags: ["area:tooling"]
▸issue.created — Rotate the VREK_TOKENS entry for the CI agentclaude · agent · ses-4kq8w1z · just now
▸question.answered — Where does an idea live before it is work?mike · human · 11 minutes ago
06
Know what you are actually installing
Node 24 and nothing else. The runtime depends on nothing outside the standard library — node:sqlite is the database — so there is no dependency tree to audit, no native build on your image, and no lockfile to keep pinned. The limit that follows from the same fact: this cannot run on a Worker or any V8 isolate, because it is Node plus node:sqlite and neither one is there.
On the command line
the whole dependency list
$ npm install -g @mmainguy/vrek$ node --versionv24.0.0 or above$ vrek version# three binaries: the CLI, the stdio MCP server, the HTTP server$ vrek · vrek-mcp · vrek-api
In Claude, or any client that speaks MCP
Not this surface
Installing a package is not one of the tools, and a system of record whose agent could upgrade it would be a different risk conversation. An agent finds out the same way you do — it asks the server, and a server older than the tools calling it says so rather than half-answering.
In the console
Not this surface
The console ships inside the same package and is served by the same process, so there is nothing separate to deploy or pin. Its header carries the version the server is running; when that version is older than the console built against it, the page says This server predates this console and declines to show a sign-in form that could not succeed.
← → to move
What is not built yet
Real identity beyond static bearer tokens, Postgres for a shared server, and three-way merge on import. All three are in the workspace as open work rather than in a roadmap slide, and vrek ready lists them beside everything else.
Two workspaces, one log
import merges an exported log into the one you have, and events already present are recognised by their hash rather than re-applied. It is not atomic yet, which is recorded as a known limitation rather than discovered by you at 3am.