feat: Support version history and rollback for traffic rules#1477
feat: Support version history and rollback for traffic rules#1477mochengqian wants to merge 43 commits into
Conversation
Running the §9.4 smoke drill end-to-end uncovered three real defects that the unit suite did not catch: - RuleVersionSubscriber recorded a duplicate UPSTREAM row whenever the registry echoed back a no-op change identical to the latest ledger row (typically right after BOOTSTRAP). Now dedupes upstream events whose content hash already matches the current head, with an explicit test in versioning_test.go. - writeVersioningResp mapped every bizerror to HTTP 200/UnknownError; bizerror.InvalidArgument (eg. empty rollback reason) now returns HTTP 400 with its original code so the frontend can act on it. Covered by a new handler/rule_version_test.go. - The 409 VERSION_CONFLICT toast auto-dismissed after the default duration; users could miss the Reload button entirely. Pinned with duration: 0 so the notification stays until acknowledged.
The §9.4 smoke drill expectation "after rollback, the rule should look the same on UI refresh" exposed a pre-existing edit-form regression: rollback was correct at the ledger and ZK levels, but the edit form silently dropped `priority`, `force`, and (for condition routes) `configVersion` because they were neither rendered in the GET response nor re-sent on save. This is not caused by versioning, but a true round-trip is the first flow that forces every field through the loop. Adds the missing fields to ConditionRuleResp / TagRuleResp on the backend, and reads/writes them in updateByFormView.vue on the frontend so a "save → rollback → reload" cycle is now lossless.
Add /task_plan.md /findings.md /progress.md to .gitignore so the planning-with-files workflow does not leak per-developer working memory into the repo.
6deb25e to
9596f7e
Compare
…nd-rollback-for-traffic-rules feat: support traffic rule version history and rollback
2471544 to
d2a6ddc
Compare
990402d to
b525c2b
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces immutable version history, diff viewing, and rollback for governor-managed traffic rules (Condition Route, Tag Route, Dynamic Config) in Dubbo Admin, along with optimistic concurrency control to prevent silent overwrites.
Changes:
- Backend: adds a versioning subsystem (stores, service, subscriber, bootstrap, intent workflow) plus REST endpoints for listing/getting/diffing/rollback and intent repair/abandon.
- Frontend: adds shared history/diff/rollback UI components, wires them into rule pages, and threads
expectedVersionIdthrough mutations with 409 conflict handling. - Store/core plumbing: adds
ListResources()and aligns empty-indexListByIndexessemantics across memory/GORM stores.
Reviewed changes
Copilot reviewed 62 out of 62 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| ui-vue3/src/views/traffic/tagRule/tabs/updateByYAMLView.vue | Adds expectedVersionId concurrency + version-error notifications for YAML editing. |
| ui-vue3/src/views/traffic/tagRule/tabs/updateByFormView.vue | Threads version precondition into form updates and handles version conflicts. |
| ui-vue3/src/views/traffic/tagRule/tabs/formView.vue | Integrates history panel entry point and current version badge. |
| ui-vue3/src/views/traffic/tagRule/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/routingRule/tabs/updateByYAMLView.vue | Adds expectedVersionId concurrency + version-error notifications for YAML editing. |
| ui-vue3/src/views/traffic/routingRule/tabs/updateByFormView.vue | Threads version precondition into form updates and handles version conflicts. |
| ui-vue3/src/views/traffic/routingRule/tabs/formView.vue | Integrates history panel entry point and current version badge. |
| ui-vue3/src/views/traffic/routingRule/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue | Adds history panel + expectedVersionId concurrency for YAML-based configurator edits. |
| ui-vue3/src/views/traffic/dynamicConfig/tabs/formView.vue | Adds history panel + expectedVersionId concurrency for form-based configurator edits. |
| ui-vue3/src/views/traffic/dynamicConfig/index.vue | Uses current version id precondition on delete and conflict notifications. |
| ui-vue3/src/views/traffic/_shared/ruleVersion.ts | Shared helpers for fetching current version state and displaying conflict/pending notifications. |
| ui-vue3/src/views/traffic/_shared/RuleHistoryPanel.vue | New history panel orchestrating list/view/diff/rollback flows. |
| ui-vue3/src/views/traffic/_shared/RuleHistoryDrawer.vue | Drawer UI for version timeline and actions. |
| ui-vue3/src/views/traffic/_shared/RuleDiffEditor.vue | Monaco diff editor wrapper for version comparisons. |
| ui-vue3/src/mocks/handlers/tagRule.ts | MSW: simulates version conflicts/pending and records admin writes for tag rules. |
| ui-vue3/src/mocks/handlers/routingRule.ts | MSW: simulates version conflicts/pending and records admin writes for condition rules. |
| ui-vue3/src/mocks/handlers/dynamicConfig.ts | MSW: simulates version conflicts/pending and records admin writes for configurators (with URL decoding). |
| ui-vue3/src/mocks/handlers/ruleVersion.ts | MSW: full in-browser mock ledger + diff/rollback + intent repair/abandon flows. |
| ui-vue3/src/mocks/handlers.ts | Registers ruleVersion MSW handlers. |
| ui-vue3/src/base/http/request.ts | Suppresses generic error toasts for version conflict/pending so the dedicated notifications can be used. |
| ui-vue3/src/api/service/traffic.ts | Adds versioning API surface + threads expectedVersionId into existing mutations. |
| pkg/store/memory/store.go | Adds ListResources() with sorting and error propagation. |
| pkg/store/memory/store_test.go | Tests ListResources() sorting and empty-index semantics. |
| pkg/store/dbcommon/gorm_store.go | Adds ListResources() and aligns empty-index semantics with memory store. |
| pkg/store/dbcommon/gorm_store_test.go | Tests empty-index behavior and ListResources() ordering. |
| pkg/core/versioning/types.go | Defines version/meta/intent models, enums, and shared errors. |
| pkg/core/versioning/subscriber.go | Records upstream changes and attaches events to matching admin intents. |
| pkg/core/versioning/store.go | In-memory immutable ledger store + intent lifecycle + retention trimming + dedup. |
| pkg/core/versioning/store_gorm.go | GORM-backed immutable ledger store + intent lifecycle + trimming + dedup. |
| pkg/core/versioning/store_gorm_test.go | Tests GORM store migration, trimming, dedup, intents, and concurrency monotonicity. |
| pkg/core/versioning/service.go | Versioning service API: list/get/diff, expected-version check, intents, repair helpers. |
| pkg/core/versioning/normalize.go | Canonical JSON normalization and sha256 hashing for dedup and intent matching. |
| pkg/core/versioning/e2e_rollback_drill_test.go | End-to-end drill covering bootstrap, admin edit, upstream push, rollback, and retention trim. |
| pkg/core/versioning/component.go | Runtime component wiring: store selection, event subscriptions, startup repair/bootstrap scan. |
| pkg/core/store/store.go | Extends ResourceStore interface with ListResources(). |
| pkg/core/manager/manager.go | Adds List(rk) to manager via store’s ListResources(). |
| pkg/core/manager/manager_test.go | Verifies manager List returns sorted resources. |
| pkg/core/events/eventbus.go | Adds SourceRegistryContextKey and clarifies event context immutability expectations. |
| pkg/core/discovery/subscriber/zk_config.go | Adds ZK delete nil-guard and emits source-registry context for version attribution. |
| pkg/core/discovery/subscriber/zk_config_test.go | Tests delete path uses local old rule and missing-local-rule is a noop. |
| pkg/core/bootstrap/bootstrap.go | Registers the versioning component as an optional bootstrap component. |
| pkg/console/service/tag_rule.go | Wraps tag rule mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/configurator_rule.go | Wraps configurator mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/condition_rule.go | Wraps condition rule mutations with intent-based versioning and expectedVersionId checks. |
| pkg/console/service/rule_version.go | Adds console-layer services for version list/get/diff/rollback and intent repair/abandon. |
| pkg/console/service/rule_version_test.go | Covers conflict handling, pending intents, rollback paths, delete marker semantics, and intent recovery. |
| pkg/console/model/tag_rule.go | Exposes force and priority fields in tag rule responses. |
| pkg/console/model/condition_rule.go | Exposes force and priority fields in condition rule responses. |
| pkg/console/handler/tag_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/configurator_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/condition_rule.go | Adds mutation options parsing and maps versioning conflicts/pending to 409. |
| pkg/console/handler/rule_version.go | Implements versioning REST endpoints and error-to-HTTP mapping. |
| pkg/console/handler/rule_version_test.go | Tests status/code mapping for InvalidArgument and pending intent id propagation. |
| pkg/console/router/router.go | Registers versioning endpoints under the existing traffic rule routes + intent ops routes. |
| pkg/console/context/context.go | Exposes RuleVersioning service from the runtime component. |
| pkg/console/component_test.go | Ensures auth middleware blocks rollback endpoint without a session. |
| pkg/config/versioning/config.go | Adds versioning config block, defaults, sanitize and validation. |
| pkg/config/versioning/config_test.go | Tests defaults, sanitize, and validation for versioning config. |
| pkg/config/app/admin.go | Adds versioning config to AdminConfig and ensures defaults for nil config blocks. |
| pkg/config/app/admin_test.go | Tests AdminConfig defaulting behavior when Versioning config is missing. |
Comments suppressed due to low confidence (1)
ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue:205
- The
catchblock only callsnotifyRuleVersionError(...)and then swallows the exception. This can hide non-versioning failures (notably YAML parse errors fromyaml.loador other runtime exceptions) because the request interceptor won’t run for those cases. Consider rethrowing or showing a generic error toast whennotifyRuleVersionErrorreturns false.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…history-and-rollback-for-traffic-rules Revert "feat: support traffic rule version history and rollback"
Remove unused ResourceManager.List() method and simplify versioning API by converting Service from interface to concrete type. Changes: - Remove ResourceManager.List() from manager interface and all implementations - Remove versioning.Service interface, rename service struct to Service (exported) - Add GetStore() to ResourceManager for bootstrap access to rule stores - Add public methods to versioning.Service: GetIntent, MarkIntentFailedWithReason, CurrentMeta, GetVersion (replaces Store() accessor) - Update all callers to use *versioning.Service instead of interface - Update component.Service() to return *Service - Fix all tests to implement GetStore (returns nil for test fakes) All tests pass: - pkg/core/versioning: 23/23 pass - pkg/console/service (RuleVersion): 11/11 pass Related: apache#1477 Phase 1.2 (manager.List removal, Service interface cleanup)
Define RuleVersion proto message and Go resource wrapper to store version history as first-class resources in the resource store. Changes: - Add api/mesh/v1alpha1/rule_version.proto with RuleVersion message * parent_rule_kind, parent_rule_mesh, parent_rule_name: parent rule identity * version_no, content_hash: version metadata * spec_json: JSON snapshot of rule spec at this version * operation, source, author, reason: mutation context * rolled_back_from_id: set if this is a rollback * created_at, committed_at: timestamps - Generate rule_version.pb.go with protoc - Add pkg/core/resource/apis/mesh/v1alpha1/rule_version_types.go * RuleVersionResource and RuleVersionResourceList types * Implement Resource interface (ResourceKind, ResourceKey, etc.) * Register with coremodel.RegisterResourceSchema This enables storing version history in the resource store alongside traffic rules, preparing for migration from versioning.Store tables. Related: apache#1477 Phase 2.1
Add index to efficiently query RuleVersion resources by parent rule. Changes: - Create pkg/core/store/index/rule_version.go - Define ByParentRuleIndexName constant - Register byParentRule indexer for RuleVersionKind - Index key format: "<parent_kind>/<parent_mesh>/<parent_name>" Example: "ConditionRoute/default/my-rule" This enables fast lookup of all version history for a given rule without scanning the entire RuleVersion store. Related: apache#1477 Phase 2.2
54a6b9e to
0888e4a
Compare
- require lease contexts for rule versioning mutations - propagate contexts through resource manager and governor writes - move repair/bootstrap reads inside rule locks - use ledger snapshots for current and deleted state - remove unused subscriber intent token path - simplify rollback commit handling and add UI coverage
| - name: Cache dependencies | ||
| # ref: https://github.com/actions/cache/blob/main/examples.md#go---module | ||
| uses: actions/cache@v4 | ||
| with: | ||
| # Cache, works only on Linux | ||
| path: | | ||
| ~/.cache/go-build | ||
| ~/go/pkg/mod | ||
| # Cache key | ||
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | ||
| # An ordered list of keys to use for restoring the cache if no cache hit occurred for key | ||
| restore-keys: | | ||
| ${{ runner.os }}-go- | ||
| cache-dependency-path: go.sum | ||
| - name: Check Code Format | ||
| run: make fmt && git status && [[ -z `git status -s` ]] | ||
| run: make fmt && git status && [[ -z $(git status -s) ]] |
There was a problem hiding this comment.
这里修改的原因是,PR CI 在可靠运行 Go 测试之前就已失败(CI failure 有明确日志证据,在failed check 82487011873)。
失败的 GitHub Actions 任务显示了手动 Go 模块缓存恢复时出现的 tar 恢复错误,包括:
Failed to restore: "/usr/bin/tar" failed
Cannot open: File exists
该冲突源于缓存了整个 ~/go/pkg/mod 目录,而 Go 1.24/工具链管理机制以及 actions/setup-go 本身已会在模块缓存下填充由工具链管理的文件。在已存在工具链文件的情况下恢复该缓存,导致 Go 1.24 矩阵任务失败。
本次修复移除了针对 Go 模块的冗余手动 actions/cache 步骤,改为由 actions/setup-go@v5 以 go.sum 作为依赖键来处理 Go 缓存。构建或测试命令本身未作更改:工作流仍然执行相同的前端构建、make fmt check 和 make test。
变更后,两个 Go CI 矩阵任务均通过:
- CI(ubuntu-latest - Go 1.24):成功
- CI(ubuntu-latest - Go 1.23):成功
|



Closes #1473.
Summary
This PR adds lightweight version history, diff, and rollback support for governor-managed traffic rules in Dubbo Admin.
Covered rule kinds:
RuleVersionis used as an audit/history resource and rollback material. It is not the live source of truth. The live rule state remains owned byResourceManager, registry, and dynamic configuration.This PR intentionally implements best-effort history recording rather than a strongly consistent version-control or intent-ledger system.
What changed
Backend
Adds
RuleVersionas a first-class resource for recording rule history snapshots.Adds backend APIs for:
Records history for create, update, enable, disable, and delete operations after the main rule write succeeds.
Captures a baseline/import history entry before the first admin-side modification of an existing rule that has no recorded history.
Keeps
ByParentRuleindexing so history can be queried by the parent traffic rule.Adds configurable retention for rule history.
Frontend
Write behavior
Create, update, enable, disable, and delete continue to write directly through
ResourceManager.After the main write succeeds, the service best-effort appends a
RuleVersionhistory entry. If history append fails, the rule mutation remains successful and the history failure is logged or surfaced as partial rollback metadata where applicable.Delete reads the current rule before deletion so the deleted content can be captured in history. After successful deletion, a DELETE history marker is appended. DELETE versions are markers and cannot be used directly as rollback targets. To restore deleted content, users should rollback to an earlier CREATE or UPDATE version.
Deleting a missing rule remains idempotent and does not create fake history.
Bootstrap behavior
Startup bootstrap is baseline-only.
It creates an initial baseline/import history entry only for existing rules that have no recorded history yet. It does not reconcile registry state, does not repair history, and does not record external registry changes after history already exists.
Rollback behavior
Rollback is implemented as a normal rule write, not transaction recovery.
Rollback reads the target
RuleVersion, requires it to contain a CREATE or UPDATE snapshot, decodes the stored spec, checks the actual current rule fromResourceManagerfor no-op detection, and writes the target spec throughResourceManager.Upsert.After a successful rollback write, the service best-effort appends a new history entry with
source=ROLLBACKandrolledBackFromVersionIdpointing to the selected target version. The new entry uses CREATE or UPDATE operation semantics depending on whether the live rule existed before rollback.If rollback writes the rule successfully but appending rollback history fails, the API returns
historyRecorded=falseso the frontend can tell the user that rollback took effect but history was not recorded.There is intentionally no expected-version check, lock, intent repair, ledger commit validation, or strong consistency contract in this implementation.
Diff behavior
Version-vs-current diff reads the current live rule from
ResourceManager.Version-vs-version diff compares the stored snapshots from the selected history records.
The UI and API use latest-recorded-history semantics and do not treat the latest
RuleVersionas guaranteed to be the current live registry state.Removed from the earlier design
This PR no longer includes the durable intent/version-ledger model.
Removed concepts include:
RuleIntentexpectedVersionIdVERSION_CONFLICTVERSION_LEDGER_PENDINGwithRuleMutation/withRuleLockwrite wrappersTests
Backend coverage focuses on the lightweight history semantics:
historyRecorded=falseResourceManagerFrontend coverage keeps:
Validated locally:
Also verified that business code no longer references the removed consistency keywords such as
RuleIntent,expectedVersionId,VERSION_CONFLICT,VERSION_LEDGER_PENDING,RepairIntent,AbandonIntent,OUTCOME_UNKNOWN,COMMITTING,COMMITTED,withRuleMutation,withRuleLock, orReconcileActualState.Non-goals
This PR does not make
RuleVersionthe source of truth for live traffic rules.This PR does not provide optimistic concurrency control, compare-and-set rollback, distributed locking, intent repair, or automatic reconciliation of external registry changes.
The goal is to provide practical admin-side history, diff, and rollback material while preserving the existing rule-write path through
ResourceManager.