DEV Community

Cover image for If persistence is not permission, where would you have drawn the line: cards only, or the entire turn?
Edward Izgorodin
Edward Izgorodin

Posted on Originally published at mnemoverse.com

If persistence is not permission, where would you have drawn the line: cards only, or the entire turn?

A RAG copilot answers a question, and the turn is stored with its source cards: which documents backed the answer, with names, versions and scores. A reload replays the conversation. Weeks later an admin revokes a permission. Should history still show the cards, the words, or nothing?

Rodrigo Diego asked exactly that at the end of his dev.to article of 25 August 2026, and the question in the title is his. The replay fight is real. It is also incomplete: search can be clean, history can be clean, and the document still returns.

TL;DR

  • A revocation has three points to reach, not two: search, the history replay, and the assembly of the next turn's context. Cards only and the entire turn are both replay lines. A narrow revocation does not reach what a stored thread already holds unless the assembly step checks it again.
  • For what a person sees, two records are defensible: keep the delivered words and withhold the derived evidence, counting a verbatim quote as evidence, or replay the turn exactly as it was shown. Either way, say that the permission behind the turn is no longer granted.
  • For what the model sees, compute the revoked state at every assembly against the permission as it is then, and never write it into the stored thread, so restoring the permission restores the result.

This article reads one public thread as a case study, the article and its comments through 9 October 2026 (two of them by the author of this article), and quotes vendor documentation only for what it states.

Why is chat history a second read path into RAG data?

A second read path is any route other than search that returns data retrieved under a permission that may since have changed. In the article's copilot, the history endpoint restores the conversation after a refresh and replays the stored source cards, so "your history endpoint becomes a second read path into the same data your search guards so carefully."

The article opens with this:

Persistence is not permission. What a turn was allowed to show at write time proves nothing about what it may show at read time.

At write time the live path runs search, entitlement checks and a per-document view gate. At read time the history endpoint selects stored rows. Between the two, weeks pass, and tenant admins flip two toggles: the copilot itself and the document-search capability that produces the cards. Hence the article's rule: "Entitlements drift between write and read. Re-check authorization at read time, in the service, not just at the gateway." The check was scoped on purpose: "The gate is entitlement-level, not document-level."

Cards only, the entire turn, or the record as shown?

The article's closing paragraph names three lines and picks one:

When the document entitlement is revoked, I kept the words and withheld the evidence. A stricter shop would redact the whole turn; a looser one would replay everything and call the persisted rows an immutable record.

Its case for keeping the words: "the words were already delivered to this user once, and the toggle governs the document-search capability". Its own caveat: "The prose answer was generated from those documents and may paraphrase them."

By 8 October 2026 the author had moved:

Turns now replay exactly as they were shown, cards included, and the document toggle governs what the next turn can do rather than what the record shows.

Another reply that day gives the mechanism: "Since the post, a turn's result blocks are written once at turn end, exactly as streamed, and the transcript replays them as seen. The document toggle scopes doing; the record stays the record." For the whole copilot: "Copilot off still means no history reads at all (now a typed 403), with every row intact and no deletion event."

Two conditions hold for every replay line. Keeping a record and letting someone read it are separate decisions: the 8 October design keeps every row and still admits no history read when the copilot is off. And a notice that the permission is gone explains the policy; it does not enforce it. None of the replay lines decides what the model receives next.

Where does a revoked document get back into the next turn?

Context assembly is the step that builds the prompt for the next model call from the stored thread, the new message and any tool results.

In the thread on 30 August 2026, the author of this article pointed at the gap: the checks on search and on the history read leave out what gets assembled into the prompt for the next turn. A document hidden after it sat inside an earlier exchange comes back, without touching either check, when that exchange returns as context. An illustration, not any team's system:

turn 1   search runs under the user's permission and returns document D
         the answer quotes D; the tool result and the answer go into the thread
revoke   an admin removes view permission on D (a narrow revocation)
replay   the history endpoint re-checks the permission and applies its replay line
turn 2   context assembly loads the thread, tool result and answer included
         unless assembly checks D again, the model receives D again
Enter fullscreen mode Exit fullscreen mode

On 8 October 2026 Rodrigo Diego answered:

Rebuilt from the stored thread, not re-derived from retrieval, so it's a genuine third gate and not a stricter second one.

