Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

ResourceEditPage 的重置/删除按钮与它执行的动作用两份不同的 artifact 判定:图标与提示写「删除」,确认框却问「重置」,且条目并不会被删除 #4886

Closed

Description

@yinlianghui

症状

metadata-admin 编辑页右上角那颗重置/删除按钮,渲染出来的语义和点下去执行的动作由两份不同的判定决定,二者对同一个条目可以给出相反的答案。

对一个「有 code 层、但不属于 artifact 档」的条目(下详),实际表现是:

  • 按钮渲染成 Trash2 垃圾桶图标,title 为 engine.edit.delete(「删除」);
  • 点下去,确认框问的却是 engine.edit.resetConfirm(「重置……」);
  • 确认之后条目不会消失 —— 走的是重置分支:重新拉 layered、留在原页;而按钮承诺的「删除后回到列表」不会发生。

也就是说:一颗写着「删除」的按钮,问了一句「要重置吗」,然后重置了。

位置与机制

packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx

渲染侧用的是页面的两级授权判定 isArtifactItem:

{!createMode && canWrite && layered?.overlay && (isArtifactItem ? lockResettable : lockDeletable) && (
  <Button … title={isArtifactItem ? t('engine.edit.reset') : t('engine.edit.delete')}>
    {isArtifactItem ? <RotateCcw …/> : <Trash2 …/>}

执行侧 doReset() 却自己重新算了一份,而且更松 —— 只问「有没有 code 层」,连 sys_metadata 哨兵都不排除:

const itemIsArtifact = !createMode && layered?.code != null;
const confirmKey = itemIsArtifact ? 'engine.edit.resetConfirm' : 'engine.edit.deleteConfirm';
…
if (itemIsArtifact) { /* 重置:重拉 layered,留在本页 */ }
else { navigate(`../`); /* 删除:回列表 */ }

两者在「有 code 层但不在 artifact 档」的条目上必然分叉。

谁会落进这个缺口

今天就可达:已发布的组织自有条目 —— 其 code 层带 sys_metadata 哨兵,isArtifactItem 为 false(渲染成「删除」),而 itemIsArtifact 为 true(执行「重置」)。这一分叉在 sys_metadata 豁免加入时就已存在。

PR #4885(#4308)会扩大这一人群:该 PR 让「可写包里的组织自有对象」也正确地退出 artifact 档,于是这些条目的 canWrite 由 false 变 true —— 按钮从此会渲染出来(此前整个控件因 canWrite 为 false 而不显示)。#4885 只修 banner,未触碰 doReset,故本缺陷既非其引入、也未被其修复,但暴露面变大,因此单独提出。

该以哪份为准(需判定,故未自行修)

服务端其实已经给了答案:resolveLockState 里 resettable = artifactBacked,并随 layered 信封下发。页面已读为 lockResettable,但 lockResettable = layered?.resettable !== false 把「没意见」和「true」合并了,因此不能直接当判据用。

两条路,代价不同:

  • A. 执行侧对齐渲染侧(doReset 改用 isArtifactItem):按钮说什么就做什么。但对组织自有条目意味着走删除分支 —— 这是破坏性动作,需确认「重置一个没有包基线的条目」到底应该删掉还是剥离 overlay。
  • B. 渲染侧对齐执行侧:按钮改说「重置」。语义上更保守(不删数据),但要确认 client.reset() 对无 artifact 基线的条目行为为何。

因为涉及破坏性动作且两种读法导向不同产品语义,未在 #4885 中顺手改。请 PM 分诊后再定方向。

来源

实施 #4308(PR #4885)时在相邻代码路径上发现,按 Prime Directive #10 立单不扩围。已搜索既有 open issue(关键词 reset/delete confirm mismatch、itemIsArtifact、文件路径)无命中。

Activity

  1. os-project-manager commented on Aug 17, 2026

    @os-project-manager
    Collaborator

    Triage: needs-user-decision + type Bug. Reachable today (published org-own entries: rendered as delete, executes reset), and PR #4885 widens the exposed population — but the fix direction turns on what reset/delete should mean for an entry with no package baseline, which is a product-semantics call on a destructive branch. Human floor; not dispatchable until ruled.

    Four-prong block:

    • Platform long-term coherence: the root defect is two client-side re-derivations of one verdict the server already computes (resolveLockState.resettable = artifactBacked, shipped in the layered envelope). Whichever semantic wins, both render and execute should bind to the server verdict — the current lockResettable = layered?.resettable !== false collapses "no opinion" into "true" and must become an honest tri-state read first.
    • Measured business pull: real — the filer traced a concrete population (org-own published entries with the sys_metadata sentinel) that hits the mismatch now.
    • AI-agent error-resistance: one authoritative server verdict consumed by both sides beats two hand-rolled predicates; this is the same "second copy of the contract, already diverged" class the framework repo keeps paying for.
    • Startup scope discipline: small fix either way; the only expansion risk is inventing a third "strip overlay" semantic without a driver — recommend not doing that unless the maintainer wants it.

    Options (from the card): A — execute follows render (delete branch for non-artifact entries; destructive, guarded by the confirm dialog); B — render follows execute (button says reset; needs client.reset()'s behaviour on no-baseline entries measured first).

    Triage recommendation: A, expressed via the server verdict — for an entry with no artifact baseline "reset" has nothing to reset to, so delete is the honest semantic and the confirm dialog is the consent gate; implement by binding both sides to the envelope's resettable and fixing the tri-state collapse. Pre-condition for whoever implements: measure what client.reset() actually does on a no-baseline entry, and state in the PR what the server answers for the sys_metadata-sentinel population — if the server itself says resettable: true for them, the bug is the server's and the fix re-homes to the framework repo.


    Generated by Claude Code

  2. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-17, recorded by PM session session_01DbRmJD3iPhXjKr6vhd4Qkv). The maintainer reviewed the four-card decision-inbox analysis in PM chat and approved its recommendations, verbatim: 「同意」.

    Ruled: Option A — the rendered semantic wins (delete for non-artifact entries), implemented by binding BOTH sides to the server verdict.

    Label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  3. self-assigned this
    on Aug 17, 2026
  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    认领(objectui 车道分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt,批次 16):branch claude/issue-4886-lock-verdict。

    按 2026-08-17 维护者裁定(A 案,经服务端 verdict 实现)派发;串行门已开(PR #4885 已落 main,c3b0912bae 之后本卡文件为合并后状态)。两个派发前提测量原样带入派发词:① client.reset() 对无基线条目的实际行为;② 服务端对 sys_metadata 哨兵人群的 resettable 读数 —— 若服务端自己答 true,缺陷归 framework 仓,fork report 停手 ⛔ 不做客户端绕修。文件面:packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx。


    Generated by Claude Code

  5. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Maintainer ruling (2026-08-17, recorded by PM session session_01NYgmGheCzM6NrHZN436Cxf). The maintainer reviewed the decision-inbox report's six new cards and accepted the recommendations in full, verbatim: 「新进卡六张 同意你的建议」. For this card:

    Ruled: Option A, expressed via the server verdict.

    • For an entry with no artifact baseline, "reset" has nothing to reset to — delete is the honest semantic, with the confirm dialog as the consent gate.
    • Implementation binds both render and execute to the layered envelope's resolveLockState.resettable, first fixing the tri-state collapse (lockResettable = layered?.resettable !== false reads "no opinion" as "true" — must become an honest tri-state read). The client-side re-derivations (isArtifactItem in render, itemIsArtifact in doReset) both retire.
    • Preconditions (first steps of the dispatch): measure what client.reset() actually does on a no-baseline entry, and state in the PR what the server answers for the sys_metadata-sentinel population — if the server itself says resettable: true for them, the bug re-homes to the framework repo (fork report, not a client workaround).
    • ⛔ No third "strip overlay" semantic is invented.

    Label flipped needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions