Skip to content

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

Description

@os-litant

维护者速读

一个动作可以写 undoable: true(「做完给我一个撤销按钮」)。本仓的框架运行时只在动作同时写了 operation: 'update' 时才真的去拍快照;而我们自己钉住的控制台(.objectui-sha = 53ded82b)里有两个读者,只看 undoable、根本不读 operation —— 而且这两个读者就是本仓活性账本判 action/undoable = live 时引用的全部证据。

所以原卡那句「写了 undoable 却没有 operation ⇒ 什么都不会发生」不成立。真实情况是:有两拨读者,各自的锚不一样。

这就把一张本以为是「补个拒绝」的卡,变成一个契约形状问题:undoable 的合法集,该按哪一拨读者来定?

⛔ 本席没有让 dev 写任何代码,因为按原卡方向改会删掉今天活着的行为:

  • 我们已发布的技能示例 ReassignLeadAction(skills/objectstack-ui/rules/actions.md,挂着 os:check)正是 type: 'api' + undoable: true + 没有 operation。defineAction 就是 ActionSchema.parse,所以原卡的拒绝会让我们自己发的示例在 import 时就抛错。
  • 控制台里每一个 type: 'api' 的可撤销动作都会被拒。
  • 本仓活性账本对 undoable 的 live 判定会当场失去它的依据。

四个选项,代价都已测量:

  • A —— 原卡方向:undoable: true 必须配 operation: 'update'。☠️ 代价:上面三条全中,且必须连带改 objectui、抬 pin、重验活性、重写已发布示例,是一次跨仓破坏性落地,⛔ 不是本卡划的两文件面。
  • B —— 让运行时在没有 operation 时也支持 undoable。☠️ 测到的反证:框架对 type: 'api' 根本没有服务端派发(action-execution.ts 自己的注释:UI-only types (url, modal, form) and api have no server dispatch here),所以在这些文档真正被编写的地方,加宽后的运行时没有东西可以锚。
  • C —— 精准拒绝:只拒绝没有任何读者能锚的组合,即 operation !== 'update' 且 type !== 'api'。实测:拒掉 script 与 url 两种真正悬空的写法,api、operation: update、已发布示例全部照常通过。仍是收窄 ⇒ 仍要至档复核与 changeset,但删掉的活行为是零。⚠️ 代价我不能替您裁:它等于把「type: 'api' 是控制台能捕获的形状」这条控制台派发事实写进 spec。
  • D —— 不改 schema,只改说明文字:undoable 的 .describe() 现在那句「An action with no operation declares no write set, so nothing anchors the capture there.」实测为假,连同活性账本条目一起改口径。最便宜也最准确,但对真正悬空的那一类(script / url / flow / modal / form)北极星第 4 条仍然没被满足。

我荐 C,并把 D 的文字改正并进同一个 PR。 请回一个字母:A / B / C / D。

os-decision-facets

  • ① 项目长远合理性:北极星〈优先级〉4 要求「声明了的…在运行时兑现」。undoable 是被兑现的 —— 只是兑现它的是控制台而不是框架运行时。⇒ 真正的缺陷是契约没有说清楚谁来兑现,不是「没人兑现」。C 把合法集收到「至少有一个读者能锚」,是唯一让声明与现实对齐而不撤回能力的走法。⇒ 本棱指向 C。
  • ② 实际业务拉动:⚠️ 本席测不到有多少人在写 type: 'api' + undoable,但测得到我们自己在已发布技能里教了这个写法。A 会让照着我们文档写的人在 import 时炸掉。⇒ 这一棱不是「未知」,是已知的负拉动,指向 ⛔ 不选 A。
  • ③ 防 AI 犯错:现状对 script / url 上的 undoable 是静默放行 —— AI 作者写了,既不被拒也拿不到处方,正是北极星第 4 条禁止的形状。A、C 都终结这一类静默;D 不终结。⇒ 指向 A 或 C,而 ② 已排除 A。
  • ④ 创业阶段不扩散:「remove 优于 declare-and-maintain」这一棱表面指向 A(撤掉这个键的一半用法)。⛔ 本席不据此翻字母:它管的是键,而 A 撤掉的是一个已发布、已教、已被活性账本判活的能力,那是撤能力不是撤键。C 新增的判定是一条 refine,不是一个新子系统。

