Skip to content

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

Open
b1ngsha wants to merge 6 commits into
oceanbase:masterfrom
b1ngsha:feat/dashboard-revision-rollback
Open

b1ngsha wants to merge 6 commits into
oceanbase:masterfrom
b1ngsha:feat/dashboard-revision-rollback

Conversation

@b1ngsha

@b1ngsha b1ngsha commented Sep 30, 2026

Copy link
Copy Markdown

Which issue or RFC does this PR close?

Closes #1634

Rationale for this change

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)
  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

Memory stays history and compare only. Profile, Prompt, Experience, Skill, Handoff, and Topic Memory can roll back.

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. Old rows are not backfilled.
  • Replace for those six families accepts optional restored_from_revision and reason. 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.
  • OpenAPI, generated models, dashboard docs, and RFCs 1468, 1485, and 1629 match this contract. The new artifact columns are nullable and additive.

Are there any user-facing changes?

Yes. Dashboard pages for these families show revision history, comparison, and, where allowed, rollback. 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.

How was this change tested?

  • uv run pytest tests/test_dashboard_profile_handoff.py tests/builtin/persistence/test_prompt_records.py tests/e2e/test_profile_api.py
  • Local dashboard with access control disabled: Profile revision 1 shows the reason field and confirm button; comparing revision 2 shows the diff; comparing a missing revision reports that it could not be compared.

AI usage statement

Used Cursor with Grok 4.7 to implement the revision history, comparison, and rollback flow, update the contract and docs, and add the regression tests.

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>
from powercontext.builtin.persistence.sources import SourceRepository
from powercontext.builtin.persistence.sqlite import SQLiteConfig, SQLiteProfile
from powercontext.builtin.persistence.tables import BUILTIN_TABLES
from powercontext.builtin.persistence.tables import ARTIFACTS_TABLE, BUILTIN_TABLES, CREATED_BY_LENGTH

@AlexStocks AlexStocks Oct 3, 2026 •

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.

承接说明:Teingi 已在同一行提过同一问题(#discussion_r4157459143,[P2])。本条不是新发现,而是补上 CI 日志级证据并把影响面定级到 P1:该 ImportError 让 tests (3.11)/(3.12)/(3.13)/(3.14) 四个作业在收集阶段就中断,一条测试都不执行(相当于这四个作业当前不提供任何信号),故建议按「必须清零」而非「普通待办」处理。修复方式仍是同一处:把常量定义出来并让导入与断言同源。

[P1] 导入的 CREATED_BY_LENGTH 在 tables.py 中不存在,tests 作业在收集阶段整体中断

tables.py 把 created_by 写成字面量 Column("created_by", String(2048), nullable=True)(src/powercontext/builtin/persistence/tables.py:199),从未定义 CREATED_BY_LENGTH;本行是新加的导入,第 287 行的 assert column == CREATED_BY_LENGTH 也是新加的。

后果不是"某个用例失败",而是 pytest 在导入阶段就报错,tests 作业里一条测试都不会执行。head fc3767f2 的 CI 日志(tests (3.13)):

E   ImportError: cannot import name 'CREATED_BY_LENGTH' from 'powercontext.builtin.persistence.tables' (/home/runner/work/powercontext/powercontext/src/powercontext/builtin/persistence/tables.py)
=========================== short test summary info ============================
ERROR tests/builtin/persistence/test_prompt_records.py
!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!
============================= 2 errors in 12.18s ==============================
make: *** [Makefile:43: unit-test] Error 2

tests (3.11) / (3.12) / (3.14) 同因失败;quality 的 ty 步骤另报 error[unresolved-import]: Module powercontext.builtin.persistence.tables has no member CREATED_BY_LENGTH。

本机在 head 上复现(隔离 venv,uv sync --frozen):

$ pytest tests/test_dashboard_profile_handoff.py tests/builtin/persistence/test_prompt_records.py tests/e2e/test_profile_api.py -q
ERROR tests/builtin/persistence/test_prompt_records.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!

这正是 PR 描述 "How was this change tested" 里给出的那条命令,因此该验证按当前 head 无法通过。

建议:本仓库的列宽常量集中在 powercontext/limits.py(tables.py:36-54 逐个导入 MAX_*_LENGTH),在那里补 CREATED_BY_LENGTH = 2048,并让 tables.py:199 改成 String(CREATED_BY_LENGTH),使第 287 行的断言与列定义共享同一个来源。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in c2afbc7, using the shared constant in limits.py as suggested. Thanks for the CI log evidence.

@@ -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
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.

UsageReportingStructuredGenerator,
)
from powercontext.builtin.persistence.dream_schema import ensure_dream_schema
from powercontext.builtin.persistence.revision_meta import ensure_revision_metadata_schema

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] 新增导入的插入位置触发 ruff I001,quality 的 ruff 钩子因此失败

本行插在 dream_schema 与 memory_index 之间,破坏了该导入块的有序性。ruff check 报 I001 [*] Import block is un-sorted or un-formatted(定位在块首 composition.py:17),并把它自动修掉,prek 因此判失败,quality 作业在 Makefile:30 挂掉:

ruff check...............................................................Failed
- hook id: ruff-check
- files were modified by this hook

  Found 1 error (1 fixed, 0 remaining).

ruff format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

  3 files reformatted, 961 files left unchanged

ruff format 要重排的 3 个文件都是本 PR 的:src/powercontext/builtin/persistence/revision_meta.py、tests/builtin/persistence/test_prompt_records.py、tests/test_dashboard_profile_handoff.py。

本机在 head 上以无副作用方式复现:

$ ruff check --no-fix --output-format=concise .
src/powercontext/builtin/runtime/composition.py:17:1: I001 [*] Import block is un-sorted or un-formatted

$ ruff format --check .
3 files would be reformatted, 951 files already formatted

建议把本行移到 memory_index 之后(revision_meta 的字母序位置),并对上述 3 个文件跑一次 ruff format。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. The import now sits in sorted position, and I ran ruff format on the three files you listed. prek run -a is clean locally.

columns = await connection.run_sync(lambda sync: inspect(sync).get_columns(ARTIFACTS_TABLE.name))
column = next((item for item in columns if item["name"] == "created_by"), None)
length = None if column is None else getattr(column["type"], "length", None)
target = ARTIFACTS_TABLE.c.created_by.type.length

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] TypeEngine.length 触发 ty 报错,quality 的 ty 钩子失败(head 上合计 6 条诊断)

ARTIFACTS_TABLE.c.created_by.type 的静态类型是 TypeEngine[Any],不含 length:

error[unresolved-attribute]: Object of type `TypeEngine[Any]` has no attribute `length`
   --> src/powercontext/builtin/persistence/revision_meta.py:152:14
  152 |     target = ARTIFACTS_TABLE.c.created_by.type.length

(CI 的行号是它的 ruff 钩子先重排过该文件之后的;未重排时对应 head 的 157 行。同一写法在 tests/builtin/persistence/test_prompt_records.py:284 也各报一条。)

运行期没有问题——created_by 确实是 String(2048)——但 quality 的 ty 钩子是硬失败。head 上 ty 合计 6 条诊断,除上面两条外还有:

  • tests/builtin/persistence/test_prompt_records.py:325 merged_source_revision 实参类型不符(Command 不是 BaseModel)
  • tests/test_dashboard_profile_handoff.py:223 在可调用对象上取 .state
  • tests/test_dashboard_profile_handoff.py:407 _memory_text_rows 传入 Api 而非 DashboardAPI

本机 ty check 在 head 上同样得到 6 条,与 CI 一致。

建议:先用 isinstance(ARTIFACTS_TABLE.c.created_by.type, String) 收窄再取 .length(或与 powercontext/limits.py 里的常量比较),并顺带清掉测试里的 4 条。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. The migration compares against the shared constant instead of .type.length. The other four diagnostics are also gone: a BaseModel subclass for the fake command, and cast for the app state and the DashboardAPI stub. ty check reports no diagnostics.

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
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.

"prompt_key",
"prompt_revision",
"prompt_revision_cursor",
"topic_revision_cursor",

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] Clear the revision cursor when switching Topic Memory artifacts

After paging the history of Topic A (8 revisions), clicking Topic B in the inventory keeps A's topic_revision_cursor in B's URL. Revision cursors are bound to artifact_id, so B's revisions request raises InvalidCursorError; load_history() catches it and returns None. The page still responds 200 but loses its entire history/compare panel. Reproduced through the Dashboard HTTP routes with SQLite on d4f5368f; opening B without that cursor restores the panel. Please clear the history cursor when topic_artifact changes and cover this navigation sequence.

@hidb4ai

hidb4ai commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

please update & resolve conflicts

@b1ngsha

b1ngsha commented Oct 11, 2026

Copy link
Copy Markdown
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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants