What happens after you find the bad source?

September 2026 · github.com/veracium-ai/Veracium

You find out on a Tuesday. The vendor feed your agent has been reading since March was compromised, or the colleague whose forwarded emails you trusted was phished, or the scraped documentation page you pointed it at was edited by someone you would not have let near it. The source is bad. You know which one it is.

That is the easy half.

The hard half is the question nobody puts on a feature list: what does your memory system do next? Go and look for the answer in whatever you are using. If the only recovery path you can find is delete the user and start over, that is not recovery. That is amputation, and it costs you every true thing the agent learned about that person alongside the false ones.

Deleting the row is not an answer

The bad claim did not stay where it landed.

By Tuesday it has been summarised into something shorter. It has been merged with neighbouring facts. It has superseded an older, truer value that your system correctly retired at the time, because at the time the claim looked fine. It has been carried into episodes, quoted in context the agent assembled for a dozen prompts, and written into an export that someone restored onto a second machine.

Delete the row the claim arrived on and every one of those survives. Worse: they survive without the thing that would have let you find them. The arrival record was the only object still carrying the source. Its descendants are plain facts, and plain facts read as true.

This is the property that makes provenance worth having at all. Recording where a fact came from is the easy half of provenance, and it is the half everyone means when they say a system has it. Being able to act on that record afterwards, across everything the fact touched, is the part that is actually hard, and it is the part that decides whether the record was a control or a label.

So the useful question is not “does your system track sources?” It is: what must a revocation reach before it deserves the name?

Three things it has to do

There is a short answer, and it has three parts. Each one has a failure mode that looks fine right up until the Tuesday it matters.

It has to reach derived content, not just the arrival record. A revocation that retires the row and stops there leaves the claim’s descendants standing. The claim outlives its own source, which is the exact inversion of what you asked for.

It has to be reversible without amnesia. You will sometimes be wrong about a source. If the only way back is to re-ingest from scratch, then revoking is a one-way door, and people do not walk through one-way doors on suspicion. They wait for certainty, which arrives late. A lift has to restore exactly what the revocation took, and nothing else: not the things that were retired for other reasons, not a best-effort reconstruction.

It has to refuse revival. This is the one that gets missed. A memory system is not a static store; it runs maintenance. It consolidates, it re-summarises, it merges, it re-derives. If any of those verbs can quietly bring back what a revocation removed, then the revocation was a moment rather than a state, and the claim comes back on its own schedule, in a shape that no longer looks like the thing you revoked.

In Veracium these are the shipped surface, not a roadmap. The sweep reaches edges, episodes, and derived content. A lift restores what the revocation took. Every maintenance verb refuses revival. You do not have to take my word for the last one, and the end of this post is about not taking my word for it.

The preview is the same computation as the commit

Here is the detail I find most persuasive, and it is a design property rather than a measurement, which means you can check it by reading rather than by trusting.

Before you commit a revocation you want to know what it will reach, so a preview is the obvious thing to build. The question is whether the preview is telling you the truth, and the usual way that goes wrong is boring: the preview becomes a second code path. It gets written later, to answer roughly the same question, and it drifts. The preview says eleven records, the commit touches fourteen, and you find out afterwards.

In Veracium the sweep is one pure function. It takes the store, the target source, and the action you are proposing, and it returns a completeness statement plus the exact list of effects. Nothing is written and nothing is applied. revoke_source with dry_run=True returns that statement and stops. With dry_run=False, it appends the revocation row and applies those same effects.

One function, both callers. The preview cannot disagree with the commit, because there is no second thing to disagree with. That is a small architectural decision and it is the difference between a preview you can plan against and a preview you have to double-check.

Together, or not at all

The revocation row and its effects land in one transaction, or neither does. If an effect cannot be applied, the row rolls back with it. There is no state in which the ledger says a source was revoked and the store has not acted on it.

That sentence is easy to write and it was not easy to earn, and the module’s own documentation says so. This is the comment that sits at the top of the shipped file:

plan and apply_effect are REQUIRED. Round 4’s R4-1 found a version of this operation that appended the row and never applied the effects — and passed, because nothing asserted an effect had landed.

An external reviewer, in the fourth round of reviewing this specification, found a version of the revocation operation that recorded the revocation and did nothing else. It passed its tests. It passed because the tests checked that the row was written, and nobody had asserted that an effect had actually landed. A control that cannot fail is not a control, and until a reviewer asked, ours was one.

That was caught before release, before any of this was in anything you could install. What matters more is how it was fixed. The plan and the effect-applier were made required arguments that the operation cannot default away, so an implementation that skips the effects will not run at all. The defect is not tested for. It is unrepresentable.

I am quoting the comment rather than summarising it because it ships in the source you install, and because a post arguing that systems should be able to show their work is in a poor position to hide its own.

There is no veracium revoke, and that is the point

One thing you should know before you reach for this, because you will find it out in ten minutes anyway: revocation is a library API and not a command. You import revoke_source and hand it a store. There is no CLI verb and no MCP tool.

That looks like an oversight, and I assumed it was one until I read the specification. It is an invariant. The rule is R14, it is asserted by a test named test_revocation_is_not_exposed_on_the_agent_surfaces, and the reasoning is written down:

A revocation is a trust decision about a source; a surface the model can reach is a surface the model can be talked into.

Consider what the alternative buys an attacker. This is a memory system whose whole premise is that untrusted content gets into it. If revocation were reachable from the tool surface, then a document your agent reads could contain instructions to revoke a source, and the mechanism designed to remove an attacker’s foothold becomes the mechanism that removes yours. The same reasoning keeps forget, dispute and confirm off the tool surface. Suppress, erase and revoke are the verbs you least want an agent talked into calling.

forget does have a command-line verb, which makes the asymmetry look worse than it is. The distinction being drawn is between an operator running a command and an authority being exercised, and revocation was deliberately held to the second: a host call whose completeness statement the caller is expected to read before committing. Whether the operator’s shell is far enough from the model to relax that is a real question, and the honest answer may be that it is getting further from qualifying rather than closer, because a shell an agent drives is exactly the reachable surface the rule is about. Either way it is not a question you answer by adding a subparser. It reopens an accepted invariant, which means a specification revision and an external review round, and that is the path it would have to take.

So: the ergonomics cost is real and you should factor it in. But it is a position, not an omission, and it is the same position the rest of this post is about. A control the attacker can invoke is not a control.

What you can run

Everything above is checkable, and the check is in the box:

pip install veracium
veracium selfcheck

selfcheck scores the guarantees this library is built on and revocation is one of them. It walks the seam end to end, including the case where a backup is restored into a store that already holds a revocation for that source, which is the scenario an operator actually meets. And it carries a regression proving the check fails when revocation is switched off: a check, not a label.

Two honest bounds on that. Revocation has been checkable since 0.14.0, and the import-side coverage since 0.17.0; a reader pinned below those has a selfcheck that cannot check them. And that import-side case covers one source, one user, one export. Multi-user files are outside the claim.

Finding the bad source is the easy half. The rest of it is machinery, and machinery is worth asking about before the Tuesday when you need it.

Discuss: GitHub Discussions