He described what the thread holds: "The next turn's context is assembled from a checkpointed thread that holds tool results retrieved under the old entitlement." And how far each revocation reaches: "Revoking the copilot entirely closes it, because no turn is admitted at all. A narrower revocation, like the document capability or view permission on a single document, doesn't reach what's already in the thread."

Point What it reads Revoking one capability or one document Revoking the whole copilot
Search The documents, live The live path checks entitlements and per-document view permission (article, 25 August) No turn is admitted (8 October)
History replay Stored turns and their cards Document capability: cards withheld (25 August). One document: not re-checked on replay, the gate is entitlement-level (25 August). Either: the turn as shown (8 October) No history reads at all, every row intact (8 October)
Next-turn assembly The checkpointed thread, tool results included Does not reach what is already in the thread (8 October) No turn is admitted (8 October)

He named where a check would go: "The assembly step already rewrites history on every model call (guardrail scoping, tool-call repair, version stamps on document-bearing results), so that's where re-authorization would live." That is a location, not a shipping report. The precedent is in the same design: a turn cut short gets an explicit message in place of the missing tool result, "so the model sees the gap rather than a shorter conversation. A revoked result deserves the same treatment."

What counts as evidence: the words or the cards?

Delivered speech is answer text that was already shown to this reader. A derived artifact is anything that exists because a retrieval ran under a permission: source cards, document names, versions, scores, and text that reproduces a document verbatim.

Heinrich Neb refined the article's line on 25 August 2026: the boundary is not text against cards but delivered speech against derived artifacts.

text that VERBATIM quotes a document is a card wearing prose.

For text that mixes both, his rule: "Where provenance is segmentable, redaction should follow it per segment; where it isn't (paraphrase), keeping the words is defensible because they were already delivered". On 8 October 2026 the author called delivered speech against derived artifact "the line I should have drawn", and wrote in the same reply that "a revoked capability now governs the next turn, not the record".

That settles what a person sees, not what the model receives, because the answer text changes role. As the author of this article put it on 9 October 2026: shown back to the user, the answer is record, and the argument that it was already delivered still holds. Fed back to the model, it is material for the next turn, which is what the revocation should reach. If that answer quoted a revoked result, the model can restate a claim it once grounded with nothing left to check it against.

Why compute the revoked marker at assembly instead of writing it to the thread?

Because the two facts age differently. In the same exchange on 9 October 2026, the author of this article put it this way: "This did not complete" is a fact about that turn and stays true forever, while "this was revoked" is a fact about the current entitlement and stops being true the moment the permission comes back. Written back into the checkpoint, the placeholder has already destroyed the result by the time a revocation is undone. Computed per call, there is nothing to undo: the next assembly reads the restored permission and includes the result again.

Treat the stored thread as the record and the assembled context as a permission-filtered view of it. A sketch, not an API:

for result in thread.tool_results:
    if not entitled_now(result):
        yield unavailable_placeholder(result)   # computed per call, never written back
    else:
        yield result
Enter fullscreen mode Exit fullscreen mode

The sketch covers tool results only; an answer that quoted a result needs provenance on the answer text as well.

Documentation built for context management, not for permissions, shows the same shape. LangGraph: "Checkpointers persist a thread’s graph state as checkpoints." LangChain's transient context is "What the LLM sees for a single call. You can modify messages, tools, or prompts without changing what’s saved in state." Summarization works the other way, "permanently replacing old messages with a summary that’s saved for all future turns." A revoked marker belongs on the transient side. Anthropic's context editing splits the same way on the server: "Context editing is applied server-side before the prompt reaches Claude. Your client application maintains the full, unmodified conversation history." Only the shape is borrowed here: a change made when the prompt is assembled, without writing to what is stored.

What should a replayed turn say when the permission behind it is gone?

Silence is a third product decision, and the thread argued against it. Heinrich Neb, on 25 August 2026:

I'd stamp the replayed turn with "generated under an entitlement no longer granted" rather than silently replaying it.

Rodrigo Diego, on 8 October 2026, after the move to replay as shown: "If the record replays as seen, the honest version tells the reader the capability behind it is no longer granted, and the replay response carries no field for that today." Kartik N V J K, on 26 August 2026, had already stored what such a notice needs: "I started snapshotting the doc IDs and the entitlement version onto each turn so a replay can diff against current access, not just re-render."

