Skip to content

fix(cloud_object): allow deleting locally-created objects (rules etc.)#313

Open
zerx-lab wants to merge 1 commit into
mainfrom
claude/sleepy-meitner-7rdpzp
Open

fix(cloud_object): allow deleting locally-created objects (rules etc.)#313
zerx-lab wants to merge 1 commit into
mainfrom
claude/sleepy-meitner-7rdpzp

Conversation

@zerx-lab

Copy link
Copy Markdown
Owner

Description

Fixes #311 — "Delete rule" in Settings > Agents > Knowledge > Manage Rules > Global has no effect; the rule remains after clicking Delete.

Root cause: app/src/cloud_object/update_manager.rsdelete_object_with_initiated_by started with:

// If the object isn't known to the server yet, we can't delete it.
let Some(server_id) = id.server_id() else {
    return;
};

This guard is a holdover from before the cloud backend was removed ("Zap 本地化" refactor). Since then, every newly created object — including AI Rules created via UpdateManager::create_ai_factcreate_object (update_manager.rs:1057: let object_id = SyncId::ClientId(client_id);) — only ever gets a SyncId::ClientId, never a SyncId::ServerId. So id.server_id() is always None for any rule a user creates today, the guard always hit, and the function returned before doing anything. Clicking "Delete rule" silently no-oped.

The sibling functions trash_object / untrash_object in the same file already handle this correctly — they branch on id.server_id() and fall back to a local-only path keyed by id.uid() when it's None. delete_object_with_initiated_by never got that treatment.

Fix: drop the server-id requirement and delete by the object's SyncId directly via the existing on_object_delete_success (which already works off SyncId::uid(), not specifically ServerId). The emitted ObjectOperationComplete.server_id now reports id.server_id() (i.e. None for local objects) instead of fabricating one via ServerId::from_string_lossy. I checked every consumer of that field (workspace/view.rs, workflows/workflow_view.rs, drive/import/queue.rs, cloud_object/model/view.rs, ai/blocklist/*.rs) — the ones that .expect() a Some value only do so for ObjectOperation::Create/Update, never Delete, so this is safe.

Testing

Added test_delete_local_object_removes_it in app/src/cloud_object/model/model_test.rs: creates a local AI fact via UpdateManager::create_ai_fact, asserts it exists, calls delete_object_by_user, asserts it's gone. This test fails (times out returning Some, i.e. the object is never removed) against the pre-fix code and passes with the fix.

Ran the full cloud_object::model test suite before and after:

  • Before this change (parent commit 5d874456): test_unshared_team_object already fails (unrelated — a Space::Team computation assertion, nothing to do with delete).
  • After this change: 15 passed; 1 failed — same single pre-existing test_unshared_team_object failure, confirmed identical on both commits. No regressions.
  • New test: test cloud_object::model::persistence::tests::test_delete_local_object_removes_it ... ok

Also ran cargo check -p warp --lib clean (only a pre-existing unrelated warning in terminal/view.rs).

Diff is scoped to exactly 2 files / 60 lines (git diff --stat 5d874456app/src/cloud_object/model/model_test.rs +55, app/src/cloud_object/update_manager.rs +14/-9), matching delete_object_by_user's 4 other call sites (drive/index.rs, ai/mcp/templatable_manager/native.rs x2, ai/facts/view/rule.rs) — none needed changes since the fix is centralized in the shared function.

Server API dependencies

N/A — purely local, no server dependency.

Agent Mode

  • Zap Agent Mode - This PR was created via Zap's AI Agent Mode

Changelog Entries for Stable

CHANGELOG-BUG-FIX: Fixed "Delete rule" in Settings > Agents > Knowledge having no effect


Generated by Claude Code

Fixes #311

Root cause: app/src/cloud_object/update_manager.rs:1444 (before this
change) — `delete_object_with_initiated_by` returned early unless
`id.server_id()` was `Some`. Since the cloud backend was removed during
the localization refactor, every newly created object (e.g. an AI
Rule created via `create_object` at update_manager.rs:1057) only gets
a `SyncId::ClientId`, never a `SyncId::ServerId`. So the guard always
failed and clicking "Delete rule" silently did nothing.

Fix: drop the server_id requirement and delete by the object's
`SyncId` directly via `on_object_delete_success`, mirroring the
existing local-object handling already used by `trash_object` /
`untrash_object` in the same file. Also stopped reporting a fabricated
`ServerId` in the emitted `ObjectOperationComplete` event for
non-server objects — `id.server_id()` (None for local objects) is
used instead. Checked all consumers of that event's `server_id` field;
none rely on it being `Some` for the `Delete` operation.

Added a regression test (test_delete_local_object_removes_it in
app/src/cloud_object/model/model_test.rs) that creates a local AI
fact via UpdateManager::create_ai_fact and asserts it is actually
removed after delete_object_by_user.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015fT7HYd9iZnDgLHirQJczb

@zerx-lab zerx-lab left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

整体判断倾向:Approve。修复精准、影响范围可控、测试到位,未发现阻塞问题。


阻塞问题

无。


建议

  • test_delete_local_object_removes_it 使用 ClientId::default()(即零值 ID)作为测试主键。在当前 App::test() 每次创建隔离沙箱的前提下不会冲突,但若将来测试框架改为在同一 model 里并发跑多个 case,建议改为 ClientId::new_v4() 或等价的唯一生成方式。不阻塞。

看起来不错的地方

  • 修复集中在 delete_object_with_initiated_by 一处,所有调用路径自动受益,无需逐一修改四个调用点。
  • server_id 字段从 Some(ServerId::from_string_lossy(&uid)) 改为 id.server_id()(即 None for local objects)前,逐一核查了所有消费者对该字段的 .expect() 路径,论证充分。
  • 回归测试直接模拟出 PR 描述中的失败场景(ClientId → 点击删除 → 对象仍存在),失败信息明确,覆盖了修复的核心路径。
  • PR 描述对根因和 sibling 函数已有正确实现的对比分析非常清晰。

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Thanks for the review! Re: the ClientId::default() suggestion — no change needed here. Unlike a typical "default" that'd be a zero/nil value, this crate overrides it:

// app/src/server/ids.rs
impl Default for ClientId {
    fn default() -> Self {
        ClientId::new()  // Uuid::new_v4()
    }
}

So ClientId::default() already generates a fresh random v4 UUID each call, same as ClientId::new() — there's no collision risk even under concurrent test execution.


Generated by Claude Code

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.

could not delete rule

2 participants