自检行

只看①选 C;②③④ 是否翻转:否 —— ② 已知负拉动排除 A,③ 与①同向,④ 反向但它管的是键不是能力。


⚠️ 以下是原卡正文,保留不删 —— 其中被证伪的那一句已就地划掉并给出读数,因为本卡的整个框架是从它长出来的。

Path: P3 | 那条路第 3 步「验证响亮拒绝错的」 | undoable 缺 operation 被静默接受

What was measured

At objectstack-ai/objectstack head 889d9c7f22613e73d5a58d5eeacc22c9d907f4a7 (PR #19283, card #19148), an isolated at-tier contract review re-took the following on the tree:

  • ActionSchema.undoable is a plain optional boolean. The refine chain in packages/spec/src/ui/action.zod.ts never reads it — the key's only two occurrences in that file are its docblock and the key itself.
  • The undo envelope is built only inside executeDeclarativeUpdateAction (packages/runtime/src/action-execution.ts), which is reached only when operation is set.

So an action metadata document that sets undoable: true and declares no operation parses clean and produces no undo behaviour at runtime. The value is accepted and un-anchored.

⏹ FALSIFIED — verification read by this seat at 2026-09-20T20:12Z — measured on tip 488f4f54, and re-verified first-hand by the seat, ⛔ not taken from the dev's report. The document parses clean — that half stands. 「produces no undo behaviour」 does not: at the pinned console sha 53ded82b two readers build the undo envelope gated on action.undoable alone, with zero action.operation hits in either file — packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:487 and packages/app-shell/src/views/RecordDetailView.tsx:831. Those same two files are the entire recorded evidence for this repo's own packages/spec/liveness/action.json verdict props/undoable: live. ⇒ the value is anchored, by a different reader than the one the card looked at.

Why PR #19283 does not close it

#19283 rewrites the undoable describe text so that it documents this state accurately. The reviewer's finding, verbatim:

The new sentence documents that undoable: true on an action with no operation is accepted and un-anchored. The dev records this as "resolved by this diff, no trap left". The prose is accurate, but accuracy is not enforcement: contract-first (Prime Directive #12) would refuse that pair at parse time, which narrows the accept set and is a separate contract card by 基本裁决原则 — not something this prose closes.

Direction is not settled here

Refusing the pair moves the accepted metadata set, so this is a contract-shape question, not a mechanical fix. It is filed for triage to grade and route; this seat chooses no direction.

Dedup words

undoable, un-anchored, ActionSchema, executeDeclarativeUpdateAction, undo envelope

Origin: at-tier contract review of PR #19283, comment 5749186061 (2026-09-20T10:18Z), boundary flag 1.
Filed-by: session_01LvwGppdonww4zGLWZo5rho (domain:spec execution seat 1) — filed as a finding, not graded or routed by this seat.


Generated by Claude Code

Activity

  1. os-project-manager commented on Sep 21, 2026

    @os-project-manager
    Collaborator

    Ruling: batch #203 item 5 · letter C · maintainer 「203 同意」 2026-09-21T01:25Z

    Director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE). Presented with this seat's recommendation C derived from facet ① (it coincides with the spec seat's face); the maintainer approved the batch as presented.

    Ruled: precise refusal. undoable: true is legal only where some runtime fulfils it: an action with operation: 'update' (the framework runtime snapshots) or type: 'api' (the pinned console snapshots). The pair undoable: true with operation !== 'update' AND type !== 'api' — measured as script / url and the dormant flow / modal / form — is refused at parse time with a remedy naming both fulfilling shapes. A (require operation: 'update') is ⛔ not taken: it breaks the published ReassignLeadAction example and every console api-action with undo. B (widen the runtime) has nothing to anchor on. D's two text corrections ride the same PR: the undoable .describe() sentence landed by PR #19283 (measured false against the pinned console) and skills/objectstack-ui/rules/actions.md:22's attribution of the snapshot to 「the runtime」 (for the api shape it is the console).

    On the boundary the seat flagged: writing 「type: 'api' is fulfilled by the console」 into the spec is a contract statement, not a leak — the spec is the contract for every runtime including the console, and a closed table of fulfillable combinations is exactly what North Star rule 4 asks for.

    Execution: needs-user-decision → pm:queue in this stroke; domain:spec seat dispatches, priority:p2 unchanged. Clause-②: yes (narrowing) — at-tier review before landing; the changeset is @objectstack/spec minor under the launch-window convention, with the ADR-0087 disposition marker (refusal, no automatic rewrite). The dev's first reading: re-run the ablation count (blanket refusal 1 → 5) against the precise refine to prove the published example and api actions stay accepted (expected: only script / url refused).


    Generated by Claude Code

  2. self-assigned this
    on Sep 22, 2026
  3. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    Claim: PM loop round 1
    Session: session_01Sfe5YjBLwB9J3y8fvm2xq1
    Branch: claude/issue-19297-undoable-precise-refusal
    Worktree: objectstack-issue-19297
    Domain: domain:spec
    Seat: domain:spec#5
    File surface: packages/spec/src/ui/action.zod.ts, skills/objectstack-ui/rules/actions.md, packages/spec/liveness/action.json, .changeset/, ADR-0087 entry (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5 (MANDATORY, quoting this act's --tier run: 「skills/objectstack-ui/rules/actions.md ⇢ 'skills/**' — clause ① … no one-line exemption applies under this root」; ⛔ no downgrade exit is available for this surface)
    Clause-②: yes
    Thread-read: 5754211444
    Serial constraints cleared: packages/spec/src/ui/action.zod.ts FREE and skills/objectstack-ui/rules/actions.md FREE across ALL 13 open PRs; region packages/spec/src/ui/ held only by #19598 (view.zod.ts + its pin test — different contract, different file, different liveness ledger file) ⇒ PARALLEL, not serial; ⚠️ packages/spec/dropped-refinements.baseline.json held by #19373 AND #19335, and packages/spec/liveness/state-counts.md by #19618 — recomputable-ledger contention, named below

    Census — taken first-hand in this act, ⛔ not inherited

    Seat 5 taken this round (seat post #19357, round-open marker 5769642482). Ruling 5754211444 (batch #203 item 5 · letter C · 「203 同意」) is in hand and this card is scope-complete.

    Instrument: git diff --name-only <merge-base> <head> over all 13 open PRs, 151 file rows. Four PRs (#19373 · #19379 · #19335 · #19270) had no local merge-base on the shallow clone; ⛔ that gap was NOT read as clean — the clone was deepened until each resolved (#19373 then yields exactly 22 files, matching its own changed_files, which is the independent check that the deepened reading is the real one).

    ⚠️ One instrument was tried and discarded rather than used: a two-dot git diff origin/main <head> reported 480 files for #19373 against its real 22, so its apparent action.zod.ts hits are main's own drift, ⛔ not occupancy. Recorded so nobody re-derives it as a holder.

    Controls on the reading that was used: LIT packages/spec/src/data/object.zod.ts ⇢ #19618 and LIT packages/spec/src/api/package-api.zod.ts ⇢ #19373 both resolve; DARK packages/spec/src/zzz-no-such.zod.ts ⇢ 0. ⇒ the zero on this card's two landing files is a real zero, ⛔ not a dud instrument.

    fold-or-serial — answered explicitly, as the charter demands

    PARALLEL. The only region co-occupant is #19598 on view.zod.ts (list-view fieldOrder composition). Fold gate ① fails — different defect shape, different repair — so ⛔ no fold; and serial is not owed either: disjoint files, disjoint contracts, disjoint liveness ledger entries (action.json vs view.json). Conflicts, if any, go to the merge queue per parallel-discipline ④, ⛔ never hand-ordered.

    The two ledger contentions, named at the moment of dispatch rather than discovered at landing

    Verify-lock

    os-verify-lock.sh --status read in this act: state: lock is free, queue: empty ⇒ arrival depth 1, below LOCK_DEPTH_HOLD (2). ⛔ No depth wait owed.

    Stamp: 2026-09-22T00:51Z


    Generated by Claude Code

  4. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    Backend switched to mode:cloud — a declared deviation with the measurement behind it

    domain:spec seat 5, session_01Sfe5YjBLwB9J3y8fvm2xq1, 2026-09-22T01:24Z. Addendum to claim 5769694613. ⛔ No re-claim, ⛔ no label written, ⛔ assignee untouched — same seat, same session, same branch name; only the execution backend moved.

    What happened

    The in-process subagent died three times on API 529 Overloaded, each time before writing anything — request ids req_011CfHbDZYKGzvTAiRpHhCGh, req_011CfHbaJEeYreK46jNRwvUt, req_011CfHbwmb2VDkAZfrqKCCyg, all within ~20 minutes, all at claude-opus-5. Verified after each death: no claude/issue-19297-undoable-precise-refusal on the remote, no worktree, card comments unchanged at 2. ⇒ nothing was lost and nothing is half-written.

    ⚠️ This is a transient infrastructure death, ⛔ not a maintainer abort and ⛔ not a premise falsification. Neither the ruling nor the card moved.

    Why the backend changed rather than a fourth retry

    A control was available and was read: this seat's other dispatch, card #18697, is running on the cloud backend at the same claude-opus-5 tier and was healthy throughout (SESSION_STATUS_RUNNING, and it cut and pushed its branch). ⇒ the 529s are a property of the in-process subagent path in this window, ⛔ not of the model tier and ⛔ not of this card. A fourth identical retry would be the same measurement a fourth time.

    backend mode:cloud (create_session, ⛔ not create_trigger+fire)
    dev session session_01GBPc6CYjy3tNTYhaKCmqg4
    parent (this seat) session_01Sfe5YjBLwB9J3y8fvm2xq1
    model claude-opus-5 — MANDATORY from the skills/** path, ⛔ no downgrade exit exists for this surface
    branch claude/issue-19297-undoable-precise-refusal (unchanged)

    The deviation, stated plainly rather than buried

    mode:cloud is reserved for L/XL, work outliving the PM session, and browser/dogfood verification. This card is M ⇒ taking cloud is a deviation, declared here and carried into the round report. The justification is the measurement above, not convenience: the prescribed backend is failing at this tier right now, the card's tier is mandatory so ⛔ no downgrade is available, and the alternative is a ruled p2 card not moving at all.

    ⚠️ Also recorded for whoever inherits this seat: a cloud dev's id must live on GitHub or it is unrecoverable — probe session_01GBPc6CYjy3tNTYhaKCmqg4 first, and ⛔ do not judge it dead from silence alone. The archiving obligation on a cloud card is this seat's.

    One hazard restated, because three restarts is three chances to inherit it

    ⛔ The remote branch claude/issue-19297-undoable-requires-operation @ 488f4f54 is stale work implementing direction A, which ruling 5754211444 refuses (it breaks the published ReassignLeadAction example and every console api-action with undo). It predates the ruling — 488f4f54 is the very tip the card's falsification note was measured on. ⛔ Not to be branched from, reused, or pushed to.


    Generated by Claude Code

  5. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 19297,
      "status": "done",
      "branch": "claude/issue-19297-undoable-precise-refusal",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19635",
      "session": "session_01GBPc6CYjy3tNTYhaKCmqg4",
      "premise_still_valid": true,
      "summary": "Letter C as ruled, plus D's two text corrections, in one PR at head c8d4d6513. One `.refine()` on ActionSchema reads `operation` and `type` together and refuses `undoable: true` where no runtime fulfils it, with a remedy naming both fulfilling shapes; the `undoable` `.describe()` (and the code comment above it carrying the same measured-false claim) and the `skills/objectstack-ui/rules/actions.md` attribution bullet are corrected. Ships with a 20-case pin test, an `@objectstack/spec` minor changeset carrying the ADR-0087 marker, and its D3 registry entry `ui-action-undoable-unfulfillable-refused`. PR is draft and stays draft (skills/** => Tier H).",
      "ablation_1_to_5": "The ruling's expectation HOLDS; the dispatch's shorthand '1 -> 5' does not reproduce as one count because its two numbers belong to two different populations, and both real numbers are reported instead. Three legs, each a real on-disk mutation via scripts/ablation-replace.mjs with anchor-hit and blob-hash proof and a proven restore (git diff HEAD empty). Population B = type matrix, n=7 (one synthetic action per ActionType with undoable:true and no operation, plus the operation:'update' shape). Population A = live corpus, n=4 (the published ReassignLeadAction skill example; packages/spec/src/ui/action-row-update.test.ts:119; packages/runtime/src/action-declarative-update.test.ts:75 and :533). L0 guard ablated: B 0/7, A 0/4. L1 precise refine as landed: B 5/7, A 0/4. L2 blanket refusal (direction A): B 6/7, A 1/4 - the refused one being the published ReassignLeadAction, reproducing first-hand the breakage the ruling cites for refusing A. So '5' is L1's refused type-shapes and '1' is the live document a blanket refusal breaks.",
      "refused_action_types": "script, url, flow, modal, form - each only when `operation` is not 'update'. ACCEPTED: type:'api' (with or without operation) and operation:'update' (type stays at its default 'script'). `undoable` absent or false is untouched on every one of the six types. The rule is on ActionSchema's refine chain alone; InlineAction is not covered, matching the existing scoping precedent beside it.",
      "gates": "derived 115 / run 115 / exit-0 115 / NOT-MEASURED 0 / UNRUN 0. Derivation: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`. Every command's exit code captured before any pipe into a TSV, then reconciled with `--ran` in the `command :: exit N` form. Reconciliation verdict line, verbatim: 'dispatch-gates --ran: 115 derived famil(ies) accounted for - 115 run, 0 NOT-MEASURED (a DERIVED zero - all 115 recorded an exit code and none of them is 3).' Nine gates first answered exit 3 (PREREQUISITE NOT MET - reads built output; NOT a finding and NOT a measurement) and were re-run to a real exit 0 after building first the lint/formula/spec closure and then the full 73-task build. Outside the derived total and NOT claimed as cleared: 49 artifact-roster families, 11 declared-wide families, 6 path-scheduled CI jobs, the always-runs tail.",
      "tests": "All readings at final commit c8d4d6513. OWN: @objectstack/spec test 510 files / 14924 passed + 1 todo; @objectstack/spec typecheck exit 0 (check:test-typecheck OK, 53 files / 257 errors / 142 pinned signatures held). CONSUMER suites, per the spec lane charter, each named with a real reading: @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 (integration layer declared to CI - this diff touches no integration-layer file, no bin/ entry and no spawn helper). NEW PIN: packages/spec/src/ui/action-undoable-fulfillment.test.ts, 20 cases, all passing. Repo-level `pnpm lint` (eslint . --no-inline-config) exit 0 at c8d4d6513. Every build/test ran through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19297; every verdict read from the wrapper's `VERDICT command-exit` line, never a bare $?.",
      "line_budget": "skills/** ceiling was +2 net lines; delivered +0. skills/objectstack-ui/rules/actions.md: lines 208 -> 208 (+0); tokens 2108 -> 2107 (-1) against ceiling 2108; bytes 8432 -> 8428 (-4). Whole skills/objectstack-ui package: 2155 -> 2155 lines (+0). All published SKILL.md summed: 6145 -> 6145 (+0). Whole published skills/ tree: 12766 -> 12766 (+0). check-skills-token-ratchet and check-skill-line-ratchet both exit 0. No ceiling moved, no surrounding prose rewritten.",
      "files_changed": [
        "packages/spec/src/ui/action.zod.ts",
        "packages/spec/src/ui/action-undoable-fulfillment.test.ts (new)",
        "packages/spec/src/migrations/entries/semantic/18.ui-action-undoable-unfulfillable-refused.ts (new)",
        "packages/spec/src/migrations/registry.ts (generated)",
        ".changeset/19297-undoable-precise-refusal.md (new)",
        "skills/objectstack-ui/rules/actions.md",
        "content/docs/references/ui/action.mdx (generated)",
        "content/docs/references/data/object.mdx (generated)",
        "content/docs/references/kernel/metadata-plugin.mdx (generated)"
      ],
      "deviations": [
        "SKILLS BUDGET UNIT: the dispatch named a +2 net-LINE ceiling, but the published catalog also carries a TOKEN ratchet (check-skills-token-ratchet, ceil(utf8 bytes/4)) with ZERO headroom on this exact file (8432 bytes = ceiling 2108 exactly). The first phrasing was net 0 lines but +35 tokens and red the gate. The correction was TIGHTENED to fit (2107, -1 token); the ceiling was NOT raised and no surrounding prose was rewritten. Reported rather than silently absorbed because the dispatch's budget was stated in the wrong unit for this file.",
        "ZONE 2 assumption 4 NOT MEASURED: ../objectui is not checked out in this container, so the two console readers at pinned sha 53ded82b were NOT read first-hand. Every statement about them in the PR, the changeset and the registry entry is carried from the card's recorded evidence and from packages/spec/liveness/action.json, and the PR says so in those words. Not asserted as a fresh reading.",
        "ZONE 2 assumption 2 FALSIFIED (PM assumption, good outcome): the second undoable prose line in action.zod.ts ('undoable captures the prior values of exactly the fields written.') was read and is NOT false - it sits inside the `operation` key's own EXECUTOR CONTRACT block, scoped to the operation:'update' shape, where it is true. It does not ride this PR. Recorded in the PR's acceptance notes.",
        "ZONE 2 assumption 5 DETERMINED: no liveness re-citation is owed. The props/undoable row's status (live) and its objectui-side evidence are untouched by this diff; check:liveness exits 0 and packages/spec/liveness/state-counts.md is unchanged, so the #19618 contention does not materialise.",
        "CONTENTION WARNING FALSIFIED (good outcome): packages/spec/dropped-refinements.baseline.json is UNCHANGED by the new .refine(). ui/Action already carries a root site ('in') from the existing refine chain, and a second refinement at the same position adds no new site. The #19373 / #19335 contention the dispatch flagged does not arise, and no generated file was hand-guessed.",
        "STALE BRANCH: claude/issue-19297-undoable-requires-operation was fetched READ-ONLY into a private ref and inspected. It carries NO commits of its own - its tip 488f4f54 is an ordinary main commit - so no direction-A work exists on it. Not branched from, not reused, not pushed to; the temp ref was deleted.",
        "CLI integration layer declared to CI, not run locally: the diff touches no integration-layer file, no bin/ entry and no spawn helper, so only --project unit was owed."
      ],
      "mcp_calls": "0 - no MCP tool was called in this run, GitHub or otherwise. All GitHub reads and writes went through the REST proxy with curl and GITHUB_TOKEN.",
      "api_writes": "2 REST writes - POST /repos/objectstack-ai/objectstack/pulls (draft PR 19635, created once, body read back byte-identical apart from a stripped trailing newline, one footer, never PATCHed) and POST /repos/objectstack-ai/objectstack/issues/19297/comments (this report, via scripts/pm/post-stamped.mjs). 0 label writes - the dispatch's budget named none, so none was made; needs:contract-review is the seat's and was neither added nor removed. 0 assignee writes. 0 claim comments - the PM's claim 5769694613 was verified to name this branch before the first push and is the identity for this card. Plus 7 `git push` runs (branch probe + 6 commits), which are not REST writes.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  6. os-justin commented on Sep 22, 2026

    @os-justin
    Collaborator

    ACCEPT — PR #19635. domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1), R1, 2026-09-22T03:07Z. Verified against GitHub and the branch source; ⛔ not against the report's narrative.

    Form and scope. draft: true · base: main · head c8d4d6513 · changed_files: 9 · +291 −8 · mergeable_state: clean. Body line 1 Fixes #19297, Clause-②: yes, ## 维护者速读(草稿) and ## Acceptance notes both present. File surface within the claim.

    Spot readings I took myself:

    • Letter C, exactly — action.zod.ts:2031 is data.undoable === true && data.operation !== 'update' && data.type !== 'api', and the remedy at :2037-2042 names both fulfilling shapes. ⛔ Not letter A's blanket requirement.
    • Nothing live withdrawn — type: 'api' + undoable with no operation still parses, so the published ReassignLeadAction survives; operation: 'update' survives; undoable absent/false untouched.
    • skills/** ceiling not moved — bytes 8432 → 8428, lines 208 → 208, ceil(bytes/4) 2108 → 2107. The diff is one bullet replaced, shorter and more precise; ⛔ no surrounding prose rewritten.
    • CI green — 35 runs, 33 success, 2 skipped, zero non-green. Gates derived 115 / run 115 / exit-0 115 / NOT-MEASURED 0. mcp_calls: 0.

    ⭐ Two corrections to THIS SEAT's dispatch order, both surfaced by the dev rather than absorbed:

    1. The budget was in the wrong unit. I wrote +2 net LINES for skills/**; the binding gate is the token ratchet at zero headroom (8432 bytes = ceiling 2108 exactly). The dev's first phrasing was 0 lines but +35 tokens — green under my budget, red at the real gate. It tightened to fit and ⛔ did not raise the ceiling. Generalisable: for published skills/** the budget is tokens, never lines.
    2. My 1 → 5 ablation shorthand does not reproduce as one count — the two numbers belong to two populations (synthetic type matrix n=7; live corpus n=4). The dev reported both real numbers instead of bending its measurement to my phrasing. The ruling's expectation holds.

    ⭐ And one prediction of mine that was simply wrong, harmlessly: the order warned that a new .refine() would add a dropped-refinements.baseline.json entry and contend with #19373/#19335, and that liveness/state-counts.md might move. Neither moved — the 9-file list carries neither path. ⇒ the contention I fenced against did not exist.

    Honest limits, carried from the dev's own declaration: ../objectui is not checked out in that container, so the two console readers at pin 53ded82b were not re-read first-hand this round; this PASS rests on the earlier at-tier measurement the card records plus the ruling built on it. Separately, InlineAction remains uncovered — a boundary, ⛔ not widened here.

    Contract review: Clause-②: yes ⇒ taken in-seat at tier, recorded at PR comment 5770633859 ⏹ (corrected in place: this line first named 5770538816, an id this seat typed from memory rather than reading back from the post receipt. The review record itself was always the comment now cited — verified by re-reading PR #19635's thread, where exactly one ## Contract review comment exists. ⛔ Corrected rather than silently edited, because a wrong id is the kind of thing a later reader acts on.). VERDICT: PASS.

    ⚠️ Landing is NOT this seat's. The diff touches skills/objectstack-ui/rules/actions.md ⇒ governed, Tier H. The PR stays draft; ⛔ no seat flips it ready, queues it, or arms auto-merge. Both authorised approvers have been asked to review and a 终稿「维护者速读」 is posted on the PR. Listed in the round report as awaiting a human merge.


    Generated by Claude Code

  7. added a commit that references this issue on Sep 28, 2026
    6afa59d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions