Skip to content

fix(spec): refuse undoable: true where no runtime fulfils it - #19635

Merged
hotlong merged 6 commits into
mainfrom
claude/issue-19297-undoable-precise-refusal
Sep 22, 2026
Merged

hotlong merged 6 commits into
mainfrom
claude/issue-19297-undoable-precise-refusal

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #19297

Clause-②: yes

Ruling 5754211444 (batch #203 item 5 · letter C · 「203 同意」), letter C plus D's two text corrections in one PR.

What this does

undoable: true on a registered action is now legal only on a shape some runtime actually fulfils, and refused at parse time everywhere else. One .refine() on ActionSchema, reading operation and type together, with a remedy naming both fulfilling shapes.

shape who takes the snapshot verdict
operation: 'update' the framework runtime accepted
type: 'api' the pinned console accepted
script / url, and the dormant flow / modal / form, without operation: 'update' nobody refused

D's two corrections ride along: the undoable .describe() sentence (and the code comment above it carrying the same claim) said an action with no operation has nothing anchoring the capture — measured false against the pinned console; and skills/objectstack-ui/rules/actions.md attributed the snapshot to 「the runtime」 on a bullet whose own example is the api shape.

The ablation the ruling names

Three legs, each a real on-disk mutation of the refine through scripts/ablation-replace.mjs (anchor hit proven, blob hash before/after printed, restore proven git diff HEAD empty). Two populations, each named so a count never travels without its definition:

  • (B) type matrix, n=7 — one synthetic action per ActionType member with undoable: true and no operation, plus the operation: 'update' shape.
  • (A) live corpus, n=4 — every action document in this tree that actually declares undoable: true: the published ReassignLeadAction skill example, packages/spec/src/ui/action-row-update.test.ts:119, and packages/runtime/src/action-declarative-update.test.ts at :75 and :533.
leg (B) refused (A) refused
L0 — guard ablated (the pre-change state) 0 / 7 0 / 4
L1 — the precise refine, as landed 5 / 7 0 / 4
L2 — blanket refusal (direction A, require operation: 'update') 6 / 7 1 / 4 — the published ReassignLeadAction

Refused at L1, by name: script, url, flow, modal, form. Accepted: api, and operation: 'update'. The ruling's expectation holds exactly — the published example and api actions stay accepted, and the refused set is the one the ruling's own binding text enumerates (「measured as script / url and the dormant flow / modal / form」). L2 reproduces, first-hand, the breakage the ruling cites as its reason for refusing A.

One note on the dispatch's shorthand 「blanket refusal 1 → 5」: it does not reproduce as a single count moving, because its two numbers belong to two different populations. Both numbers are real and both are above — 5 is the precise refine's refused type-shapes, 1 is the live document a blanket refusal would break. No fork: the ruling's stated, testable expectation is met.

Risk, and what was measured rather than assumed

  • Zero live behaviour deleted. (A) above is the whole in-tree population and the precise refine refuses none of it.
  • The two ledger contentions the dispatch flagged did not materialise. packages/spec/dropped-refinements.baseline.json is unchanged — ui/Action already carries a root site (in) from the existing refine chain, so a second refinement at the same position adds no new site; check:authorable-surface and the spec build are green without touching it. packages/spec/liveness/state-counts.md is unchanged too, and check:liveness is green: the props/undoable row's status (live) and its objectui-side evidence are untouched by this diff, so no re-citation is owed here.
  • ../objectui is not checked out in this container, so the two console readers at the pinned sha were not read first-hand. Every statement about them in this PR is carried from the card's recorded evidence and from packages/spec/liveness/action.json, not re-measured. Stated rather than implied.
  • Rollback is one commit: the refine is a single self-contained block in ActionSchema's chain plus its pin test. The prose corrections are independent of it and correct on their own.

Skills line budget — both readings

The dispatch's ceiling was +2 net lines. The published catalog also carries a token ratchet with zero headroom on this file, which the first phrasing red at +35 tokens; the correction was tightened to fit rather than the ceiling raised.

reading before after delta
skills/objectstack-ui/rules/actions.md — lines 208 208 +0
skills/objectstack-ui/rules/actions.md — tokens (ceil(utf8 bytes / 4), ceiling 2108) 2108 2107 −1
skills/objectstack-ui/rules/actions.md — bytes 8432 8428 −4
whole skills/objectstack-ui package — lines 2155 2155 +0
all published SKILL.md — lines 6145 6145 +0
whole published skills/ tree — lines 12766 12766 +0

No surrounding prose was rewritten and no ceiling was moved. check-skills-token-ratchet and check-skill-line-ratchet are both green.

Verification

All readings at c8d4d6513, the final commit.

Gates — derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, every derived command run with its exit code captured before any pipe, reconciled with --ran:

115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN — a DERIVED zero, all 115 recorded an exit code and none of them is 3.

All 115 exit 0. Nine first returned exit 3 (PREREQUISITE NOT MET — a gate that reads built output, not a finding) and were re-run to a real reading after the dependency closure and then the full build.

Suites — the spec lane charter's consumer requirement, each package named with a real reading:

package reading
@objectstack/spec (own) 510 files / 14924 passed, 1 todo · typecheck exit 0
@objectstack/runtime 272 files / 3799 passed, 1 skipped
@objectstack/lint 108 files / 4084 passed
@objectstack/objectql 303 files / 5050 passed
@objectstack/metadata-protocol 188 files / 2645 passed, 19 skipped
@objectstack/example-crm 5 files / 45 passed
@objectstack/example-showcase 29 files / 384 passed
@objectstack/example-todo 7 files / 238 passed
@objectstack/cli unit layer: 222 files / 3141 passed. The integration layer is declared to CI — this diff touches no integration-layer file, no bin/ entry and no spawn helper

pnpm lint (eslint . --no-inline-config, the whole repo) exit 0 at c8d4d6513.

New pin: packages/spec/src/ui/action-undoable-fulfillment.test.ts, 20 cases — every unfulfilled shape refused at the undoable path, the remedy naming both fulfilling shapes and the runtime that fulfils each, refusal through the registered action metadata door, both fulfilled shapes accepted, and undoable absent or false untouched on every type.

Acceptance notes

  • The second undoable prose line in action.zod.ts (「undoable captures the prior values of exactly the fields written.」) was read and left as written: it sits inside the operation key's own EXECUTOR CONTRACT block, scoped to the operation: 'update' shape, where it is true. It is not the sentence ruling D calls measured-false.
  • The stale remote branch claude/issue-19297-undoable-requires-operation was inspected read-only and carries no commits of its own — its tip 488f4f54 is an ordinary main commit. Nothing of direction A exists on it to inherit. Not branched from, not pushed to.
  • The rule lives on ActionSchema's chain alone, matching the existing scoping precedent beside it; an inline action is not a registered action and has no console reader to speak for. The .describe() renders into the InlineAction reference table too, so its refusal sentence is qualified 「on a registered action」, the same way the confirmText describe is.

维护者速读(草稿)

改了什么

「做完给我一个撤销按钮」这个开关,以前写在哪种动作上都收,可真正会去拍快照的只有两种。现在把合法范围收到这两种——一种由框架运行时兑现,一种由我们自己钉住的控制台兑现——其余写法在解析时就被拒,拒绝话术里同时点名这两条出路。顺带改正两处说明文字:schema 里那句「没有 operation 就没有东西可锚」实测为假,技能文档里把快照归给「运行时」的那句,对 api 那种形状其实是控制台干的。

为什么改

声明了却没人兑现,是北极星第 4 条禁止的形状:作者写了,既不被拒也拿不到处方。但最直觉的修法——强制配 operation: 'update'——实测会当场炸掉我们自己已发布的示例和控制台里每一个带撤销的 api 动作,所以没有采用。这次收的是「一个读者都没有」的那部分,删掉的活行为是零。

风险与代价(含回滚)

风险低:全仓四份真实写法无一被拒,例子应用、runtime、lint、objectql、metadata-protocol 的套件全绿。代价是把「api 形状由控制台兑现」这句话写进了 spec——裁决已认定这是契约陈述而非越界。回滚是一次 revert:那条规则是独立的一段,两处文字改正即使单独留下也是对的。派发词担心的两个生成账本(dropped-refinements、state-counts)实测都没被动到,不存在和其它 PR 抢同一个文件的问题。一处如实说明:姊妹仓 objectui 没有检出到这个容器里,控制台那两个读者本次未能亲自读到,相关陈述沿用卡片已记录的证据。

席位意见

你要做的

回一句可落地即可。本 PR 触到 skills/**,按 Tier H 规则保持 draft,不合并、不排队、不开自动合并;落地等的是至档合约复核加上您的批准。


Generated by Claude Code

… console

The `undoable` describe sentence and the comment above it both claimed that an
action with no `operation` has nothing anchoring the capture. Measured false:
the pinned console builds the undo envelope for a `type: 'api'` action from
`undoable` alone, and those readers are the whole recorded evidence for this
key's `live` liveness verdict. State the closed fulfillable set instead.

Claude-Session: https://claude.ai/code/session_01GBPc6CYjy3tNTYhaKCmqg4
Co-authored-by: Claude <noreply@anthropic.com>
`undoable` was accepted on every action shape, and on most of them nothing
ever built an Undo. Close the accepted set to the two shapes some runtime
fulfils — `operation: 'update'`, snapshotted by the framework runtime, and
`type: 'api'`, snapshotted by the pinned console — and refuse the rest at
parse time with a remedy naming both.

A blanket requirement of `operation: 'update'` is deliberately not the rule:
it would refuse the published `ReassignLeadAction` example at import time and
every console api action with undo.

Claude-Session: https://claude.ai/code/session_01GBPc6CYjy3tNTYhaKCmqg4
Co-authored-by: Claude <noreply@anthropic.com>
For the `type: 'api'` shape the snapshot is the console's, not the framework
runtime's. Name both fulfilling shapes on the same bullet, so an author reads
the closed set the schema now enforces. Net 0 lines.

Claude-Session: https://claude.ai/code/session_01GBPc6CYjy3tNTYhaKCmqg4
Co-authored-by: Claude <noreply@anthropic.com>
…iling

The published catalog's token ratchet had zero headroom on this file
(8432 bytes = ceiling 2108 exactly), so the longer phrasing red it at +35
tokens. Tighten the same correction to 2107 tokens: net 0 lines, -4 bytes,
-1 token, and no surrounding prose touched.

Claude-Session: https://claude.ai/code/session_01GBPc6CYjy3tNTYhaKCmqg4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/troubleshooting.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/actions.mdx (via ActionSchema (symbol, a top-level const))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-3.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/releases/v17/index.mdx (via ActionSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3429a0eb8f06b00b9e0a70d69410d3520eb45984 — the merge of head c8d4d651309b58bb63496bcf1a6656b77f5cf823 into base 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3429a0eb8f06b00b9e0a70d69410d3520eb45984 && git checkout 3429a0eb8f06b00b9e0a70d69410d3520eb45984
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 c8d4d651309b58bb63496bcf1a6656b77f5cf823 && git checkout -B drift-repro 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 && git merge --no-ff c8d4d651309b58bb63496bcf1a6656b77f5cf823

node scripts/docs-audit/affected-docs.mjs --json 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6ffccc51e2f36c24cbabea0a5242d0001a8d00f3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c8d4d651309b58bb63496bcf1a6656b77f5cf823

In-seat at tier (last_served_model read from the session record equals the constant). Clause-②: yes — this card narrows a published accept set. Review taken 2026-09-22T03:06Z. Every judgment is read off the branch source and the API, ⛔ never off the dev's report narrative.

① Derived judgments

The narrowing is exactly letter C. Branch packages/spec/src/ui/action.zod.ts:2031:

if (data.undoable === true && data.operation !== 'update' && data.type !== 'api') {

⇒ refused iff undoable: true AND not operation: 'update' AND not type: 'api'. That is the ruled predicate, ⛔ not a blanket operation: 'update' requirement (letter A, which the ruling refuses).

What it does NOT withdraw — the thing a narrowing must prove: type: 'api' with undoable and no operation stays accepted, so the published ReassignLeadAction example in skills/objectstack-ui/rules/actions.md still parses, and every console api-action with undo survives. operation: 'update' stays accepted. undoable absent or false is untouched on every type. Refused set: script, url, flow, modal, form — and only when operation is not 'update'.

The remedy names both fulfilling shapes, as the ruling requires — read at :2037-2042: it names operation: 'update' and type: 'api', and offers "otherwise drop undoable". ⇒ a refused author is told the two legal shapes, not just that they are wrong.

A defaulting subtlety the dev handled rather than tripped on (:2026): type is already defaulted to 'script' by the time a refinement runs, so type !== 'api' is evaluated against the resolved value. That is why operation: 'update' with a defaulted type is still accepted.

The falsified describe is repaired at the source of the falsification. The old sentence 「An action with no operation declares no write set, so nothing anchors the capture there」 is gone; the replacement (:1327) states the two-fulfiller set and that anything else is refused. ⇒ the declared contract now matches the enforced one, which is the whole point of the (b)-class card.

The skills/** correction is a net reduction, and the ceiling did not move. Measured on both refs:

ref bytes lines ceil(bytes/4)
origin/main 744a0a3f1 8432 208 2108
branch c8d4d6513 8428 208 2107

The diff is one bullet line replaced — ⛔ no surrounding prose rewritten — and the replacement is shorter and more precise than what it replaced (it names both snapshot owners and the refusal). ⇒ no ratchet was raised.

② Semver level

@objectstack/spec minor, per the ruling and the launch-window convention (a narrowing ships at minor with the banner rather than waiting for a major). The ADR-0087 semantic entry is registered as the ruling specifies — a refusal with no automatic rewrite — at packages/spec/src/migrations/entries/semantic/18.ui-action-undoable-unfulfillable-refused.ts. ⇒ the ledger is the notification channel this narrowing owes, and it exists.

③ Boundary flags

  1. ⚠️ This seat's dispatch stated the budget in the WRONG UNIT, and the dev caught it. The order named a +2 net-LINE ceiling for skills/**. The binding constraint on that file is the token ratchet (check-skills-token-ratchet, ceil(utf8 bytes/4)) sitting at exactly zero headroom — 8432 bytes = ceiling 2108. The dev's first phrasing was net 0 lines but +35 tokens, i.e. green under my budget and red at the real gate. It tightened the wording to fit (2107, −1 token) and ⛔ did not raise the ceiling. ⇒ the defect is in my order, not the diff. Generalisable: for published skills/** the budget is tokens, never lines.
  2. ⚠️ ZONE 2 assumption 4 was NOT measured this round, and the dev said so plainly instead of asserting it. ../objectui is not checked out in that container, so the two console readers at pinned sha 53ded82b — the evidence that the console fulfils type: 'api' from undoable alone — were not re-read first-hand here. The chain this PASS rests on is therefore: an earlier at-tier review measured them (recorded in the card's own falsification note) → the director seat ruled on that measurement → this diff implements the ruling. ⛔ Not a first-hand reading in this round, and stated as such.
  3. ⚠️ InlineAction is not covered. The rule sits on ActionSchema's refine chain alone, which the dev reports matches the existing scoping precedent beside it. ⇒ an inline action can still declare an unfulfillable undoable. Recorded as a boundary, ⛔ not widened here and ⛔ not a blocker: widening the rule's reach is a different accept-set move than the one ruled.
  4. ℹ️ This seat's 1 → 5 ablation shorthand does not reproduce as one count, and correctly so: the two numbers belong to two populations (a synthetic type matrix, n=7, and the live corpus, n=4). The dev reported both real numbers rather than bending its measurement to my phrasing. The ruling's expectation holds.
  5. CI on this head: 35 runs, 33 success, 2 skipped, zero non-green. mcp_calls: 0. Gates: derived 115 / run 115 / exit-0 115 / NOT-MEASURED 0.

Implemented-by: claude/issue-19297-undoable-precise-refusal
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿)—— 本 PR 需要您的一个动作

席位已对照自己读的 diff 校正了草稿并填入席位意见。头 c8d4d6513。

改了什么 —— 「做完给我一个撤销按钮」(undoable)这个开关,以前写在任何一种动作上都收,可真正会去拍快照的只有两种:operation: 'update' 由框架运行时拍,type: 'api' 由我们自己钉住的控制台拍。现在合法范围收到这两种,其余写法(script / url / flow / modal / form,且没有 operation: 'update')在解析时就被拒,拒绝话术同时点名这两条出路。顺带改正两处说明文字:schema 里那句「没有 operation 就没有东西可锚」实测为假,技能文档里把快照归给「运行时」的那句,对 api 那种形状其实是控制台干的。

为什么改 —— 声明了却没人兑现,是北极星第 4 条禁止的形状:作者写了,既不被拒、也拿不到处方。而最直觉的修法(强制配 operation: 'update')实测会当场拒掉我们自己已发布的 ReassignLeadAction 示例和控制台里每一个带撤销的 api 动作 —— 所以裁决明确不取它。这次收掉的只是「一个读者都没有」那部分。

风险与代价(含回滚) —— 风险低,且是量过的:四份真实写法无一被拒;CI 35 条检查全完成,33 绿 2 跳过,零非绿;消费包套件(runtime / lint / objectql / metadata-protocol / 两个示例应用)全绿。代价是把「type: 'api' 由控制台兑现」写进了 spec —— 裁决已认定这是契约陈述而非越界。回滚是一次 revert:规则是独立一段,两处文字改正即便单独留下也仍然是对的。

席位意见 —— 建议批准。 三个理由:① 这是一次收窄,而收窄唯一要证明的事(没删掉活行为)是量出来的,不是推断的;② 被证伪的那句 describe 是在它自己所在的那一行修好的,不是在别处加个补丁绕过;③ 消融实验方向预测在前、结果在后,而且 dev 顶回了我两处措辞错误(见下),说明它在读门禁而不是读我的散文。

⚠️ 但请知道两条本席不能替您消掉的限制:

  • 姊妹仓 objectui 没有检出到那个容器里,所以控制台那两个读者(pin 53ded82b)本轮没有被亲自读到。这个 PASS 依赖的链条是:更早一轮的达档复核量过它们 → 总监席据此裁决 → 本 PR 实现该裁决。⛔ 不是本轮的一手读数,如实写明。
  • InlineAction 未覆盖 —— 内联动作仍可以声明一个没人兑现的 undoable。这是边界,不是缺口:扩大规则辐射面是另一次接受集移动,不在本次裁决内。

⭐ 顺带一件关于我自己的事 —— 我给 dev 的预算写错了单位:我写「skills/** 净增 ≤ +2 行」,而那个文件真正卡着的是token 棘轮(ceil(字节/4)),余量恰好为零(8432 字节 = 上限 2108)。dev 的第一版措辞是 0 行但 +35 token,按我的预算是绿的、按真门禁是红的。它把话缩短塞进去(2107,−1 token),⛔ 没有抬上限、⛔ 没有重写周边散文。结论可推广:已发布 skills/** 的预算单位是 token,不是行。

你要做的 —— 一个动作:本 PR 触到 skills/objectstack-ui/rules/actions.md,整单按 Tier H 处置,只有您(或您授权的批准)能让它落地。⛔ AI 席位不会合并、不排队、不解除 draft。已向 os-zhuang 与 hotlong 两个授权账户发出复审请求。若您认为 @objectstack/spec 的 minor 档不对,或认为 InlineAction 应当在本 PR 一并覆盖,说一句即可 —— 后者会扩大本次接受集移动的范围,所以我没有自行加进去。


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review September 22, 2026 06:42
@hotlong
hotlong enabled auto-merge September 22, 2026 06:43
@hotlong
hotlong added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 6afa59d Sep 22, 2026
41 checks passed
@hotlong
hotlong deleted the claude/issue-19297-undoable-precise-refusal branch September 22, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

undoable: true on an action without operation is accepted and silently un-anchored

3 participants