Skip to content

feat(dashboard): browse, compare, and roll back artifact revisions - #1813

Merged
hidb4ai merged 12 commits into
oceanbase:masterfrom
b1ngsha:feat/dashboard-revision-rollback
Oct 19, 2026
Merged

hidb4ai merged 12 commits into
oceanbase:masterfrom
b1ngsha:feat/dashboard-revision-rollback

Conversation

@b1ngsha

@b1ngsha b1ngsha commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Which issue or RFC does this PR close?

Closes #1634

Rationale for this change

The Dashboard can browse an artifact's revisions, open one exact revision, compare two revisions, and roll an older revision forward. Rollback reuses conditional replace (If-Match: "revision:{n}"). There is no new rollback route and no new permission.

open revision
  show history and exact content
  compare selected revision with any other revision
confirm rollback(reason)            # not offered for Atomic Memory
  replace(head, content of source, restored_from_revision, reason)
    reject when the head moved, the source is missing, or the content does not match
  append a new revision
  previous revisions stay readable

Profile, Prompt, Experience, Skill, Handoff, and Topic Memory support history, comparison, and rollback. Each Atomic Memory shows and compares its own revisions read-only. Restoring Atomic Memory is a follow-up that will go through the existing restoration preview/restore flow, so the full set of affected memories is shown before confirmation.

What changes are included in this PR?

  • Every new artifact revision stores created_at. A rollback revision also stores the actor, restored_from_revision, and reason. Server startup adds the four nullable columns to an existing pc_artifacts table on SQLite and MySQL/OceanBase, the same way it adds other compatibility columns. Existing rows are not backfilled and read back as unknown history. No Alembic revision is added, so this PR does not take a migration number.
  • Replace for the six rollback families accepts optional restored_from_revision and reason. Every other family, including Atomic Memory, rejects both. A source revision requires a non-blank reason of at most 1024 characters. The submitted content must match that revision and differ from the current head.
  • Dashboard history stays on the existing pages. Compare accepts any revision number. The rollback form is hidden without replace permission. With access control disabled, the form is shown. POST /dashboard/rollback requires a same-origin Origin header.
  • The Notes page shows the selected Atomic Memory's revision history through the Artifact revision APIs. Revision links open that exact revision, and comparison diffs kind and text. No rollback form is shown.
  • A revision cursor is dropped when a link targets a different artifact or clears the selection, so switching Topic Memory, Prompt, Experience, Skill, Handoff, or Atomic Memory, paging the Notes directory, or changing the Notes state no longer loses the history panel.
  • An instruction-only legacy Skill revision is compared in its packaged form on both sides, so it can be rolled forward. Opening a missing Prompt revision shows the read error instead of the current configuration.
  • OpenAPI, generated models, desktop bindings and IPC types, the Dify tool contract, dashboard docs, and RFCs 1468, 1485, and 1629 match this contract. The desktop contract generator accepts a single-member allOf reference, which the new nullable created_by field uses.

Are there any user-facing changes?

Yes. Dashboard pages for these families show revision history, comparison, and, where allowed, rollback. The Notes page shows read-only history and comparison for each Atomic Memory. Revision reads and replace responses gain nullable created_at, created_by, restored_from_revision, and reason. Existing clients can ignore the new fields. Rollback does not delete or rewrite older revisions, and it does not change a Skill's lifecycle state.

Upgrading adds nullable columns only, at Server startup.

How was this change tested?

  • uv run prek run -a (ruff, format, ty)
  • make unit-test
  • make contract-test
  • pnpm lint, pnpm ipc:check, and cargo check in desktop/
  • integrations/dify/generate_contract.py --check
  • New and updated tests:
    • Atomic Memory history and comparison are read-only and open exact revisions; Atomic Memory replace rejects rollback fields.
    • A Topic Memory revision cursor does not follow a link to another topic, and a Notes directory link does not keep the previous note's revision cursor.
    • A database whose pc_artifacts lacks the metadata columns still reads and replaces a Profile after restart.
    • A legacy instruction-only Skill revision rolls forward; a missing Prompt revision shows an error and no controls.
    • The six-family rollback, cross-origin rejection, and actor length tests carry over.
  • The startup column upgrade was exercised on SQLite only. MySQL/OceanBase relies on the code-seekdb and OceanBase CI jobs.

AI usage statement

Used Cursor with Grok 4.7 to implement the original revision history, comparison, and rollback flow. Used Claude Code (Claude Opus 5.5) to merge master, rebuild Memory history on Atomic Memory, add the startup column upgrade, fix review findings, and update the contract, docs, and tests.

🤖 Generated with Claude Code

Record created_at on every new revision, and keep the rollback source, reason, and actor on the replacement revision. Dashboard history reuses conditional replace instead of a dedicated rollback route.

Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant

CLAassistant commented Sep 30, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

- Increased the length of the `created_by` column in the artifacts table to accommodate longer principal IDs.
- Implemented a new function to widen the `created_by` column if necessary during schema updates.
- Updated the dashboard to handle historical notes more effectively, including displaying notes from older memory revisions and managing pagination.
- Added a new label for truncated comparison results in the dashboard UI.
- Improved the handling of memory artifact IDs in links and context management.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread tests/builtin/persistence/test_prompt_records.py Outdated
@@ -0,0 +1,108 @@
{% if history %}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 新模板缺少仓库统一的 OceanBase 授权头,Check License Header 作业失败

head fc3767f2 的 Check License Header(skywalking-eyes)日志直接点名本文件:

ERROR the following files don't have a valid license header:
src/powercontext/server/dashboard/templates/components/revision-history.html
ERROR one or more files does not have a valid license header

同目录的其它组件都以 Apache 头开始(components/brand.html、components/comparison-chart.html、components/evidence-modal.html、templates/topics.html …),本文件是本 PR 新增文件中唯一缺头的。

建议在首行前补上与兄弟模板一致的 HTML 注释头:

<!--
Copyright (c) 2026 OceanBase.
...
-->

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the standard Apache header to revision-history.html.

Comment thread src/powercontext/builtin/runtime/composition.py Outdated
Comment thread src/powercontext/builtin/persistence/revision_meta.py Outdated
b1ngsha and others added 2 commits October 9, 2026 22:38
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rollback

Define the actor column width in limits so tests and DDL share one source,
add the missing license header, restore import order and formatting, fix the
ty diagnostics, and clear the retained note citation when switching Memory
revisions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@b1ngsha

b1ngsha commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Merged master into the branch and resolved the conflicts in content.py and notes.html.

b1ngsha and others added 2 commits October 10, 2026 11:05
…tdates the revision

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@hidb4ai hidb4ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one reproducible Topic Memory navigation issue on d4f5368f.

Comment thread src/powercontext/server/dashboard/routes.py
@hidb4ai

hidb4ai commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

please update & resolve conflicts

…artifact

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@b1ngsha

b1ngsha commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Merging the latest master conflicts in 14 files, and not mechanically: #1857/#1880 removed the legacy Memory collection in favor of Atomic Memory, and this PR's Memory history and compare is built on that collection (manifest, memory_history, per-note revision resolution). The other six families (Profile, Prompt, Experience, Skill, Handoff, Topic Memory) don't conflict in substance.

I plan to rework Memory history and compare on Atomic Memory in this PR, aligned with the restoration flow now on master, instead of splitting it out. Before I start, could you confirm the scope? In particular:

  • Should history and compare apply to each Atomic Memory entry's own revisions?
  • Should dashboard rollback for Memory go through the existing restoration preview/restore endpoints, or stay history and compare only as in the current description?

I'll update the PR description and RFC notes once the scope is settled.

@hidb4ai

hidb4ai commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

please update & resolve conflicts

@AlexStocks AlexStocks added the 1.4.0 v1.4.0 release tracking label Oct 14, 2026
@hidb4ai

hidb4ai commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

please update & resolve conflicts

@hidb4ai

hidb4ai commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Regarding your scope questions, please proceed in this PR with the following scope:

  • Apply history and comparison to each Atomic Memory artifact's own revisions, using the current Artifact revision APIs. Remove the dependency on the legacy Memory collection and manifest model.
  • Keep Atomic Memory history and comparison read-only in this PR. Restoration should be a follow-up using the existing preview/restore flow, with the full set of affected artifacts shown before confirmation.
  • Keep history, comparison, and rollback for Profile, Prompt, Experience, Skill, Handoff, and Topic Memory, adapting the implementation to the current master contracts.

Please update from master, resolve the conflicts, and address the Topic revision-cursor issue with a regression test. Update the PR description and relevant docs/RFC notes to match this scope, then run the relevant checks. We can review the updated implementation once those changes are ready.

@hireview001 hireview001 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Resolve merge conflicts with the target branch.

Comment thread src/powercontext/builtin/persistence/rollback.py Outdated
Comment thread src/powercontext/server/dashboard/content.py Outdated
b1ngsha and others added 2 commits October 17, 2026 11:22
Resolve the conflicts with Atomic Memory. Each Atomic Memory now shows and
compares its own revisions read-only on the Notes page, and replace rejects
rollback fields for it. The legacy collection history and manifest compare
are gone. Revision metadata columns move into migration p0005 instead of
startup DDL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@b1ngsha

b1ngsha commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Updated from master and resolved the conflicts, following the scope in your comment:

  • Each Atomic Memory shows and compares its own revisions on the Notes page through the Artifact revision APIs. It is read-only: no rollback form is shown, and replace rejects restored_from_revision and reason for Atomic Memory. The legacy collection history and manifest compare are removed.
  • Profile, Prompt, Experience, Skill, Handoff and Topic Memory keep history, comparison and rollback on the current master contracts.
  • The revision metadata columns now come from migration p0005 (SQLite and MySQL/OceanBase) instead of startup DDL.
  • The Topic revision-cursor issue is fixed with a regression test (replied in the thread).
  • The PR description and dashboard docs match this scope.

Local checks: prek run -a, make contract-test, desktop pnpm lint and cargo check, and the dashboard and persistence suites pass. The MySQL/OceanBase migration still needs the CI jobs, since I could not run those backends locally.

…t revisions

Compare instruction-only Skill revisions in their packaged form on both
sides, so a legacy revision can be rolled forward. Opening a missing Prompt
revision shows the read error instead of the current configuration and
offers no history or rollback controls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@hidb4ai hidb4ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 8088d6b5. The original Topic cursor case is fixed, and per-artifact Atomic Memory history/comparison with a read-only boundary matches the agreed scope. Three remaining issues need addressing before merge: the existing-database upgrade path, Notes revision-cursor cleanup, and downstream generated contracts.

The upgrade and Notes failures were reproduced through public HTTP routes with isolated SQLite databases. OceanBase/SeekDB behavior was not exercised locally.

Comment thread src/powercontext/builtin/persistence/tables.py
Comment thread src/powercontext/server/dashboard/routes.py Outdated
Comment thread openapi/powercontext.yaml
Comment on lines +6041 to +6045
created_at:
type: string
format: date-time
nullable: true
description: When this revision was committed. Absent for revisions stored before this field existed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Regenerate the downstream revision contracts

Adding these ArtifactRevision fields also changes the Dify tool response schemas and Rust-exported Desktop IPC types. Dify's generate_contract.py --check fails for pc_memory_get.yaml, pc_memory_revise.yaml, and contract.json; the same generator reports no drift with base 06794f6's OpenAPI schema. Desktop's pnpm ipc:check also fails because desktop/ui/src/generated/ipc.ts still declares ArtifactRevision without the four new fields. Both failures are visible in the Dify CI job and Desktop CI job. Please regenerate and commit these downstream outputs alongside the OpenAPI/Rust updates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regenerated. The Dify tool contract (contract.json, pc_memory_get.yaml, pc_memory_revise.yaml) and desktop/ui/src/generated/ipc.ts now include the four ArtifactRevision fields. generate_contract.py --check and pnpm ipc:check both pass locally.

…ursors

Replace migration p0005 with a startup step that adds the nullable revision
metadata columns to an existing pc_artifacts table, matching how other
compatibility columns are added. This keeps databases created by the current
release readable and frees the migration number.

Drop a revision cursor whenever a link clears the selected artifact, so Notes
directory paging and state changes keep the history panel. Regenerate the
Dify tool contract and the desktop IPC types for the new revision fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@b1ngsha
b1ngsha requested a review from yihong0618 as a code owner October 17, 2026 13:15

@hidb4ai hidb4ai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Column("artifact_id", identity_string(MAX_ARTIFACT_ID_LENGTH), primary_key=True),
Column("revision", Integer, primary_key=True),
Column("content", _canonical_payload_type(), nullable=False),
Column("created_at", DateTime(timezone=True), nullable=True),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Upgrade metadata before offline projection rebuilds

On a pre-upgrade database, powercontext server atomic-memory-rebuild-projection now fails because Artifact reads select these new columns, while the command bypasses the startup schema upgrader. Apply the nullable-column upgrade in the writable maintenance path before reading artifacts so offline repair remains usable during upgrades.

if family == "experience":
return {name: content.get(name) for name in _EXPERIENCE_FIELDS}
if family == "skill":
return {name: content.get(name) for name in _SKILL_FIELDS}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Normalize legacy Skill revisions in dashboard comparisons

Comparing an instruction-only legacy Skill revision with its equivalent packaged revision reports false changes and enables a rollback that validation rejects as unchanged. Normalize legacy content to packaged form in both dashboard comparison and same_content checks.

<div class="card-body">
<dl class="row mb-3"><dt class="col-sm-4">{{ t.prompt_revision }}</dt><dd class="col-sm-8">{{ prompt.artifact.revision if prompt.artifact else t.prompt_auto }}</dd><dt class="col-sm-4">{{ t.prompt_demonstrations }}</dt><dd class="col-sm-8">{{ prompt.effective.demonstrations|length if prompt.effective else 0 }}</dd></dl>
{% if prompt.effective %}<h3 class="h4">{{ t.prompt_instructions }}</h3><pre class="page-body-pre text-break">{{ prompt.effective.instructions }}</pre>{% elif prompt.builtin %}<h3 class="h4">{{ t.prompt_instructions }}</h3><pre class="page-body-pre text-break">{{ prompt.builtin.instructions }}</pre>{% endif %}
{% set opened = prompt_opened if prompt_opened and prompt.prompt_key == prompt_key else none %}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Display the selected Prompt revision's mode

When a Prompt's head is auto and an older custom revision is opened, the page shows historical instructions but labels their mode using the current head; the reverse transition is also mislabeled. Compute opened before rendering the header and use its content mode for the badge.

@hidb4ai
hidb4ai merged commit 4a200f4 into oceanbase:master Oct 19, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.4.0 v1.4.0 release tracking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support viewing, switching, and rolling back artifact revisions in Dashboard

5 participants