Repository navigation
Commit 6afa59d
fix(spec): refuse
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](https://claude.ai/code/session_01GBPc6CYjy3tNTYhaKCmqg4)_
---------
Co-authored-by: Claude <noreply@anthropic.com>undoable: true where no runtime fulfils it (#19635)1 parent a36914b commit 6afa59d
9 files changed
Lines changed: 291 additions & 8 deletions
File tree
- .changeset
- content/docs/references
- data
- kernel
- ui
- packages/spec/src
- migrations
- entries/semantic
- ui
- skills/objectstack-ui/rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
461 | 461 | | |
462 | 462 | | |
463 | 463 | | |
464 | | - | |
| 464 | + | |
465 | 465 | | |
466 | 466 | | |
467 | 467 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
330 | 330 | | |
331 | 331 | | |
332 | 332 | | |
333 | | - | |
| 333 | + | |
334 | 334 | | |
335 | 335 | | |
336 | 336 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12410 | 12410 | | |
12411 | 12411 | | |
12412 | 12412 | | |
| 12413 | + | |
| 12414 | + | |
| 12415 | + | |
| 12416 | + | |
| 12417 | + | |
| 12418 | + | |
| 12419 | + | |
| 12420 | + | |
| 12421 | + | |
| 12422 | + | |
| 12423 | + | |
| 12424 | + | |
| 12425 | + | |
| 12426 | + | |
| 12427 | + | |
| 12428 | + | |
| 12429 | + | |
| 12430 | + | |
| 12431 | + | |
| 12432 | + | |
| 12433 | + | |
| 12434 | + | |
| 12435 | + | |
| 12436 | + | |
| 12437 | + | |
| 12438 | + | |
| 12439 | + | |
| 12440 | + | |
| 12441 | + | |
| 12442 | + | |
| 12443 | + | |
| 12444 | + | |
| 12445 | + | |
| 12446 | + | |
| 12447 | + | |
| 12448 | + | |
| 12449 | + | |
| 12450 | + | |
| 12451 | + | |
12413 | 12452 | | |
12414 | 12453 | | |
12415 | 12454 | | |
| |||
0 commit comments