fix: allow deleting system default sync channel with immediate heal

Remove hard ban that returned 400 with no UI feedback; delete then Ensure+rebinding, and show confirm/toast on SyncPage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-06 09:03:41 +08:00
parent 362dcee242
commit 75622a728e
4 changed files with 56 additions and 16 deletions

View File

@@ -121,21 +121,23 @@ func syncDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
authx.WriteError(w, http.StatusNotFound, err.Error())
return
}
// Z12h-1公司默认同步通道禁止删除账号落点依赖它
if ch.IsSystemDefault {
authx.WriteError(w, http.StatusBadRequest, "公司默认同步通道不可删除;账号绑定落点依赖此通道。若异常请刷新页面自动修复")
return
}
wasDefault := ch.IsSystemDefault
svcCtx.DBSync.StopChannel(id)
if err := svcCtx.DBSync.Store().DeleteChannel(id); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
// 若误删后仍有 Binding 挂死heal 会挂回默认通道
if _, hErr := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); hErr != nil {
_ = hErr
// Z12h-1默认同步可删删后 Ensure+heal账号落点自动挂回默认通道
out := map[string]any{"ok": true, "deleted_id": id, "was_system_default": wasDefault}
if healed, hErr := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); hErr != nil {
out["heal_error"] = hErr.Error()
} else if healed != nil {
out["channel_id"] = healed.ChannelID
out["recreated_default"] = wasDefault || healed.ChannelCreated
out["bindings_fixed"] = healed.BindingsFixed
out["agents_fixed"] = healed.AgentsFixed
}
httpx.OkJson(w, map[string]any{"ok": true})
httpx.OkJson(w, out)
}
}

View File

@@ -586,9 +586,35 @@ export function SyncPage(props: {
<Button
size="small"
danger
onClick={async () => {
await deleteSyncChannel(session, r.id);
onClick={() => {
Modal.confirm({
title: `删除通道「${r.name || r.id}」?`,
content: r.is_system_default
? "这是公司默认同步通道。删除后将自动重建并重挂库绑定,账号落点不会丢失。"
: "删除后,仍挂在此通道上的绑定会自动改挂公司默认同步通道。",
okText: "确认删除",
okType: "danger",
cancelText: "取消",
onOk: async () => {
setBusy(true);
try {
const res = await deleteSyncChannel(session, r.id);
if (res.heal_error) {
message.warning(`通道已删,自愈未完成:${res.heal_error}`);
} else if (res.recreated_default) {
message.success("已删除;公司默认同步通道已自动重建并重挂绑定");
} else {
message.success("已删除");
}
await refresh();
} catch (e: any) {
message.error(e.message || String(e));
throw e;
} finally {
setBusy(false);
}
},
});
}}
>

View File

@@ -869,6 +869,8 @@ export type SyncChannel = {
pk_columns: Record<string, string>;
/** 绑定智能体:模块/库归属对照 */
agent_id?: number;
/** 公司默认同步通道Z12 */
is_system_default?: boolean;
/** 绑定已发布模块 slug */
app_slug?: string;
last_error?: string;
@@ -966,6 +968,16 @@ export async function deleteSyncChannel(session: Session, id: string) {
});
const data = await readJson(res);
throwIfBad(res, data, "delete sync channel failed");
return data as {
ok?: boolean;
deleted_id?: string;
was_system_default?: boolean;
recreated_default?: boolean;
channel_id?: string;
bindings_fixed?: number;
agents_fixed?: number;
heal_error?: string;
};
}
export async function testSyncEndpoints(

View File

@@ -62,7 +62,7 @@
| 默认同步通道默认启动 | 创建即 enabled重启自动 Start |
| **Z14c** ACL | 智能体 JWT 放行挂载 `online_db_id``agentOwnsOnlineDB` |
| Podman DNS / 脚本可执行位 | compose aliases`restart.sh` 100755 |
| **Z12h / Z12h-1 / Z12h-2** | `HealTenantSyncBind`Ensure 默认通道 + 重挂 Agent/Binding`is_system_default` 禁删;列表/换票/`agents/me`/ensure Binding 触发;见 §5.9 |
| **Z12h / Z12h-1 / Z12h-2** | `HealTenantSyncBind`Ensure 默认通道 + 重挂 Agent/Binding默认同步**可删**,删后立即 Ensure+heal;列表/换票/`agents/me`/ensure Binding 触发;见 §5.9 |
> 产品一句:**账号已绑定 ⇒ 落点信息固定;通道没了平台自动补,终端无需手填 channel_id。**
@@ -552,13 +552,13 @@ POST .../schema
| **Z12f** | **P1** | **开通 UX** | 启用智能体即可DSN 用公司级默认 | **智建已落实** |
| **Z12g** | **P1** | **默认同步通道默认启动** | 创建 `Enabled=true`;重启对 `IsSystemDefault` 自动 Start控制台保存后自动启动 | **智建已落实**`cb76824` |
| **Z12h** | **P0** | **通道误删自愈** | `HealTenantSyncBind`Ensure 默认通道 + 重挂挂死 Agent/Binding保留 `online_db_id`);触发:`ensureAgentSyncBind` / `agents/me` / 换票 / 通道·Binding 列表 / 删非默认通道后 | **智建已落实** |
| **Z12h-1** | **P1** | **禁删或删后重建默认同步** | `is_system_default` **禁止删除**;删其它通道后 heal | **智建已落实** |
| **Z12h-1** | **P1** | **禁删或删后重建默认同步** | `is_system_default` **可删**;删后立即 Ensure+heal 重建并重挂 | **智建已落实** |
| **Z12h-2** | **P1** | **orphan Binding heal** | 列表 / Ensure Binding 时通道缺失 → 静默改挂默认通道 | **智建已落实** |
**缺口说明2026-08-06 · 已关闭)**
-SyncPage「通道已删除」、`ensureAgentSyncBind` 见非空 `channel_id` 即跳过。
- 现:列表/换票等路径先 heal默认通道可删。生产须 **pull** 后验收第 4 条。
- 现:列表/换票等路径先 heal默认通道可删并立即重建。生产须 **pull** 后验收第 4 条。
**宇恒已做2026-08-05**
@@ -571,7 +571,7 @@ POST .../schema
1. 新公司:只「启用智能体」,**不**点新建通道,换票已带 `channel_id`,宇恒可 drain。
2. 账号 A 的本机库 push 不会出现在账号 B 的线上库;共享库仅在显式共享时可见。
3. 用户全程无需 F12、无需手填 `YXD_SYNC_CHANNEL_ID`
4. **Z12h** 默认同步通道 API **不可删**;若 Binding/Agent 仍挂历史死 `channel_id`(或误删非默认通道)→ `agents/me` / 换票 / 打开 SyncPage → Ensure 默认通道并重挂,「通道已删除」红字消失;宇恒无需手填新 ID。
4. **Z12h** 删除默认同步通道 → 立即重建并重挂Binding/Agent 挂死 ID 时打开 SyncPage / 换票 / `agents/me` 亦会自愈,「通道已删除」红字消失;宇恒无需手填新 ID。
### 5.10 【双方代码已接 Z13】绑定流程简化绑定码 / 手机号2026-08-05