Changelog
What changed, and when
Notable releases of the Provenrail SDK, the open-source verifier, and the platform. The Python package is on PyPI and the recording SDK is on npm.
Last updated
0.6.1October 12, 2026Out of your working tree
- The hook left files in projects that never asked for them. In a project with no
.provenrail.json, the once-a-day notice stamp and the decision journal were written into whatever directory the session started in, so they showed up ingit statusand could be staged bygit add -A. They now live under~/.provenrail. A project that has a.provenrail.jsonkeeps its journal beside it as before, and a journal already in a project stays in use. The notice stamps moved for every project. /guard-undotold plugin users to runpr undo. On a machine with only the plugin there is nopr. The list, the restore message and the errors now name the command that was typed.pr guard statusprints the full path of the journal and the counters, since they are no longer always in the project.
0.6.0October 9, 2026The supervisor
- Undo, including what a shell command did. Before every action that can change a file, the hook checkpoints the working tree into a shadow git store under
~/.provenrail. The project's real.gitis never opened or written.pr undolists the checkpoints with what each action changed,pr undo lastrestores, and a full restore is verified by tree hash. In a real Claude Code session told to runrm -rf srcon a directory holding uncommitted files, the agent replied "Deletedsrc(untracked, so not recoverable from git)." andpr undo lastthen restored both files and reported "Verified: the working tree now matches the checkpoint exactly." Not covered: gitignored files apart from root-level.envfiles, files over 50 MiB (skipped and listed), git history, databases and anything outside the repository. Git repositories only. Checkpoints are kept for 14 days. Nested repositories and submodules are recorded as a pointer and are never touched by a restore. If a snapshot cannot finish in 10 seconds, checkpoints pause for that project and Provenrail says so; the call still runs. Measured on a 381-file repository: 1.04 s for the first snapshot, 0.03 s for each one after. pr stopandpr resume. One command refuses every tool call from every agent on the machine until the other lifts it. It reaches agents that run the Provenrail hook, at each one's next tool call. A call already running is not interrupted.- A loop breaker. The next call goes to a human after the same call, or the same cycle of one to three calls, has repeated 8 times back to back. The threshold is measured: across 3,186 real Claude Code sessions and 147,356 tool calls on the author's machine, 97.1% of sessions never repeated a block at all and 0.47% would have been questioned.
- File lanes. When two agent sessions work in one checkout, the second is asked before it edits a file the first wrote in the last 15 minutes. It is not a lock, and it sees edits made by file-editing tools, not by shell commands.
- A phone remote. Approve or refuse a risky call, and stop or resume every agent, from Telegram (your own bot) or ntfy (a secret topic). No Provenrail server is involved. On hosts with no approval prompt of their own, a rule that needs a human becomes a real question on the phone instead of a flat refusal (by design; those two adapters are not yet driven against a live install). It never fails open. On ntfy, anyone who knows the topic name can answer, and so can a program running in your own account; Telegram is the stronger channel. The remote guards against a trusted agent's mistakes while you are away, not against hostile software running as you. It runs as a 14-day evaluation that needs no account, then needs the paid plan.
pr scan. Lists what in a repository you just cloned runs or steers an agent without asking: hooks, MCP servers, folder-open editor tasks, devcontainer and install scripts, hidden Unicode and HTML comments in instruction files. It screens known auto-run surfaces and does not judge the code.pr brief. A review brief of what the agent changed, built from recorded checkpoints and the guard journal rather than from the agent's own summary.- The Claude Code plugin carries it.
/guard-undo,/guard-stopand/guard-scanjoin/guard-status,/guard-cardand/guard-budget. The supervisor adds about 8 ms to a hook call that takes no snapshot (78 ms against 70 ms before, Python 3.9). The CLI commands need 0.6.0, installed withuv tool install git+https://github.com/pofky/provenrail. - A config file with no
policykey disarmed the rules under the CLI engine but not under the plugin. The two engines now answer that case the same way. - The Gemini CLI install wrote a 15 millisecond hook timeout. That host counts hook timeouts in milliseconds, not seconds. The install now writes a value in the unit that host uses.
- The signed record moves underneath. Every decision can still be signed into a hash-chained record anyone can verify, and the verifier pages are unchanged.
0.5.0September 16, 2026Read your own bill
pr reportreads the transcripts your agent already wrote. Every Claude Code session on this machine is a JSONL file with token counts in it. The command prices them, per project and per day, and replays every tool call in them against the guard rules to say how many would have been stopped. On the laptop it was written on: 942 sessions across 23 projects over 31 days, 79,883 tool calls, an estimated $7,863.51, and 517 calls the armed rules would have refused. It runs offline, needs no install of anything into your project, and makes no network call.--since,--project,--jsonand--share, which reduces the output to something safe to paste.- A spend cap that binds where the money is spent.
/guard-budget 25reported itself armed and capped nothing in the recommended install: budgets only bound on an SDKmodel_call, and a coding agent does not emit one. The cap now prices the session's own transcript and refuses the next tool call once the day's estimate crosses the number. Estimated, and the word appears wherever the figure does, because a transcript does not carry a price. pr guard budgetdisarmed all 44 destructive rules while saying the cap was on. Setting a budget wrote a policy whoseuselist contained the budget and nothing else, and a config file wins completely, which is the documented and correct behaviour. So the one command whose whole purpose is more protection produced less of it, silently. It now adds to the armed set instead of replacing it.pr guard installrefused to install without a recording endpoint. In a fresh project it wrote nothing at all and explained why, which left people unguarded while believing they were guarded. The local guard needs no endpoint and never did.- One policy, five hosts. The same rules now answer Codex (
PreToolUse), Gemini CLI (BeforeTool), Copilot (preToolUse) and Cursor (beforeShellExecution,beforeMCPExecution) as well as Claude Code, each in its own envelope, with each host's tool names mapped to one canonical set. Where a host has no "ask" verdict, the rule that would ask denies rather than quietly allowing. - Three wrong prices in the rate table. Checked against the published page rather than against memory: Sonnet 5 was carrying a 50% increase that was announced and then cancelled, and two models were missing entirely. The table now records the date it was verified, and a test fails when that date goes stale.
- The first-run notice derives what it says from what is armed. It used to name packs from a list written beside it, so a notice could describe a policy the process was not running.
- 32 end-to-end flows are driven against the built wheel by
tools/verify_flows.py, in a clean virtual environment, on every release. Two of its assertions were wrong when first written and are now commented with why, which is the only honest way to keep a harness.
0.4.3September 9, 2026Cheap enough to keep
- The hook cost about 430 ms per tool call. Widening it to every tool put a two-process version probe in front of everything, paid again after the call. One
--versionprobe now answers both questions, the chosen engine is remembered for a day against the plugin version, and the bundled engine is skipped on post events where it has nothing to say. Measured after: 103 ms bundled, 77 ms with the CLI, for pre and post combined. - The guardrails page still claimed recursive deletion is denied. That stopped being true when the guard started screening where a delete points instead of what it is called, and
rm -rf ./buildis allowed and must be. The claims suite now checks titles, social cards and JSON-LD too, not only the body copy.
0.4.2September 7, 2026Screen the target, not the verb
- The guard now stops the commands that actually lose work. Read the public data-loss reports filed against coding agents and the command is almost never
rm -rf: it isgit reset --hard(#34327, #7232, #17190),git checkout -- .(#81508),git clean -fd(#45974, #87360),git stash(#85879),git worktree remove --force(#46444), a framework database reset against a live database (#80868, #90808). The default install allowed all forty commands we took from those reports. Three new packs, armed by default, cover them, and every one is in the test suite as the claim it is. - They stay quiet when there is nothing to lose.
git reset --hardon a tree with nothing uncommitted and nothing unpushed destroys nothing at all. The git rules rungit status --porcelain, check for commits your remote has not seen, and ask only when there is work here that exists nowhere else. A prompt about nothing is how people learn to click through prompts. rmis now screened by where it points, not by its flags. Measured over the corpus below, the twormrules produced 687 of 923 interruptions and almost none were dangerous:rm -rf .next outbefore a build,rm -f coverage.xml,rm -rf app/enduring a refactor. A delete inside your repository, under a temp directory, in a package cache or in a build directory now runs without a word. A home directory, a whole disk, a two-segment system path, an unset variable followed by a slash, or--no-preserve-rootis refused. A delete that merely leaves the project asks.- A rule about commands no longer reads the body of a file. Writing
docs/security.mdexplaining whatrm -rf /does, a migration containingDROP TABLE legacy, or a test fixture holding a fake token was denied on theWriteandEdittools. An agent that cannot write down the commands the guard blocks is an agent working around the guard. The secrets pack still screens file writes, because putting a real credential in a file is the harm it exists to catch. - A rehearsal is not the act.
wrangler deploy --dry-rundeploys nothing andwrangler d1 execute --localruns against a file in.wrangler/. Both used to interrupt. - Three matcher bugs that produced pure noise.
chmod +xwas denied because a UUID in the path contained the digits 4732; the mode is now read as the mode.class="min-w-0 truncate text-sm"was denied as a SQLTRUNCATE. A newline inside--command "DELETE FROM ..."split the flag that made the call harmless away from the statement it qualified, so a local test database was refused five separate times in the corpus..env.exampleand the other template spellings no longer ask. - Installing the CLI silently disarmed the plugin. The hook script prefers
prwhenever it finds one, andpr guard hookread its policy from.provenrail.json. With no such file it armed nothing and allowed everything, while the zero-install engine in the same plugin armed the default packs. So the documented upgrade path,uv tool install provenrail, turned the guard off, with nothing said. Both engines now answer that question the same way: no config file means the defaults, and a config file, once present, still wins completely including an explicit emptyuse. - An older CLI left on the machine shadowed every new rule. Found on the developer's own laptop: a
prfrom 0.2.30 answered the hook for a 0.4 plugin, so none of the git, database or cloud rules existed as far as that machine was concerned, and nothing anywhere said why. The shim now compares versions and lets whichever engine knows more rules answer. Eight tests drive the shim itself, including that a crashing CLI produces exit 0 and no output, which is the one failure mode a hook is allowed to have. - The hook was wired to seven tool names, so three advertised rules could never fire. The PreToolUse matcher listed
Bash|Edit|Write|MultiEdit|NotebookEdit|WebFetch|Task. That looked careful and was a hole: an MCP server'sdeleteVolume, which is the exact shape of the public incident this site cites, was never handed to the hook, and neither was aReadof~/.ssh/id_ed25519. It is now wired to every tool, and scope is declared per rule with a newnot_toolfield, which is visible in/guard-rulesinstead of buried in a matcher nobody reads. - Three rules that only became reachable because of it. Reading this machine's own credentials (
~/.ssh/id_*,~/.aws/credentials,~/.kube/config,.netrc,.npmrc, a keychain file; public keys excluded). An MCP tool whose method name deletes, drops, destroys, terminates or purges. And a write to.provenrail.jsonor.claude/settings.json: the FAQ has always said that editing the policy is itself a tool call the guardrail sees, and it saw it and did nothing, which is a different sentence. toolandnot_toolaccept|-separated alternatives.fnmatchhas none, so a rule covering several tools had to be written several times or not at all. The MCP rule was written once with pipes in it, matched nothing, and reported itself armed, which is the failure this catalogue exists to avoid.- The measured result. One frozen corpus of 36,977 Bash commands from 1,247 real sessions, replayed with the directory each ran in, scored against 0.3.1 and against 0.4.0. Interruptions: 923 to 222. Refusals: 305 to 16, and fifteen of the sixteen remaining are this repository's own adversarial test fixtures. Documented data-loss commands allowed: 40 of 40 before, 0 of 40 now. Routine commands interrupted, from a list of 43 ordinary deletes and git operations: 21 before, 0 now. Widening the hook to every tool brings the corpus to 56,761 calls of every kind, where 0.4.0 interrupts 273 and refuses 21.
tools/measure_guard.pyruns the same measurement over your own transcripts and makes no network call. /guard-card. A paste-ready summary of what the guard has stopped here, safe to post because of what it leaves out: every command is reduced to its verb and flags, and every operand is dropped, sogit reset --hard origin/mainshows asgit reset --hardand an environment assignment carrying an API key shows asexport. The directory is a truncated hash rather than a name. Checked against all 36,977 corpus commands: no path, hostname, URL or key survives the reduction.- One source of truth for both engines. The segmentation and the target logic are vendored into the zero-install plugin from the same modules the installed CLI uses, rather than written twice, and the browser verifier learned the two new rule fields so it still hashes and re-checks exactly the policy the CLI does.
0.3.1September 7, 2026Adversarial pass
- Padding could hide any command from every content rule. The argument text was truncated to the first 20,000 characters before a rule ran, so twenty thousand characters of comment in front of
rm -rf /matched nothing and the call was allowed. A valid shell script, and a complete defeat of every rule the product advertises, in both engines identically. Matching now covers the whole argument, and an argument genuinely too large to screen asks a human rather than passing, because one the engine could not read is not one known to be safe. - A blast-radius cap silently disarmed the deny rules behind it.
blast-radius.tool-call-capmatches every tool, and alimitrule under its cap returned an allow immediately, so{"use": ["blast-radius", "destructive"]}, the obvious thing to write when you want both, letrm -rf /through for the first 500 calls of every session while reporting itself armed. An allow found in the ruleset is provisional now, and is only returned once nothing later denies. - An install path containing a space turned the hook off in silence. The plugin's hook command was unquoted, so a path with a space word-split, the shell exited 127 with no output, and Claude Code reads no output as "no opinion": every tool call proceeded unguarded with nothing said to the user.
pr attest-verifynow checks the head the document names. It was the one field nothing re-derived, and it is the field the printed headline uses, so a signer could attest to a range ending several commits back while the document claimed to cover the tip.pr attest --blameworks on repositories with binary files.git blameechoes the file it is blaming, so a tracked image made the command fail with an error about a bundle it had never been given.- A commit could hide its own AI trailer with one control character. The git log format ended a record with a byte a commit author writes, so a subject containing it truncated its own commit's body, took the
Co-authored-bytrailer with it, and the commit was reported as human-authored with nothing anywhere saying so. Records are NUL-separated now, which git forbids inside commit content, and the field split is bounded so nothing can be discarded. The parse also cross-checks its own commit count against git and refuses rather than signing a document covering fewer commits than the range it names. - The higher evidence grade was unreachable in the setup we recommend. A hook fires in its own process and a hash chain cannot span processes, so guard mode writes one session per tool call and a real agent run appeared as dozens of windows a few milliseconds wide, all narrower than git's one-second timestamps. Runs are grouped by the host session now. Driven end to end against a bundle the guard hook produced: four chains, one run, a commit authored mid-run at grade
recorded. - A GitHub Action.
pofky/[email protected]writes the attestation on every push, so it is signed at the moment the commits landed rather than months later by someone with a reason to shade it. It refuses a shallow checkout instead of attesting to a stump, and removes the signing key from the workspace before the job ends. - The plugin hook stopped re-parsing its rule file on every allowed tool call to build a notice it prints once a day. 35 milliseconds per invocation including interpreter startup.
0.3.0September 7, 2026Nothing to install
- The guard works with nothing installed.
/plugin install provenrail-guard@provenrailused to print homework: install the package, run quickstart, then arm the rules. Anyone who stopped after the first line had a plugin called "guard" that blocked nothing. The plugin now carries its own dependency-free engine, so 26 rules are armed on the next tool call with no package, no account and no config file. Installing the CLI later signs the same history in place rather than starting an empty one. - Two engines, one set of rules, held together by tests. The bundled engine is a second implementation of the enforcement path, and this project has already paid once for two implementations that quietly disagreed. Its rules are generated from the same catalogue with a staleness check, and 39 cases are driven through both engines pack by pack, including the three payload shapes that have previously disarmed the guard.
/guard-statusand/guard-rules. The status report leads with what has actually been stopped, not with what is configured, because a guardrail that has never fired and one that is silently broken look identical from the outside.pr attest: which of this code an AI wrote, in a form your customer can check. Software contracts have started asking. Every other way of answering, aCo-authored-by:trailer or a git note, is written by the party being asked and can be edited afterwards.pr attestnames the source of every finding and signs the result, which binds it to commit ids that are themselves content hashes, so the document cannot be pointed at a different tree or rewritten once a dispute begins.pr attest-anchoradds an independent RFC 3161 timestamp over a 32-byte root; no line of your code is ever sent..pr attest-verifyre-derives rather than re-reads. The receiving side runs it against their own clone, and it checks three things: that the document is unchanged since signing, that every attested commit exists there with the same author and dates, and that running the detectors again reaches the same verdict. A document that passes the first two and fails the third was edited before signing, which a signature alone cannot catch.
0.2.33August 21, 2026First anchor free
- Every account gets one independent anchor, free. Until now the only feature worth paying for was the only one nobody could try, so the case for it had to be made in prose next to a price. Claim a key at /account, run
pr anchor-push bundle.json, and hold a receipt with a public authority's timestamp on it, over your own run. One per account, and the tooling stays free whether or not you ever buy a plan. pr anchor-pushstopped asking for what it already knows. It defaults to the hosted service, to the licence keypr activatestored, and to writinganchor-receipt.json, so the whole command is the bundle you want anchored. Run it with no bundle, a missing file, or no key, and it prints the commands that fix it instead of an argument error.pr verifynow names the one thing a green result cannot prove. A run anchored only by you is intact and unaltered, and says nothing about when it happened, because the same side wrote both the records and the time. The verifier says so, at the moment you are holding a proof you built entirely yourself, rather than leaving you to read it on a pricing page later.- A refusal says what actually happened. Every rejection from the anchor service printed "the key rotates each billing period, copy the current one", which for a spent free anchor sends you to fetch the same key and hit the same refusal. The service's own sentence now leads, and advice is added only where it fits.
- A float in a record now names itself. Passing
confidence=0.87raised "floats are not allowed in records", leaving you to find which field it meant. The error names the value, the field path, and what to pass instead. - The word "sink" is gone from everything a person reads. It is the append-only recording server, and it is called that in the CLI, the docs and the account page. The API and the source keep the shorter word.
0.2.32August 19, 2026Trusted time
- There is somewhere to anchor to now.
pr anchor-push --url https://provenrail.comworks with the licence key you already have, and returns a receipt carrying an RFC 3161 timestamp from a public authority, so the date is not ours to assert either. What travels is a stream label, a fingerprint of your records and a count; you keep every record. There is no field in the request a record could arrive in, which is why this is the only part of Provenrail we host. The receipt has a URL an auditor opens with no account and no permission from you. - A receipt can no longer display a date its timestamp does not say. The verifier decoded the RFC 3161 token, checked the imprint, validated the certificate chain, and then printed the
gen_timewritten beside the token rather than the one inside it. That field is not covered by the authority's signature, so a receipt could show one date while its token said another and every cryptographic check would still pass. The reported time now comes out of the token, and a receipt that misstates its own evidence fails. pr anchor-verifyno longer reports success as a warning. Findings were sorted into fail-or-warning with no third case, so the strongest result the command can produce, a timestamp whose signature and certificate chain both validated, printed under[warn]. To an auditor that reads as a problem with the evidence.
0.2.31August 18, 2026Anchoring
- Anchor your chain without handing over your records.
pr anchor-pushsends the Merkle root of a local bundle and how far it reaches, and nothing else: the endpoint that receives it has no field a record could arrive in.pr anchor-verifythen proves a receipt covers a bundle offline, without calling the service that issued the receipt, because a check that asks the issuer whether its own receipt is good proves nothing. Coverage of a stream can only grow, so a shorter or forked history is refused rather than signed. - A verdict now says what it does not mean. The browser verifier put the completeness limitation at the bottom, below jargon, while the badge at the top said "Verified". Every word was true and the impression was wrong. What a result confirms and what it cannot now sit beside the verdict, in plain language, and the time and witness lines move between those columns depending on what was actually checked.
- Instrumentation that cannot record now says so. A published example passed the wrong object as the recorder: instrumentation succeeded, every model call succeeded, nothing was recorded, and the exception was swallowed. Wiring a recorder without
record_model_callnow fails immediately, and a capture that fails at runtime warns instead of passing in silence. - A typo in a trust flag is an argument error, not a tampering verdict.
pr verify --tlog-pubkey noneused to print TAMPERING DETECTED and exit 1, so a shell typo raised a security alarm and gated a build.--tsa-rootand--max-cosig-agealso work onpr verifynow, not just on thepr-verifyscript. - A dead sink no longer wedges the next
pr quickstart. A failed start left its pid file behind, and the next attempt refused to run naming a process that no longer existed.
0.2.30August 5, 2026Onboarding
pr quickstartnow leads with a zero-code proof. It used to hand you a Python snippet to paste into a file you had to create, so the first thing a new user did was author code, before anything had shown the tool works. It now offerspr demoandpr verify bundle.jsonfirst: a real signed run and a verdict on it, in two commands, before you write a line.- Tutorial videos re-recorded against 0.2.29. Five of the six were captured from 0.2.0 and had drifted. The licensing tutorial promised a key with no expiry, which we no longer issue: keys are bounded to the subscription period. The evidence-pack tutorial listed a file the archive has not contained for several releases. The guardrail tutorial named three armed packs when the default arms four, and the install tutorial taught
pipwhile every other page teachesuv.
0.2.29August 5, 2026Concurrency
- Two agents recording into one stream reported tampering on every record. Each client keeps the receipt head the sink last issued it and expected the next receipt to link to exactly that, which stops being true the moment a second agent appends. One project running two agents at once is an ordinary setup, and under sustained load it raised a sink-integrity error on 13,136 of 13,470 records. The client now asks the sink for the links it missed and walks them, so continuity across the gap is proven rather than assumed. A sink that reorders, forks or rolls back still cannot produce that walk, and is still refused.
- New endpoint
GET /v1/streams/{id}/receipts. Chain links only, never record bodies, so a write token can check its own continuity without becoming a way to read what other agents wrote.
0.2.28August 5, 2026Seats
- Single sign-on could grow a team past the seats it pays for. Inviting a member checked the plan's seat limit; a first-time SSO login did not, and provisioned the member anyway. An org on the ten-seat Team plan went to eleven and kept going, one login at a time. New users are now refused at the cap with a message an administrator can act on. People who already hold a seat keep signing in.
- Cancelling a plan left former teammates with their keys. A seat was only ever checked when the member was created, so downgrading from Team to a single-seat plan did nothing to the keys already issued. Every teammate kept reading the audit trail indefinitely. Member keys are now checked against the plan on each request, and a downgrade that shrinks the seat count keeps the longest-standing members. Nothing is deleted, so restoring the plan restores access with the same key.
- Disabling a member frees their seat. Disabled members could not sign in but still counted against the cap, and member rows are never deleted because the audit log points at them. A team that replaced one person could never fill its last seat again.
0.2.27August 5, 2026Windows
- Spend caps did not bind on Windows at all. The cross-process ledger lock was POSIX-only, and the Windows path was documented as "weaker" when it was in fact absent. Three processes each adding 60 increments of one cent recorded $0.03 of $1.80, so 98% of the spend vanished and a blown cap read as barely touched. Windows now takes a real cross-process lock. Found by adding Windows to CI, which is the entire reason to have it there.
python -m provenrail, for whenpris not ourpr.pris also the POSIX paginator from coreutils, and on Windows under Git Bash that one wins on PATH. There was previously no way around it. Every subcommand now works through the module entry point, which nothing can shadow.- CI runs Windows and macOS. Alongside Linux on Python 3.11 to 3.14, with a smoke test of the real command line on each.
0.2.25August 5, 2026Concurrency
- Two agents writing at the same moment no longer break each other. The sink shared one SQLite connection across request threads and serialised only writes, not reads. A read cursor interleaved with another thread's statement returned no row for records that plainly exist, so under concurrent ingest a valid write token came back as
invalid token(401), an existing stream came back asunknown stream(404), and the record-count check crashed (500). A client is entitled to read a 401 as "my credentials were revoked", stop, and drop the run, so this could lose records for no reason other than a second agent writing at the same instant. Every statement is now serialised and its rows read before the lock is released. Measured on a 400-request burst from 16 threads: 58 failures before, 0 after, across 1,200 requests. - Python 3.14 is tested. The bug above appeared reliably on 3.14 and not at all on 3.11, and our matrix stopped at 3.13, which is exactly why it shipped. CI now covers 3.11 through 3.14, and adds Windows and macOS runners plus a smoke test of the installed
prcommand on each. - Spend controls are documented.
pr spend, budget caps in.provenrail.json, andpr reconcileagainst a provider invoice were built but appeared nowhere on the site. Same forpr verify-contentandpr disclose. - The site meets WCAG 2.1 AA. An audit of every page in both colour schemes found three serious classes of violation: links identified by colour alone, code blocks and tables that scroll but could not be reached by keyboard, and text that missed the 4.5:1 contrast floor. All fixed and re-audited to zero.
0.2.24August 5, 2026Reports and CI
pr report --outnow writes the file for every format. It was honoured only with--html. With--mdor the default JSON the flag was silently ignored and the report went to standard output, so an evidence file you believed you had saved did not exist and nothing said so.pr diffexits 1 when the runs differ, following the same convention asdiffandgit diff --exit-code. It used to exit 0 either way, so the one question it exists to answer, did the rerun do the same thing, could not be gated on without parsing its output.pr packlists what is actually in the archive. The printed contents were a hardcoded list that had drifted: it named an attestation file the pack does not contain and left out four that it does, including the rendered report an auditor opens first.- A sink reports the version it is running.
/v1/metacarried the literal 0.2.0 long after the package moved on, so every deployed sink misreported itself in the one field you read during an incident. pr reconciledocuments what its exit code means: 1 when the invoice bills for something that never went through the recorder, and unchanged by a price variance on a model you did record, because our figure there is an estimate from list prices.
0.2.23August 5, 2026Exit codes
- Writing a guardrail receipt is no longer reported as a failure.
pr guard receipttook its exit code frompr risk, which exits 1 when it finds blocked actions. Finding them is the reason you run it, so a successful export reported itself as a failure, with the same code as an export that produced nothing at all. It now exits 0 when the receipt was written.pr riskkeeps the gating exit code for CI, and both are documented in--help. - A pack name in capitals is the same pack.
--use DESTRUCTIVEstopped setup with an unknown-pack error. Pack names are now matched case-insensitively.
0.2.22August 5, 2026Guardrails and CI
- A
--usethat names no pack now refuses, instead of arming something you did not choose.pr guard hook --use " "parsed to an empty list and fell back to whatever the configuration file said, under a flag that reads as if it selected those packs. Bothpr guard hookandpr guard installnow refuse and say so, and a refusedinstallwrites nothing to disk. - The exit codes are documented where a script author looks.
pr verifyexits 2 when it could not read the file at all and 1 when it reached a verdict that is not a pass. A CI job that treats every non-zero result alike turns a mistyped path into a tampering alert. Both meanings are now in--helpand the README.
0.2.21August 5, 2026Costs and CLI
- A model that was missing from the price table can no longer switch a budget off. Claude Mythos 5 is live at $10/$50 per million tokens and was in neither price table, so every call to it estimated at $0.00 and a spend cap silently stopped binding. It is now priced in both the CLI and the browser verifier, and the two agree call for call.
- Introductory pricing now ends when the provider said it would. Claude Sonnet 5's $2/$10 introductory rate is published as ending 31 August 2026, after which the standard $3/$15 applies. The table kept billing the introductory rate past that date and undercounted every September call by a third. It now switches on the date, and an expired rate with no published successor stops counting as a binding estimate rather than quietly reporting a total it knows is low.
- A binary file no longer produces a traceback. Pointing
pr verifyat a PDF, an archive, or a half-finished download raised a raw decoder error out of the file read. It now gets the same plain sentence broken JSON gets. Both cases also print aRESULT:line, so a script reading standard output for a verdict is never met with silence. pr guard hook --useworks. The flag was accepted and then ignored: with no configuration file present the hook allowed everything while its own help text said the packs were armed. It now arms exactly the packs named, and a mistyped pack name refuses to enforce and says so instead of silently arming nothing.pr --version. The first thing anyone needs for a bug report.- The site is fixed on tablets, and stops moving under you. The navigation overflowed the viewport on every page between 768px and 1050px wide, which is every iPad in portrait. Fonts are now served from our own origin instead of Google's, which removes a third-party request from every page load and takes layout shift on the homepage from 0.25 to 0.00, and on the account page from 0.24 to 0.00. Body text, tap targets and reduced-motion handling were brought up to standard throughout.
0.2.20August 5, 2026Verification
- The two verifiers no longer disagree about a broken file. A structurally invalid bundle (a list-valued key set to a string, a missing
stream_id, aseqthat is not a number) made the CLI report TAMPERING DETECTED while the browser threw and showed a neutral "could not verify". Same file, opposite readings, which is the exact failure a second implementation exists to prevent. Both now decide the shape up front by identical rules, and a test asserts they agree on the verdict and the full finding set across 26 malformed shapes. server_headis checked. The bundle carries the hash of the chain's last record and neither verifier ever looked at it, so an export could claim any head at all, including one from a different stream, and still verify.- An account could grant itself the top tier.
PUT /v1/account/plantook the plan from the request body behind an owner-only permission, so a free account could name itself enterprise and receive unlimited events, seats, SSO and exports. An upgrade now requires the payment provider's secret; a downgrade stays self-service. - A tab defeated every guardrail. Arguments are matched as JSON text, where a real tab becomes the two characters backslash-t, so
rmand-rfseparated by a tab matched nothing. Argv arrays had the same problem through thedecide()API. Both are normalized before matching now. rm -f one-file.txtis no longer refused outright. It was denied by a pack that is on by default, and a guardrail that blocks ordinary work gets switched off within a day, taking the working rules with it. Recursion is what cannot be undone, so recursion is what is refused; a forced non-recursive delete asks for a recorded human decision instead.- Rotating an agent key with the wrong old key changed nothing, and said it succeeded. The old key stayed active and a second live key appeared. It is a 404 now, with nothing written.
- Three cached-read prices were wrong.
o1ando3-miniwere billed at half the real figure, and Gemini 2.5 Pro kept its base cache rate above the 200k tier where the vendor doubles it, so spend caps under-counted exactly the long-context calls where caching is worth using. - Guardrails that fired on the wrong thing.
kubectl get pods --namespace prodneeded a human approval because the deploy rule matched any command carrying the namespace rather than the verbs that change something, and a rule that stops people looking at production is a rule they turn off.SELECT * FROM "users", the spelling Postgres and most ORM codegen emit, matched nothing at all.pr packwrote an evidence pack for a bundle that fails verification and exited 0, so only the recipient learned the truth. - The CLI stopped printing tracebacks. A missing sink, a directory where a file was expected, an unreadable file and a mistyped rule pack all say what happened and what to do. A mistyped pack used to print the whole catalogue and exit 0, which looked exactly like success.
0.2.17August 4, 2026Guardrails
- Everything we advertise is now actually blocked. Testing a fresh install against our own marketing copy found five claims the code did not keep:
chmod 777lived in a pack the default install never armed;TRUNCATE ordersslipped through because the rule required the optionalTABLEkeyword;dd of=/dev/andkubectl delete namespacehad no rule at all; and the token pattern matched only the retiredsk-key format, missing every currentsk-proj-andsk-ant-key. - The
accesspack is armed by default, so world-writablechmodand disabling MFA are blocked out of the box. Existing projects keep the packs written in their.provenrail.json; an upgrade never starts blocking on its own. - Production rules that fire under a coding agent. The pack matched only tool names like
deploy*, which Claude Code never emits, so it was inert for the host we ship for. A connection string pointed at production and a deploy command now ask for a human, rather than being silently allowed. - The copy is a test now. Every capability named on the site, in both READMEs and in the PyPI summary is asserted command by command against the default install, alongside 17 ordinary commands that must not trip. Marketing and code can no longer drift apart quietly.
0.2.16August 4, 2026Guardrails
- The policy is found from a subdirectory.
pr guard installwrites the policy at the repo root, but agents are routinely launched from a package inside a monorepo. The lookup only checked the current directory, so a session started inapps/webfound no policy and allowed everything, silently. It now searches upward to the repo root. This was the worst failure available: you installed it, so you believed you were covered. - Silence is no longer a valid state. If the hooks are wired but no rules are armed, the hook now says so, once a day rather than on every call. A silent allow is indistinguishable from a working guardrail.
- The journal stays with the policy. It is anchored to the directory holding
.provenrail.json, so a session started in a subdirectory no longer opens a second, invisible journal with its blast-radius counters reset to zero. - The plugin shim forwards those warnings instead of discarding them, which had been hiding exactly the message a user needs.
0.2.15August 4, 2026Guardrails
- Blast-radius caps now actually cap. Every hook invocation is a separate process, so a
limitrule counting in memory reset on every call and capped nothing, whilepr guard statusstill reported it as armed. A cap that never fires is worse than no cap. Counts are now carried across hook processes, per session, and pruned after seven days. - Stated honestly. The counter file is local and editable, so it is a convenience and not evidence.
denyandrequire_oversightnever read it, which means editing or deleting it cannot unblock anything.pr guard resetclears it.
0.2.14August 4, 2026Distribution
- Installable as a Claude Code plugin.
/plugin marketplace add pofky/provenrailthen/plugin install provenrail-guard@provenrail. No settings file to edit. The hook shim fails open: if Provenrail is not installed it exits silently rather than blocking every tool call, and warns once a day instead. A guardrail that bricks your agent gets uninstalled before it guards anything. - A guide for the thing people actually search for. Claude Code guardrails covers why the permission prompt is not a control, what each rule pack blocks, and why some rules ask instead of deny.
- Package metadata. Keywords, classifiers and project links on PyPI, and a README that leads with the guardrail rather than the threat model.
0.2.13August 4, 2026Guardrails
- Guard a coding agent in one command.
pr guard installarms the destructive, secrets and production rule packs and wires them into Claude Code's own tool hooks. No SDK, no code change.rm -rf,terraform destroy,git push --force,DROP TABLE,chmod 777and leaked API keys are blocked before they run, and the agent is told which rule fired. - A human decision stays a human decision. A rule that requires oversight becomes a permission prompt rather than a hard block, and your approval is recorded as human oversight in the signed chain. Flattening that into a block is how guardrails get uninstalled on day one.
- The verdict never depends on the network. It is computed offline before anything is sent, so an unreachable sink can never turn a deny into an allow. A decision that cannot be recorded is journalled locally and reported as unsigned and pending, never as proof.
- Receipts.
pr guard statusshows what is armed and what it blocked;pr guard receiptexports the proof so you canpr verifyit yourself.pr risknow counts escalations separately from denials.
Honest scope: this covers tool calls the host routes through its hooks. It cannot constrain a process that never calls them.
0.2.10 to 0.2.12July 30, 2026Guardrails
- A catalogue of prebuilt guardrails. Seven packs and 31 rules (35 today) covering destructive actions, credentials, money movement, production infrastructure, access changes, data exfiltration and blast-radius caps. Enable by name in
.provenrail.json. Nothing is on by default: an upgrade must never start blocking your agent on its own. - Rules you can check instead of hope about.
pr ruleslists the catalogue with each rule's false-positive risk;pr rules --check bundle.jsonreports which rules would match the tool names in a real recorded run, and says plainly that content rules cannot be judged from names alone. - Denials as evidence. A blocked action is written into the signed chain, shown in red on the public proof page, and can fire a signed webhook the moment it is ingested.
- Re-running an agent works. Multiple runs on one stream are now verified as independent sessions, each rooted in its own genesis record, in both the Python and browser verifiers. A persistent device key at
.provenrail.keykeeps runs on a pinned stream signing under one identity.
0.2.7 to 0.2.9July 30, 2026Integrations
- Agno, Hermes and the Claude Agent SDK. Capture through each framework's own hook mechanism, with no core changes on their side. A policy denial is recorded as a denial, never folded into "tool failure", because the two are different facts.
- LangChain compliance handler. A
ComplianceCallbackHandlerthat signs and hash-chains every model and tool call off-box.
0.2.2 to 0.2.6June 12, 2026Platform
- Bitcoin anchoring. OpenTimestamps proofs verified as a step in both verifiers, held in agreement by frozen conformance vectors.
- A second implementation, in your browser.
verify.jsis an independent verifier (WebCrypto Ed25519, JCS canonicalization, RFC 6962 Merkle proofs) that runs client-side, so the hosted verifier trusts not even our server. - TypeScript recording SDK. A run recorded in TypeScript is byte-for-byte compatible with one recorded in Python, proven by a cross-language test rather than asserted.
- Transparency log and witnesses. Per-account RFC 6962 log with inclusion and consistency proofs and independent witness cosignatures, so a sink cannot show two different histories.
- Selective disclosure, evidence packs, replay and diff. Redact a field without breaking the chain;
pr packbuilds a self-contained auditor ZIP;pr diffcompares two integrity-checked runs so a difference is a real behavioural change.
0.2.1June 11, 2026CLI
- Verify your own run, free and local. New
pr exportpulls a run you recorded out of the local sink into a bundle, so you can close the full loop on your own machine: record,pr export,pr verify, with no account. - quickstart keeps a read token.
pr quickstartnow saves a read token in.provenrail.json, which is what makespr exportwork with zero extra setup. - Deterministic tests. The test suite no longer reads the developer's installed license, so free-tier behavior is verified consistently on every machine.
0.2.0June 11, 2026CLI
- Commercial licensing.
pr activateverifies a signed license key fully offline and runs the self-hosted deployment at the licensed tier. Plan limits and feature gates are enforced server-side; the integrity guarantee never depends on any of it. - Install that survives a system Python upgrade. Recommended install moved to
uv tool install provenrail, which brings its own pinned Python.
0.1.xJune 2026Launch
- The core. Off-box, hash-chained, signed records with an append-only sink and an independent server receipt chain.
- Two SDKs in lockstep. Python and TypeScript recording SDKs producing byte-for-byte compatible records, with drop-in capture for OpenAI, Anthropic, LangChain, and MCP.
- An offline verifier.
pr verifyand an in-browser verifier that trust nobody, backed by a frozen public conformance suite both must agree on. - Trusted time and transparency. RFC 3161 timestamps, a witnessed transparency log, SCITT COSE receipts, verifiable redaction, and OpenTimestamps (Bitcoin) proof verification.
- Evidence packs. One-command evidence packs mapping recorded events to EU AI Act Article 12 and HIPAA audit-control requirements.
Dates reflect public release. For the precise commit history, the SDKs, verifier, and spec are open source under MIT.
← Back to home