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 in git status and could be staged by git add -A. They now live under ~/.provenrail. A project that has a .provenrail.json keeps its journal beside it as before, and a journal already in a project stays in use. The notice stamps moved for every project.
  • /guard-undo told plugin users to run pr undo. On a machine with only the plugin there is no pr. The list, the restore message and the errors now name the command that was typed.
  • pr guard status prints 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 .git is never opened or written. pr undo lists the checkpoints with what each action changed, pr undo last restores, and a full restore is verified by tree hash. In a real Claude Code session told to run rm -rf src on a directory holding uncommitted files, the agent replied "Deleted src (untracked, so not recoverable from git)." and pr undo last then restored both files and reported "Verified: the working tree now matches the checkpoint exactly." Not covered: gitignored files apart from root-level .env files, 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 stop and pr 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-stop and /guard-scan join /guard-status, /guard-card and /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 with uv tool install git+https://github.com/pofky/provenrail.
  • A config file with no policy key 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 report reads 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, --json and --share, which reduces the output to something safe to paste.
  • A spend cap that binds where the money is spent. /guard-budget 25 reported itself armed and capped nothing in the recommended install: budgets only bound on an SDK model_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 budget disarmed all 44 destructive rules while saying the cap was on. Setting a budget wrote a policy whose use list 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 install refused 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 --version probe 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 ./build is 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 is git 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 --hard on a tree with nothing uncommitted and nothing unpushed destroys nothing at all. The git rules run git 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.
  • rm is now screened by where it points, not by its flags. Measured over the corpus below, the two rm rules produced 687 of 923 interruptions and almost none were dangerous: rm -rf .next out before a build, rm -f coverage.xml, rm -rf app/en during 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-root is refused. A delete that merely leaves the project asks.
  • A rule about commands no longer reads the body of a file. Writing docs/security.md explaining what rm -rf / does, a migration containing DROP TABLE legacy, or a test fixture holding a fake token was denied on the Write and Edit tools. 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-run deploys nothing and wrangler d1 execute --local runs against a file in .wrangler/. Both used to interrupt.
  • Three matcher bugs that produced pure noise. chmod +x was 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 SQL TRUNCATE. 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.example and the other template spellings no longer ask.
  • Installing the CLI silently disarmed the plugin. The hook script prefers pr whenever it finds one, and pr guard hook read 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 empty use.
  • An older CLI left on the machine shadowed every new rule. Found on the developer's own laptop: a pr from 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's deleteVolume, which is the exact shape of the public incident this site cites, was never handed to the hook, and neither was a Read of ~/.ssh/id_ed25519. It is now wired to every tool, and scope is declared per rule with a new not_tool field, which is visible in /guard-rules instead 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.json or .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.
  • tool and not_tool accept |-separated alternatives. fnmatch has 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.py runs 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, so git reset --hard origin/main shows as git reset --hard and an environment assignment carrying an API key shows as export. 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-cap matches every tool, and a limit rule under its cap returned an allow immediately, so {"use": ["blast-radius", "destructive"]}, the obvious thing to write when you want both, let rm -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-verify now 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 --blame works on repositories with binary files. git blame echoes 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-by trailer 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@provenrail used 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-status and /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, a Co-authored-by: trailer or a git note, is written by the party being asked and can be edited afterwards. pr attest names 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-anchor adds an independent RFC 3161 timestamp over a 32-byte root; no line of your code is ever sent..
  • pr attest-verify re-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-push stopped asking for what it already knows. It defaults to the hosted service, to the licence key pr activate stored, and to writing anchor-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 verify now 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.87 raised "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.com works 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_time written 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-verify no 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-push sends 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-verify then 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_call now 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 none used to print TAMPERING DETECTED and exit 1, so a shell typo raised a security alarm and gated a build. --tsa-root and --max-cosig-age also work on pr verify now, not just on the pr-verify script.
  • 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 quickstart now 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 offers pr demo and pr verify bundle.json first: 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 pip while every other page teaches uv.
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 when pr is not our pr. pr is 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 as unknown 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 pr command on each.
  • Spend controls are documented. pr spend, budget caps in .provenrail.json, and pr reconcile against a provider invoice were built but appeared nowhere on the site. Same for pr verify-content and pr 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 --out now writes the file for every format. It was honoured only with --html. With --md or 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 diff exits 1 when the runs differ, following the same convention as diff and git 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 pack lists 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/meta carried 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 reconcile documents 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 receipt took its exit code from pr 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 risk keeps the gating exit code for CI, and both are documented in --help.
  • A pack name in capitals is the same pack. --use DESTRUCTIVE stopped setup with an unknown-pack error. Pack names are now matched case-insensitively.
0.2.22August 5, 2026Guardrails and CI
  • A --use that 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. Both pr guard hook and pr guard install now refuse and say so, and a refused install writes nothing to disk.
  • The exit codes are documented where a script author looks. pr verify exits 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 --help and 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 verify at 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 a RESULT: line, so a script reading standard output for a verdict is never met with silence.
  • pr guard hook --use works. 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, a seq that 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_head is 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/plan took 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 rm and -rf separated by a tab matched nothing. Argv arrays had the same problem through the decide() API. Both are normalized before matching now.
  • rm -f one-file.txt is 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. o1 and o3-mini were 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 prod needed 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 pack wrote 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 777 lived in a pack the default install never armed; TRUNCATE orders slipped through because the rule required the optional TABLE keyword; dd of=/dev/ and kubectl delete namespace had no rule at all; and the token pattern matched only the retired sk- key format, missing every current sk-proj- and sk-ant- key.
  • The access pack is armed by default, so world-writable chmod and 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 install writes 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 in apps/web found 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 limit rule counting in memory reset on every call and capped nothing, while pr guard status still 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. deny and require_oversight never read it, which means editing or deleting it cannot unblock anything. pr guard reset clears it.
0.2.14August 4, 2026Distribution
  • Installable as a Claude Code plugin. /plugin marketplace add pofky/provenrail then /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 install arms 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 777 and 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 status shows what is armed and what it blocked; pr guard receipt exports the proof so you can pr verify it yourself. pr risk now 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 rules lists the catalogue with each rule's false-positive risk; pr rules --check bundle.json reports 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.key keeps 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 ComplianceCallbackHandler that 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.js is 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 pack builds a self-contained auditor ZIP; pr diff compares 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 export pulls 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 quickstart now saves a read token in .provenrail.json, which is what makes pr export work 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 activate verifies 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 verify and 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