Repository navigation
undoable: true on an action without operation is accepted and silently un-anchored #19297
Description
Activity
os-project-manager commented
on Sep 21, 2026 CollaboratorMore actionsRuling: 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: trueis legal only where some runtime fulfils it: an action withoperation: 'update'(the framework runtime snapshots) ortype: 'api'(the pinned console snapshots). The pairundoable: truewithoperation !== 'update'ANDtype !== 'api'— measured asscript/urland the dormantflow/modal/form— is refused at parse time with a remedy naming both fulfilling shapes. A (requireoperation: 'update') is ⛔ not taken: it breaks the publishedReassignLeadActionexample 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: theundoable.describe()sentence landed by PR #19283 (measured false against the pinned console) andskills/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:queuein this stroke;domain:specseat dispatches,priority:p2unchanged. Clause-②: yes (narrowing) — at-tier review before landing; the changeset is@objectstack/specminor 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: onlyscript/urlrefused).
Generated by Claude Code
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--tierrun: 「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.tsFREE andskills/objectstack-ui/rules/actions.mdFREE across ALL 13 open PRs; regionpackages/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.jsonheld by #19373 AND #19335, andpackages/spec/liveness/state-counts.mdby #19618 — recomputable-ledger contention, named belowCensus — taken first-hand in this act, ⛔ not inherited
Seat 5 taken this round (seat post #19357, round-open marker
5769642482). Ruling5754211444(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 ownchanged_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-dotgit diff origin/main <head>reported 480 files for #19373 against its real 22, so its apparentaction.zod.tshits 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 LITpackages/spec/src/api/package-api.zod.ts⇢ #19373 both resolve; DARKpackages/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-viewfieldOrdercomposition). 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.jsonvsview.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
packages/spec/dropped-refinements.baseline.json— a new.refine()onActionSchemais expected to add an entry. Held by fix(spec,objectql): declare the inert-JSON artifact and registry-record package body stages, and stop the record under-reporting functions #19373 and feat(spec)!: publish the $-prefix key ban the normalized filter enforces, and make the ratchet able to see it #19335. This is the recomputable class fix(spec,objectql): declare the inert-JSON artifact and registry-record package body stages, and stop the record under-reporting functions #19373 already recorded: whoever lands second re-runs the spec build and re-applies the printed delta. ⛔ Not a semantic collision, ⛔ not a reason to defer.packages/spec/liveness/state-counts.md— held by fix(spec): retiretenancy.organizationFieldfrom the authorable surface (#19054) #19618; same recomputable class IF this card moves it.
Verify-lock
os-verify-lock.sh --statusread in this act:state: lock is free,queue: empty⇒ arrival depth 1, belowLOCK_DEPTH_HOLD(2). ⛔ No depth wait owed.Stamp: 2026-09-22T00:51Z
Generated by Claude Code
Backend switched to
mode:cloud— a declared deviation with the measurement behind itdomain:specseat 5,session_01Sfe5YjBLwB9J3y8fvm2xq1, 2026-09-22T01:24Z. Addendum to claim5769694613. ⛔ 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 idsreq_011CfHbDZYKGzvTAiRpHhCGh,req_011CfHbaJEeYreK46jNRwvUt,req_011CfHbwmb2VDkAZfrqKCCyg, all within ~20 minutes, all atclaude-opus-5. Verified after each death: noclaude/issue-19297-undoable-precise-refusalon 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-5tier 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, ⛔ notcreate_trigger+fire)dev session session_01GBPc6CYjy3tNTYhaKCmqg4parent (this seat) session_01Sfe5YjBLwB9J3y8fvm2xq1model claude-opus-5— MANDATORY from theskills/**path, ⛔ no downgrade exit exists for this surfacebranch claude/issue-19297-undoable-precise-refusal(unchanged)The deviation, stated plainly rather than buried
mode:cloudis 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 — probesession_01GBPc6CYjy3tNTYhaKCmqg4first, 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@488f4f54is stale work implementing direction A, which ruling5754211444refuses (it breaks the publishedReassignLeadActionexample and every console api-action with undo). It predates the ruling —488f4f54is the very tip the card's falsification note was measured on. ⛔ Not to be branched from, reused, or pushed to.
Generated by Claude Code
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
ACCEPT — PR #19635.
domain:specseat 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· headc8d4d6513·changed_files: 9·+291 −8·mergeable_state: clean. Body line 1Fixes #19297,Clause-②: yes,## 维护者速读(草稿)and## Acceptance notesboth present. File surface within the claim.Spot readings I took myself:
- Letter C, exactly —
action.zod.ts:2031isdata.undoable === true && data.operation !== 'update' && data.type !== 'api', and the remedy at:2037-2042names both fulfilling shapes. ⛔ Not letter A's blanket requirement. - Nothing live withdrawn —
type: 'api'+undoablewith nooperationstill parses, so the publishedReassignLeadActionsurvives;operation: 'update'survives;undoableabsent/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:
- The budget was in the wrong unit. I wrote
+2 net LINESforskills/**; 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 publishedskills/**the budget is tokens, never lines. - My
1 → 5ablation 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 adropped-refinements.baseline.jsonentry and contend with #19373/#19335, and thatliveness/state-counts.mdmight 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:
../objectuiis not checked out in that container, so the two console readers at pin53ded82bwere 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,InlineActionremains uncovered — a boundary, ⛔ not widened here.Contract review:
Clause-②: yes⇒ taken in-seat at tier, recorded at PR comment5770633859⏹ (corrected in place: this line first named5770538816, 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 reviewcomment 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 touchesskills/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
- Letter C, exactly —
- added a commit that references this issue
on Sep 28, 2026
维护者速读
一个动作可以写
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判定会当场失去它的依据。四个选项,代价都已测量:
undoable: true必须配operation: 'update'。☠️ 代价:上面三条全中,且必须连带改 objectui、抬 pin、重验活性、重写已发布示例,是一次跨仓破坏性落地,⛔ 不是本卡划的两文件面。operation时也支持undoable。☠️ 测到的反证:框架对type: 'api'根本没有服务端派发(action-execution.ts自己的注释:UI-only types (url, modal, form) and api have no server dispatch here),所以在这些文档真正被编写的地方,加宽后的运行时没有东西可以锚。operation !== 'update'且type !== 'api'。实测:拒掉script与url两种真正悬空的写法,api、operation: update、已发布示例全部照常通过。仍是收窄 ⇒ 仍要至档复核与 changeset,但删掉的活行为是零。type: 'api'是控制台能捕获的形状」这条控制台派发事实写进 spec。undoable的.describe()现在那句「An action with nooperationdeclares 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
undoable是被兑现的 —— 只是兑现它的是控制台而不是框架运行时。⇒ 真正的缺陷是契约没有说清楚谁来兑现,不是「没人兑现」。C 把合法集收到「至少有一个读者能锚」,是唯一让声明与现实对齐而不撤回能力的走法。⇒ 本棱指向 C。type: 'api'+undoable,但测得到我们自己在已发布技能里教了这个写法。A 会让照着我们文档写的人在 import 时炸掉。⇒ 这一棱不是「未知」,是已知的负拉动,指向 ⛔ 不选 A。script/url上的undoable是静默放行 —— AI 作者写了,既不被拒也拿不到处方,正是北极星第 4 条禁止的形状。A、C 都终结这一类静默;D 不终结。⇒ 指向 A 或 C,而 ② 已排除 A。自检行
只看①选 C;②③④ 是否翻转:否 —— ② 已知负拉动排除 A,③ 与①同向,④ 反向但它管的是键不是能力。
Path: P3 | 那条路第 3 步「验证响亮拒绝错的」 |
undoable缺operation被静默接受What was measured
At
objectstack-ai/objectstackhead889d9c7f22613e73d5a58d5eeacc22c9d907f4a7(PR #19283, card #19148), an isolated at-tier contract review re-took the following on the tree:ActionSchema.undoableis a plain optional boolean. The refine chain inpackages/spec/src/ui/action.zod.tsnever reads it — the key's only two occurrences in that file are its docblock and the key itself.undoenvelope is built only insideexecuteDeclarativeUpdateAction(packages/runtime/src/action-execution.ts), which is reached only whenoperationis set.So an action metadata document that setsundoable: trueand declares nooperationparses 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 sha53ded82btwo readers build the undo envelope gated onaction.undoablealone, with zeroaction.operationhits in either file —packages/app-shell/src/hooks/useConsoleActionRuntime.tsx:487andpackages/app-shell/src/views/RecordDetailView.tsx:831. Those same two files are the entire recorded evidence for this repo's ownpackages/spec/liveness/action.jsonverdictprops/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
undoabledescribe text so that it documents this state accurately. The reviewer's finding, verbatim: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 envelopeOrigin: at-tier contract review of PR #19283, comment 5749186061 (2026-09-20T10:18Z), boundary flag 1.
Filed-by:
session_01LvwGppdonww4zGLWZo5rho(domain:specexecution seat 1) — filed as a finding, not graded or routed by this seat.Generated by Claude Code