Disco ParrotDisco Parrot Home
Docs
Request a Demo

Reading version history

The operational guide to working with version history on initiatives and plans. How to open the history tab, what a version row carries, the diff view, the edits list, the restore flow, and the practical playbook for reviewing what the agent has been doing in your work.

When a Disco Parrot agent rewrites a plan during a Flow, expands the open questions on an initiative in chat, or refines a spec on your behalf, you want to see exactly what changed, who set it in motion, and put it back the way it was if the rewrite went sideways. Version history is the surface that makes that real.

The concept page explains the design (snapshots, AI attribution, write-forward restore) and what guarantees the system holds. This page is the how, not the why: how to open the tab, how to read a version, how to compare two, how to restore one, and the weekly playbook teams use to keep up with what the agent is doing. If you have read the concept page, start at "Where the history lives on a record" and work down; if you have not, this page works on its own.

Version history applies to initiatives and plans inside Plan & Track Work today. Skills carry their own version history under Platform → Skills; agent instructions carry theirs under Settings → Agent instructions. Both use the same components and read the same way; the patterns on this page transfer to those surfaces directly.

Where the history lives on a record

Every initiative detail page and every plan detail page carries a Version history tab in the same tab row as Spec, Plans, Bugs, Documents, Sessions, and the rest of the detail surface. The tab fetches the version list when you click it, so the rest of the page renders fast and the list arrives when you ask.

Three surfaces on the same detail page together answer "what happened on this record."

Version history tabOn the record detail page.CAPTURESFull snapshot of the bodyand the spec fields per save.USE FORRead the diff. Restore anolder body. Filter by source.Activity panelAt the bottom of the same page.CAPTURESEvery audit event:creates, updates, restores.USE FORSee per-field updates andnon-body changes at a glance.Sessions transcriptFind by timestamp and actor.CAPTURESThe agent run that producedthe AI save: tool calls, edits.USE FORTrace what the agent didto arrive at this version.
Three records on every initiative and plan answer different questions. The version history holds the editorial body at each save. The activity panel holds every audit event including restores. The session transcript shows the agent run behind an AI save.

The three are linked by the record id and the source mark. Pick the right surface for the question you are asking.

SurfaceBest forGranularity
Version history tab"What did the body look like at this point in time? Can I restore it?"One row per editorial body change; full snapshot per row
History section (sidebar)"What events have touched this record? Status changes, restores, ownership transfers?"One row per audit event; metadata only
Sessions transcript"What did the agent do in the chat or Flow that produced this AI save?"The full per-execution transcript: tool calls, file edits, model output

The History section is rendered as a SectionCard in the right rail of every initiative and plan detail page (titled "History"), not as a bottom-of-page panel. The Sessions transcript is reached separately from the Sessions index by filtering on actor and timestamp; today there is no in-product link from a version row directly to the session that produced it, so you locate the matching session by the editor name and the version's timestamp.

The shape of a version

Each row in the version list is a full snapshot of the record at a moment in time. Snapshots are not diffs; the entire body and the structured fields are stored on the row, which means reading a past version is a single lookup, not a replay of changes.