The stamp is a display concern; the snapshot is a diff input. The recommendation here: a replayed turn keeps what the record holds and carries a status computed at read time against the reader's current permission, in the replay response itself, so that every client can show it. That is a recommendation, not a reported implementation.

Does gating the response keep revoked data out of the service?

Only if the policy concerns the response alone. The article's second gate withheld the cards in the response builder, and the author confirmed on 8 October 2026:

The revoked payload was selected, normalized and held in memory, and only serialization was gated, so a traceback, a payload-capturing span or a debug dump would all have seen it.

Keeping cards out of a response and out of the request's data path are different properties. The author agreed with the alternative: "Driving the column list from the entitlement (already loaded by then), with a not-selected negative control, is the right shape". The first gate had the stronger property, and a test for it: "The test pins this: when the toggle is off, the session and turn queries are never even awaited." A test that proves the protected read never ran says more than a test of the response shape.

Can a cache in front of the history endpoint replay what was revoked?

A check in the service protects the requests that reach the service. Vinh Nguyen, on 26 August 2026: "If the endpoint is cacheable, the cache key has to include tenant plus entitlement version, or revocation must purge it". His test: "A negative test that warms the cache before revocation and replays after would cover the read path the mocked database test cannot see."

The author agreed on 8 October 2026: without an explicit cache header, "nothing caches this" is "an assumption about whatever sits in front of the service, not a property of the service." His remedy: "Cache-Control: no-store on the history routes is the cheap fix that makes the guarantee local, and your warm-then-revoke-then-replay test is the one that proves it end to end."

How do assistants and memory APIs document this?

Two examples, each in its own words.

Microsoft Copilot. Data, Privacy, and Security for Microsoft Copilot: "Microsoft Copilot only surfaces organizational data to which individual users have at least view permissions." On stored history: "The stored data includes the user's prompt and Copilot's response, including citations to any information used to ground Copilot's response." Stored history there carries citations too, which is the surface this article is about.

Mnemoverse (the author's company). In shared-memory rooms (beta), "an expired or revoked grant stops working on the very next request." The rooms reference states the boundary: "Revocation is immediate and forward-only. A revoked member loses access from that moment on. There is no retroactive un-sharing: whatever the member already read, it keeps, and atoms it wrote remain in the room." Access can come back: "Re-inviting a revoked member restores access." This is the record-stays-the-record position at the level of a memory API. The next turn is assembled in the client that reads from the API, so the third point still needs its own check there.

Which tests prove the permission line holds?

Five tests, one per decision.

  1. Not awaited. With the copilot entitlement off, request history and assert that the session and turn queries never ran, not only that the response is empty. This is the article's own test of its first gate.
  2. Not selected. Where a policy forbids reading a field once access is revoked, assert that the field was never selected, not nulled in the response.
  3. Warm, revoke, replay. Warm any cache in front of the history route, revoke, replay through the same route, and assert the current replay policy.
  4. Revoke, continue. Revoke one document's permission after an exchange that used it, and continue. Assert that the assembled input carries an explicit marker in place of the tool result and of any quotation of it in the answer.
  5. Restore, continue. Restore the permission and continue. Inspect the stored thread itself, not the model output, and assert that it still holds the original result with no placeholder written into it; then assert that the next assembly includes the result again.

A passing replay test does not establish the continuation result; a passing continuation test does not establish that restoration works.

What this article does not claim

No performance or latency figure for anyone; the author of the original design did not measure one. Positions are dated, because the article of 25 August 2026 and the replies of 8 October 2026 differ. Product documentation is quoted for what it states; where a page says nothing about revocation, this article says nothing either, and it tests no product. Apart from the first test, which the original article describes as its own, the tests are proposals, not a description of any team's suite.


Sources and method. Every quotation was read on 25 October 2026 with two instruments: a browser-like request and a reader service with its cache off, and for dev.to comments the comment page and the dev.to API. A made-up address on each host answered 404 as a control. The text describes those sources as of that date. A longer version, with the full list of sources, is in the Mnemoverse documentation library: If persistence is not permission, where would you have drawn the line: cards only, or the entire turn?

Edward Izgorodin · Mnemoverse · 2026-10-25

Top comments (0)