Each row carries the metadata you need to scan the list quickly.

  • Version number. A monotonically growing integer per record. Versions never get renumbered or compacted.
  • Edited at. A relative time on the row ("2h ago", "yesterday", "3d ago", then a short date for older saves). The full ISO timestamp is in the row's tooltip.
  • Edited by. The display name of the user under whose session the save happened. AI saves still carry a real user; an agent runs inside a person's chat session and the save attributes to that person. The display name is frozen at the moment of the save, so a later name change does not rewrite history.
  • Source badge. One of three: User (a human typed and saved), AI (an agent wrote on a person's behalf), or System (a platform process saved). User and AI share the same informative-blue tint; the icon (person, bot, or gear) is what tells them apart at a glance. The latest row also carries a Current pill so you know which one is the live state.
  • Change note. A short summary of what changed.

The change note takes one of several auto-generated shapes when the platform produces it: Updated X for a one-field change, Updated X and Y for two, Updated X, Y, and Z for three, and Updated 4 fields (count only) when four or more fields move at once. A title-only change short enough to fit reads Renamed to "...". The very first version of a record reads Created initiative or Created plan. Restore writes a row with Restored from v3 (or the override you supplied). Human saves can override the auto-summary with a custom message; AI saves write whatever the agent passed along.

The list is filterable and sortable. The two filter categories the panel exposes are Source (User / AI / System) and Editor (the unique humans who have edited the record, populated from the loaded version rows). The default sort is newest first; you can reverse it to read the record's life chronologically. The list paginates via a Load more button at the bottom rather than infinite scroll, so a record with two hundred versions does not load all of them at once. The empty state on a brand-new record reads "No version history yet. Each save creates a version automatically; restore, diff, and attribution will appear here once edits start landing."

add_photo_alternate
Screenshot to capture
The Version history tab on an initiative detail page: a list of version rows in descending order, each row showing the version number, an Edited at relative time, an Edited by display name with avatar, a colored Source badge (AI / User / System) with bot/person/gear icon, and the change-note line; the latest row carries an additional Current pill; the FilterPanel pinned to the right showing Source and Editor categories with the AI filter applied; one row in the middle highlighted with its overflow menu open showing View diff and Restore actions; a Load more button at the bottom of the list.
save as: public/docs-media/version-history-tab.png
Caption when added: Version history on an initiative with the Source filter set to AI. Each row carries the version number, the editor, the source badge, and the change-note summary.

What gets captured on a version

A version row captures the editorial surface of the record: the body, the structured fields a human or an agent would edit, and the spec content. Workflow surfaces (assignments, sprints, estimates, links) are captured selectively. The matrix below is the honest map for both record types.

LEGENDCaptured in snapshotPreserved on restoreNot captured, not rewrittenNot applicableINITIATIVEPLANBody, spec, stepsCapturedCapturedStatusCapturedCapturedOwnerCapturedCapturedOpen questions, clarificationsCapturedCapturedDependencies, acceptance criteriaCapturedCapturedSchedule (dates)CapturedProject / initiative parentPreservedPreservedSandbox profilePreservedPreservedIntegration linksPreservedPreservedSprint, sprint positionSkipAssigneeSkipEstimate (hours / points)SkipPlan typeSkipLinked test casesSkipPull request fieldsPreserved
A version snapshot captures the editorial surface. Workflow assignments stay where they are; integration links and sandbox identity are preserved on restore. The split is what lets a restore bring back the body without rewinding the team's coordination.

An initiative captures sixteen fields on every snapshot: title, status, summary, body, the open-questions list, dependencies, owner, the project assignment, the start and target dates, the three sandbox profile fields (id, inputs, and the preset version), the two approval metadata fields (approvedBy and approvedAt), and the integration links. A plan captures sixteen as well: title, status, body, owner, branch, the approval attribution (approvedBy only; plans do not snapshot an approvedAt), dependencies, feature, the unblocks list, the steps list (including each step's nested acceptance criteria), the clarifications list, integration links, the parent initiative id, the two sandbox profile fields (id and inputs), and the suppress-environment-health flag.

Two ordering rules are worth knowing because they decide when a "reorder" produces a new version.

  • Tag-like arrays are sorted before hashing. A plan's dependsOn and an initiative's dependsOn lists are reorderable in the UI but semantically order-irrelevant; reordering them does not produce a new version row, because the canonical form sorts them before computing the hash.
  • Semantic arrays preserve order. An initiative's open questions, a plan's steps (including each step's nested acceptance criteria), and a plan's clarifications are priority-ordered; a reorder does produce a new version row, because the canonical form preserves their order.

Two carve-outs on the plan side are worth knowing.

Plans skip the pull-request fields. A plan's PR number, URL, status, branch, and open/merged timestamps come from GitHub through webhooks, not from anything a person or an agent typed. Including them in the snapshot would make a plan look like it had changed because a PR moved from open to merged, which is not an editorial change anyone made. Restoring a past version of a plan also preserves the current PR fields on the record rather than reverting them, so you cannot accidentally undo a PR by restoring an older plan body.

Plans do not version their workflow assignments. A plan's sprint, sprint position, assignee, story-point estimate, hour estimate, plan type, and linked test cases are workflow fields rather than editorial fields. They live on the plan record and move through their own surfaces. The plan version history captures the spec, the steps, and the parent linkage; it does not capture which sprint the plan is in or who it is assigned to.

info
The carve-out in one concrete example

Tom was assigned the CSV download button plan last Tuesday. The team reassigns it to Priya on Friday after Tom rotates onto a different project. On Monday, an agent rewrites the spec poorly and Sarah restores Tuesday's version of the plan body. Priya is still the assignee after the restore; the reassignment is not in the snapshot. If Sarah wants to undo the reassignment too, she does that from the assignee surface, not from version history.

There is a subtle asymmetry between what a snapshot captures and what a restore rewrites. An initiative's projectId, dates, sandbox profile fields, and integration links are captured on every snapshot (so you can read the historical value) AND preserved-from-current on restore (so restoring an older version does not rewind the project assignment or wipe accumulated integration links). The capture is for transparency; the preserve-on-restore is for safety. Most readers will not need to distinguish the two, but the moment a planner asks "why does the restored version still show today's project?", the answer is that the snapshot stored both values and the restore wrote the editorial fields forward while preserving the metadata.

How an agent edit is marked

The agent runs inside someone's chat session. When the agent saves an initiative or a plan, the save lands as the user whose chat ran the agent, with the source badge set to AI. The history row reads:

v12   AI   Sarah Chen   09:14   Updated spec

The same human you would have seen on a human save shows up on the AI save; the badge is what tells you the editor was the agent. The audit-log entry on the same record carries the same editSource: ai marker, so a filter that asks "show me only the agent's edits on this record" answers truthfully from either surface.

A Flow that drives an agent to write also lands as AI, attributed to the person who launched the Flow. From the version history's point of view, agent-driven saves are agent-driven saves regardless of whether they came from a chat conversation or a Flow step. System rows are rare; they appear when a platform process saves a record (data migrations, scheduled rewrites, internal cleanups) and use the gear icon and a subtle tint to set them apart.

lightbulb
Filter the workspace audit log by source for a cross-record agent review

The same editSource: ai marker the version history reads is also written to every audit-log row on every record. A workspace lead who wants to see what the agent has been doing across the team can filter the workspace audit log to editSource = ai and get the cross-record view in one query, then drop into the individual version histories for the diffs.

A save without a real change does not write a row

The platform canonicalizes the body before saving, computes a SHA-256 hash over the canonical JSON, and compares it to the previous version's hash. Identical bodies skip the version write; the canonical record updates and the response returns the existing version number. The hash is over a canonical JSON form, which is why reordering a tag-like list (like an initiative's dependencies) does not produce a false diff. The result is that the gap between v(n-1) and v(n) is always a real editorial change; the change-note is always honest; the list is always worth reading.

In practice this means a planner who saves a record three times with no edits between them sees one version row, not three. A Flow that re-runs a no-op step that re-emits the same body adds no version. The history is the editorial trail, not the save trail.

In rare cases a version write can fail. The platform retries on Cosmos write conflicts up to three times; if every retry collides, the canonical record is still saved and the missing version row is logged as a failure rather than silently dropped. This is the one failure mode where a save succeeds without producing a version row, and the operations team is the one who sees it in the logs.

Reading a single version

Open a row's overflow menu and click View diff to load the detail page; the row itself is not a hyperlink. The detail page header carries the version number, the editor, the timestamp, the source badge, and the change note. The body of the page shows the diff between this version and the one before it (rendered as v(n-1) → v(n)), which is the question that gets asked most often: "what did this revision change?"

The diff renders in two tabs.

Edits only is the default. It walks the document paragraph by paragraph and renders the changed regions as cards. Each card carries an Edit N label and a small in <heading> location (derived from the nearest preceding markdown heading in the previous version, or at top of document when no preceding heading exists; the heading text on the card preserves its leading # glyphs from the source). Adjacent paragraphs that were removed and added collapse into a single card with a Remove pill on one side and a Replace with pill on the other. Removals on their own render with a single Removed pill; additions render with a single Added pill.

Side-by-side is the structural view. Two columns, each carrying the full rendered markdown of one version, labeled with the version numbers. Both columns scroll independently. This is what you reach for when you want to read both versions in full rather than only the changed regions.

The diff itself is computed in the browser from the two bodies the detail page already loaded; the two bodies are fetched as two separate single-version reads when the page opens, and the comparison runs locally without re-fetching. Large bodies (over about 58 KB of text) overflow to blob storage on the version row and arrive transparently from there; you do not see the overflow from the detail page, but it is the storage layer's polite way of dealing with a one-megabyte spec body without losing it.

add_photo_alternate
Screenshot to capture
The Version detail page for v12 of an initiative: header showing 'v11 → v12' (with arrow glyph) on the left, an AI source badge, the editor name and timestamp, and a Restore button on the right; the Edits only tab active showing two cards stacked vertically: the first labeled 'Edit 1 · in ## Acceptance criteria' with a Remove pill (red tint) showing two removed lines and a Replace with pill (green tint) showing the rewritten three lines side by side; the second labeled 'Edit 2 · in ## Open questions' with a single Added pill showing one new question; the Side-by-side tab visible in the tab bar above.
save as: public/docs-media/version-diff-edits.png
Caption when added: A single AI version compared to its predecessor, rendered as per-change cards under the headings they landed in.
add_photo_alternate
Screenshot to capture
The Version detail page on the Side-by-side tab: header 'v11 → v12' with restore button on the right; two equal-width columns labeled 'v11' on the left and 'v12' on the right, each carrying the full rendered markdown of that version with the standard Disco Parrot prose styling; both columns independently scrollable; the changed paragraphs visible at the same scroll position in each column.
save as: public/docs-media/version-diff-side-by-side.png
Caption when added: The Side-by-side tab carries both bodies in full for reading rather than scanning for changes.

The first version of a record (v1) has no predecessor; the detail page renders the body directly with a caption naming the row as the initial version. The current version of the record (the latest row, which carries the Current pill in the list) hides the Restore button on the detail page because there is nothing to roll back to.

When the diff is empty

The change-note summary names the field that moved, and the body diff renders empty if the body did not change. This happens when a save updates a structured field outside the body, like a plan's status moving from In progress to Complete or an initiative's target date sliding by two weeks; the version row exists because a tracked field changed, but the side-by-side and Edits-only tabs both read "No content changes between these versions." This is the platform telling you the change was structured rather than editorial.

When you see the empty-state diff, look on the sidebar History section of the same detail page; the audit row for the same save carries the per-field detail (the field name and the from-and-to value). The version history captures the body that was; the sidebar History captures the field that moved.

Restoring an older version

Restore is the action you take when a version was where the record should be and the work that came after took it somewhere worse. It is non-destructive: restoring writes a new version that takes the older version's body and re-saves it as the current state.

BEFORE THE RESTOREv1USRv2AIv3USRTARGETv4AIv5AIv6USRv7AIv8USRRestore v3v9USRCURRENTRestored from v3
Restoring v3 writes a new v9 that carries v3's body. Versions 4 through 7 stay in the history exactly where they were; nothing is overwritten. The Current badge moves forward, never backward.

Restore is for the editorial record, not the workflow record. The mechanics below preserve the record's identity and its workflow assignments; only the editorial fields move.

The flow.

  1. Open the version row you want to restore. Click Restore in the row's overflow menu, or open the version detail page and click the Restore button there.
  2. A confirmation dialog opens. The title reads "Restore version n?"; the body explains that the action creates a new version with the content of vn and that the current version stays in history. An optional Change note field defaults to "Restored from vn" and accepts up to 500 characters if you want to record why.
  3. Click Restore. The server re-saves the record with the older body, the version history gets a new row (v(N+1) where N was the last version), the audit log records the change with restoredFrom: n in its payload, and the SSE stream notifies every connected client. Your view of the record refreshes with the restored body in place.
  4. An inline confirmation message reads "Restored from vn as v*(N+1)." If the canonical body of the older version is identical to the current state (because the record never changed after the version you targeted), the dedup gate skips the version write and the message reads "Already at vn*'s content; no new version created." Either way, the record is now at the older body.
warning
Restore bypasses the workflow transition rules

The platform treats a restore as an explicit admin "go back" and does not re-validate the older version's status against the current transition list. Restoring a plan whose v3 status was Draft into a record currently at Complete lands the plan at Draft regardless of whether complete → draft is a current transition. The audit log records the restore so the move is traceable; if your workflow's transition list is part of your team's policy, the restore can land outside it.

add_photo_alternate
Screenshot to capture
The Restore version dialog open over a dimmed Version history tab: the dialog title reads 'Restore version 7?', body text 'This will create a new version with the content of v7. The current version stays in history.', a single text input labeled 'Change note (optional)' with placeholder 'Restored from v7', a primary Restore button on the right and a secondary Cancel button on the left, the X close icon in the top-right corner; behind the dialog the v7 row is highlighted on the version list.
save as: public/docs-media/version-restore-confirm.png
Caption when added: The restore confirmation captures an optional reason and explains that the current version stays in history.

What restore preserves vs rewrites

Restore brings back the editorial record. Anything that is not part of the editorial record stays where it is. The specifics:

  • Initiative metadata preserved. Project assignment, start and target dates, sandbox profile (id, inputs, preset version), and integration links all stay where the current record had them.
  • Plan metadata preserved. Parent initiative, integration links, sandbox profile (id and inputs), the suppress-environment-health flag, and all six PR fields stay where the current record had them.
  • Plan workflow assignments are not touched. Sprint, sprint position, assignee, hour estimate, story-point estimate, plan type, and linked test cases are not captured on the snapshot, so a restore does not rewrite them. They stay at whatever the current plan record carries.

The honest framing: restore is for the spec the agent overwrote, not for the team's coordination state. If a Flow rewrote the spec on Tuesday and you restore it on Wednesday, the plan still belongs to whoever was assigned and still sits in whatever sprint it was scheduled for. The sprint and the assignee are not part of the editorial record being restored; the spec is.

Two worked examples

Sarah's weekly review

Sarah's CSV Export on the Reporting Page initiative has been edited several times during the week. She wants to see what the agent has done before she presents to Marcus.

She opens the initiative, clicks the Version history tab, and filters by Source = AI. The list shows three rows in the last week.

v14   AI   Sarah Chen   Thursday 14:30   Updated open questions
v12   AI   Sarah Chen   Wednesday 09:14  Updated spec
v9    AI   Sarah Chen   Tuesday   11:02  Updated dependencies

She opens v12's overflow menu, clicks View diff, and the detail page loads with the Edits-only diff against v11. Two cards: one paired card under "Acceptance criteria" where the agent tightened a vague line into three precise ones, and one Added card under "Open questions" where the agent surfaced a customer-fixture question Sarah had not noticed. She likes the acceptance-criteria rewrite. She likes the question. She closes the page.

She opens v14. The Edits-only tab shows three Added cards under "Open questions" from Thursday's chat session. One of them is a question she would rather the agent had not raised; it is too speculative for the presentation she is putting together. She does not need to restore (the question is just text she can edit out), but she wants to see exactly where it landed. The in ## Open questions prefix on the card tells her. She closes the diff, opens the initiative's main body, and edits the question out. The save lands as v15 (User, Sarah, "Updated open questions"). The history grows by one. The agent's v14 row stays in the history; if Sarah's edit turns out to be wrong, v14 is still there to restore.

The next morning, Sarah opens the same initiative again to find that an automated rewrite has produced a v16 she does not agree with: the agent has rewritten the executive summary in a register that does not match how she wants to present to Marcus. She decides the right state is v15. She opens v15's row menu and clicks Restore, accepts the default change note ("Restored from v15"), and clicks Restore on the dialog. The confirmation message at the top of the page reads "Restored from v15 as v17." The body of the initiative reverts to Sarah's v15 wording. The agent's v16 stays in the history, untouched; if she changes her mind, v16 is still there. The audit log on the sidebar History feed records the restore with restoredFrom: 15 in the payload.

Tom reviewing a plan before merging the PR

Tom is the engineer on the CSV download button plan. The PR is ready to merge; before he hits Approve on GitHub, he wants to verify what the agent did on the spec last week. He opens the plan, clicks the Version history tab, and reads the four rows since the plan was created.

v7   User   Priya Patel   Tuesday morning   Updated spec
v6   AI     Tom Asare     Monday evening    Updated steps
v5   User   Tom Asare     Friday afternoon  Created plan

Tom is interested in v6, the agent's edit on Monday during his chat session. The badge says AI, the editor is his own name (because the agent ran inside his chat), and the change note says "Updated steps." He opens View diff from v6's overflow menu. The Edits-only tab shows three paired cards under "Steps", each tightening a step's wording. Tom recognizes two of them as edits he would have made himself; the third tightens a step in a way he disagrees with. He scrolls down to read the original wording on the v5 side of the card and decides the agent over-constrained the step. He does not restore (Priya's v7 is built on top of the agent's edit and he does not want to lose her tightening); instead he opens the plan body, edits that one step back to something looser, and saves. The save lands as v8. The cascade of v5 (his draft), v6 (the agent's tightening), v7 (Priya's review), and v8 (his rebalance) reads as the working history of the plan; anyone walking the trail later sees who reached for what and when. He approves the PR.

The sidebar History section on the same plan carries every audit event, including the restores Sarah might perform on initiatives she watches, the status moves Priya made when she pushed the plan to In review, and the assignee change from Tom to Priya last Friday.

Practical playbook for weekly review

The single highest-value routine for a team running with agents in the loop is a weekly scan of the records the agent touched. The playbook is a few minutes per record once you have the rhythm.

Filter Source = AINarrow to agent editsScan change notesLook for the surprisesSurprising note?Open the diffEdits-only tab firstWent sideways?Restore older versionWrite-forward; v(n+1)yesyesnoMove onnoEdit forward or close
The weekly review playbook as a decision flow. Filter by source, scan the change notes, open the diffs that surprise you, restore the rare ones that went sideways. Takes a few minutes per record once you have the routine.

1. Filter to Source = AI

Open the record's Version history tab, open the FilterPanel, set Source to AI. The list narrows to the agent's contributions on this record. For a workspace-wide view across many records, the audit log with the same editSource: ai filter is the right surface.

2. Scan the change notes

"Updated spec", "Updated open questions", "Updated 4 fields" are enough for a quick scan. The notes that surprise you are the ones to open. A change note that says "Updated 4 fields" but you do not recall a major edit landing is the most reliable signal that something moved you should read.

3. Open the diffs that matter

For each surprising row, open the overflow menu and click View diff. The Edits-only tab shows you the per-change cards. Most agent edits are tightening or expansion; you read the cards, agree or disagree, and close. When the diff is empty (the change was structured rather than editorial), the sidebar History section carries the per-field detail for the same save.

4. Restore the rare ones that went sideways

Rare in practice; the change you make by editing the record after the agent saved tends to be enough. When you do restore, your overrides on top of the restored version land as the next version, and the agent's row stays in the history. The restore-bypass-transitions caveat above applies; verify the status you are restoring to is one your team is comfortable with the platform setting outside the transition rules.

5. Cross-reference the Session that produced a save

For an AI version row, the matching Sessions transcript shows the tool calls, file edits, and model output the agent ran to produce the save. There is no in-product link from a version row to the session today; locate the matching session by the editor name on the version row and the timestamp, both of which appear on the Sessions index. Three records together (version history, sidebar History feed, Sessions transcript) tell the full story from "this is the body the agent saved" through "the save happened, here is the audit row" to "here is the run that produced it."

Who sees the change

A save writes a version row, broadcasts a <entity>.updated event on the SSE stream, and lands an audit row on the sidebar History feed. Connected clients on the same record see the new version appear in the list within seconds without a page refresh. A restore rides the same path: the new v(N+1) appears in the list, the audit row records restoredFrom, the SSE event fires. There is no notification class specific to a version append or a restore today; if a workspace has notification rules that filter on editSource: ai (for instance, "tell me when the agent touches my owned initiative"), those rules will fire on the AI version append and on the restore both, since the restore audit row also flows through the standard update notification path.

Why version history is shaped this way

For a senior PM evaluating Disco Parrot, the question version history answers is whether an AI authoring tool can be trusted in your real work. The answer turns on three guarantees the platform makes and this surface delivers: every meaningful save is captured as a full body snapshot, the agent's edits carry the same attribution shape as a human's so a filter has something honest to read, and a restore writes forward rather than overwriting so the history grows by one and nothing you wanted is ever lost behind something the agent did. The dedup gate is what keeps the list scannable; the carve-outs are what keep the restore safe.

For your team day to day, the surface is the editorial trail. The version history captures the body; the sidebar History captures the events; the Sessions transcript carries the agent run. Three records together tell the full story; each one is durable, filtered, and answerable. The agent rewriting your initiative is not a black box.

Permissions

Reading the version list and opening a past version goes with the entity's read scope (initiatives.read, plans.read). Restoring goes with the entity's manage scope (initiatives.manage, plans.manage). The diff view is computed in the browser, so it inherits the read scope automatically; a user who can read can compare any two consecutive versions of a record. A user who can read but not manage can audit without being able to change anything.

A restore is recorded in the audit log as an update with restoredFrom: n in the payload and the change-note prefixed with "Restored from v". The audit row is implicitly marked as a user edit (the human pressed Restore), which is what you want when filtering the audit log by source.