Skip to content

Commit 6f79300

Browse files
committed
Merge origin/main into claude/issue-23265-dispatcher-delete-version
Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
2 parents 3813914 + 05ab9ef commit 6f79300

27 files changed

Lines changed: 1183 additions & 78 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: a page action row declares `ActionSchema`'s own `operation` enum and refuses `onSuccess` beside `operation: 'update'`, with the action's text
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: registered ui-page-action-row-operation-declared -->
10+
11+
**BREAKING**: an accept-set narrowing on two keys of four published page component rows, shipped as `minor` under the launch-window convention for accept-set narrowings.
12+
13+
The page action rows are `action:button` and `action:icon` (`properties`), and each member of an `action:group` or `action:menu` (`properties.actions[]`). Each forwards `operation` to the console's action runner, which reads it before any executor: `operation: 'update'` performs the declarative write through the platform action route and runs no executor of its own. The rows typed `operation` as an open value and never carried `ActionSchema`'s refusal of `onSuccess` beside `operation: 'update'`, so a row with an `api` or `script` executor, the write and a hop parsed while the same action was refused. Now:
14+
15+
- a row's `operation` is `ActionSchema`'s own enum (one member, `'update'`); `'delete'` and `'custom'` get the action's "bulk-def operation with no row-level form" refusal, and any other value is refused at `operation`;
16+
- a row's `onSuccess` beside `operation: 'update'` is refused whatever executor the row names (or none), with ONE text shared with `ActionSchema`: the hop would follow the write, whose success carries no result `${result.*}` could read.
17+
18+
`ActionSchema`'s reach is unchanged. Its message for the pairing used to open "`onSuccess` is not defined for an `operation: 'update'` action yet"; it now states what the runner does (reads `operation` before any executor, writes, then hops) and keeps the prescription "Drop `onSuccess`".
19+
20+
### FROM → TO
21+
22+
| before | what to write instead |
23+
| --- | --- |
24+
| `{ actionType: 'api', operation: 'update', patch: { status: 'done' }, onSuccess: { navigate: '/done' } }` (or `type` on a member, or no executor) | the same row without `onSuccess`: the runner performs the write before any executor, so the hop followed a write whose success declares no result |
25+
| `{ operation: 'delete', … }` / `{ operation: 'custom', … }` on a row | no `operation`: a row delete is the object's own delete affordance, and a row is already the action a bulk def's `'custom'` would name |
26+
| any other `operation` value | `operation: 'update'` with a `patch`, or no `operation` (the runner ran nothing for it) |
27+
28+
**The one-line fix: on a page action row, write `operation` only as `'update'`, and never beside `onSuccess`.** `os migrate meta --from 17` lists the D3 entry `ui-page-action-row-operation-declared`; the rewrite is by hand.
29+
30+
**What an author now sees.** `os validate`'s props gate reports the refusal at the row's `operation` or `onSuccess` (`properties.…`, or `properties.actions.N.…` for a member). For example, `{ actionType: 'api', operation: 'update', patch: { status: 'done' }, onSuccess: { navigate: '/done' } }` on an `action:button` gets: ``An `action:button`'s `onSuccess` (`operation: 'update'`) would follow a result the contract does not declare: the console's action runner reads `operation` before any executor, …``. The same row without `onSuccess` parses unchanged.
31+
32+
**Measured producers.** None in this repository. In objectui (at its pin `1fb1ced36243` and its main `b9814b3`), the action renderers' own tests carry `operation: 'update'` rows without `onSuccess`, which stay accepted; no row carries another `operation` value or the pairing. objectui's metadata-admin form already clears `onSuccess` when an author switches an action into an update.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: the page parse refuses a page component's `data` prop that names the retired view-level `api` data source, so the Studio, REST and MCP save door refuses it as `os validate` does (#23357)
6+
7+
**BREAKING** — a page whose `object-grid`, `object-map`, `object-gantt` or `object-tree` component carries `properties.data` with `provider: 'api'` is now refused by `PageSchema`, at the component's `properties.data.provider`, with the retirement's own prescription. Those four components take a view data source (`ViewDataSchema`) as `data`, so the step-18 retirement of the view-level `api` source reached them through the same schema; but the page parse leaves a component's `properties` an open record, and the component-props gate that refused the binding runs at the CLI doors only. A page saved through the Studio, REST or MCP therefore kept the binding, with its request headers, and the console kept sending it.
8+
9+
Clause-②: yes (narrowing)
10+
11+
The judge is the retirement's own: `ViewDataSchema` parses the component's `data`, and the prescription it raises is raised by the page parse unchanged (one text for a view and a page; the page's issue `code` is `custom`, as every object-level check on `PageSchema` raises). It reaches every component under `regions` and `slots`, at any depth, a component nested in a `page:tabs` item or a container included. It judges nothing else in the bag: another `data` provider, a component whose `data` is a row array (`object-kanban`, `object-calendar`, `object-timeline`), a custom or registered type with no props row, and a component's own `dataSource` binding are untouched. `checkPageComponentViewData` is exported from `@objectstack/spec/ui`, like the four other `PageSchema` checks, so a mirror built from `PageSchema.shape` can re-attach it.
12+
13+
## FROM → TO
14+
15+
| you wrote (accepted by the page parse until this change) | write instead |
16+
| --- | --- |
17+
| `properties.data` with `provider: 'api'` (and its `read` / `write` request blocks) on an `object-grid`, `object-map`, `object-gantt` or `object-tree` page component | `properties.data` with `provider: 'object'` and `object: '<object_name>'`, or `provider: 'value'` with `items: [...]`; an endpoint the page must read is a page component's `dataSource: { provider: 'api', read: { url: '/api/...' }, path? }`, same-origin and with no credential key |
18+
| `read.headers: { Authorization: '...' }` inside that binding | nothing: a credential never lives in metadata, and the console reads an element binding with its own signed-in fetch |
19+
20+
**The one-line fix:** bind each such component to its object or to static rows, and move any endpoint read onto a page component's `dataSource`.
21+
22+
**What an author who still writes it sees.** `definePage()`, `defineStack({ pages })`, `os validate` and `PUT /api/v1/meta/page/:name` refuse the page at `regions[i].components[j].properties.data.provider` (or the nested or slot position) with the prescription. At the save door that is `422 INVALID_METADATA`, nothing persisted.
23+
24+
**A page already stored with such a component.** `os migrate meta --stored` lists it by page and component (the step-18 entry `view-data-api-provider-retired`), and nothing rewrites it: deleting `data` would make the component query its object instead. Left as stored, the page still loads and is served, registered under `[metadata_spec_invalid]` with the refusal in the `_diagnostics` of every read, and its next save is refused with 422. The console stops sending the request when its own reader of the retired arm is deleted (objectstack-ai/objectui#12325); until then a stored page keeps sending it.
25+
26+
<!-- adr-0087: not-required (already-registered view-data-api-provider-retired) the step-18 entry already names the data prop of these four page components in its scope; this change makes the page parse refuse what that entry retired, and its acceptance criteria now name the page parse beside the component-props gate -->

‎.claude/settings.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
"Bash(node --use-env-proxy scripts/pm/label-write.mjs *)",
6969
"Bash(node scripts/pm/post-stamped.mjs *)",
7070
"Bash(node scripts/pm/label-write.mjs *)",
71+
"Bash(node scripts/pm/fleet-write/dispatch.mjs *)",
72+
"Bash(node --use-env-proxy scripts/pm/fleet-write/dispatch.mjs *)",
7173
"Bash(node scripts/pm/close-cards.mjs *)",
7274
"Bash(node --use-env-proxy scripts/pm/close-cards.mjs *)",
7375
"Bash(node scripts/pm/issue-create.mjs *)",

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
- 绿色输出≠ 该绿证明了被测风险:拒收断言、全绿方向与时序、pin 翻转、边界后收益。
177177
- 触 `skills/**` 的 PR 加问整包价值密度:从整包加载的客户 agent 座位读,⛔ 不从作者座位读。
178178
- 超派发预算或小功能大扩写 ⇒ REWORK,⛔ 不因已经写好了放行。
179-
- 判决 ACCEPT:issue 英文短评论,核对清单结论 + 抽查读数 + 偏差,链接 PR,⛔ 不复述其叙事。
179+
- 判决 ACCEPT:PR 评论 `## 席位核对`,清单结论+抽查读数+偏差,⛔ 不复述其叙事;卡留指针行。
180180
- `mcp_calls` 点名写工具(`settings.json` deny 清单 + `update_pull_request`)⇒ 拒收,⛔ 不带注放行。
181181
- 判决 REWORK:逐项反馈,同认领重派;补丁轮优先 SendMessage 续派原 dev;最多 2 轮,第三次升级。
182182
- 判决 ESCALATE:见〈升级与决策〉;熔断任一命中亦判,PR 暂停,答复前 ⛔ 不派下一轮。

‎.claude/skills/pm-dispatch/references/landing-operations.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@
4949
- 车道 PM 的权责三件:验收(复核清单)、首次入队、确认 MERGED;落地执行在案判决,非自批。
5050
- 首次入队:ACCEPT 后挂 6–9 分钟 flip 定点,到点核门禁 job 结论。
5151
- 绿即经中继 op `pr_ready` + `automerge_enable` 落地,拼写见 `rest-channel.md`;未绿按阶梯重挂定点。
52+
- 直敲 `node scripts/pm/fleet-write/dispatch.mjs --repo OWNER/NAME --actions-file F`,⛔ 套壳:许可逐字匹配。
53+
- 写节流拒(exit 10) ⇒ 待 `node scripts/pm/write-pace.mjs --status` 现空槽再重试。
5254
- CI success webhook 不可靠:⛔ 不坐等,也 ⛔ 不忙轮询;定点文本照定时器写法纪律。
5355
- undraft 撞 429 ⇒ 落地入等 `resets at` 态:该刻记卡,到点一次重试;⛔ 不报阻塞、不自定节奏。
5456
- 落地遭分类器拒 ⇒ 停手、报维护者、卡上记命令与拒因;⛔ 不换拼法、不退回 ccr 绕过。
5557
- 确认 MERGED 要两个读数:每轮同时读队列分支与 `origin/main`。
5658
- 落地评论 = 两个读数的探针表 + 一行状态变更(摘标/关卡/收口),⛔ 不复述 `## 席位核对`。
5759
- 契约复核 PASS 落地的 PR 到窗口时已 ready 且 auto-merge 在挂,见 `contract-review.md`。
5860
- 转 ready/入队前跑 `check-governed-merges.mjs --pr N`:受管面照两层;>3000 行(含生成物)照 Tier H。
59-
- 受管路径全在 `.claude/**` 者 Tier S:席内达档复核过落地前检三条即转正式入队。
61+
- 受管路径全在 `.claude/**` 者 Tier S:席内达档复核过落地前检两条即转正式入队。
6062
- Tier H(其余受管面)者:四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地。
6163
- ⛔ 两层不由席位批准;清标即落地同受此闸,漏判会被队列守卫在 merge group 里拒收。
6264
- 再读 `mergeable_state`:`dirty` ⇒ 先 merge `origin/main` 再挂;生成物在面上按 A 的固定序。
@@ -66,7 +68,6 @@
6668
- ② enable-auto-merge 调用根本没落地,重发与效果验证序列见 `platform-readings.md`。
6769
- ③ PR 碰 `.github/workflows/**`:令牌常驻 `workflows: write`(裁决评论 6104822731),仍拒即授权漂移。
6870
- 确认 MERGED 同一动作里给 `Part of` 卡收口、`Fixes` 卡代关但标也须摘,⛔ 不拆到下轮巡检。
69-
- 卡以 completed 关闭且带 `Adopts:` 行 ⇒ 同一动作在所指 fork PR 贴致谢与落地链接并关闭它。
7071
- `Part of` 卡开着不摘 `pm:dispatched`,无在飞物的卡就永远算进 `label:pm:dispatched is:open`。
7172
- 摘标换回 `pm:queue` 或按剩余物定级,加一条写清交付了什么、剩下归谁的评论。
7273
- 同刻读相关卡 `closed_by_pull_requests`,确认没有卡被正文闭合关键词误关。
@@ -93,7 +94,6 @@
9394

9495
- 每一棒都是一整圈:merge main + A 的四步 + 全套验证 + 兄弟单断言复核。
9596
- 然后 PM 复核回报 → 转 ready → 挂 auto-merge 或入队;每棒都重走。
96-
- auto-merge 由 PM 挂,dev 永不碰。
9797
- 相邻两棒同碰一个文件时,交接的是语义不是文本。
9898
- 前棒在回报里写明它对共享文件改动的性质:改名、提取变量、增补断言,还是纯追加。
9999
- PM 原样转告下一棒,并要求两个 PR 的意图叠加,⛔ 禁止机械取一边。

‎.claude/skills/pm-dispatch/references/review-checklist.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
- 收敛期转红走补丁轮续派原 dev,⛔ 不作 REWORK 的理由;重量级卡可在派发令写本单等 CI。
4747
- 每个门禁读数先钉到 PR 当前 head:比对 run `head_sha` 与 `head.sha`,不一致的双向都不入账。
4848
- ⛔ 非当前 head 上的 `cancelled` 零动作、永不重跑:新推送自带全套 run。
49+
- 席位核对门禁证据 = 钉 head 的 check-runs(名+结论);本地并集只证本地子集,⛔ 不替缺席者。
4950
- dev 本地并集钉 head:所抄 `git rev-parse --short HEAD` 不符 PR `head.sha` 或没抄 ⇒ 双向都不入账。
50-
- 复核后又推提交而 HEAD 未动 ⇒ 补跑并集(至少棘轮族)再更新报告。
5151

5252
## 断言与收益
5353

‎AGENTS.md‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -299,20 +299,20 @@ localStorage / auth gotchas.
299299
approval, or a Tier S merge without a PASS record is a seat violation, filed and rolled back. No seat judges this.
300300

301301
15. **⛔ A version release is performed by the maintainer, by hand — no AI seat publishes, tags, cuts a Release, or
302-
triggers a release workflow, and none merges the Version Packages PR.** A rule that binds every seat lives here,
302+
triggers a release, and none merges the Version Packages PR.** A rule that binds every seat lives here,
303303
not only in a lane-specific skill file. **Release-adjacent work stays open to every seat**: the release board,
304304
`.objectui-sha` pin bumps, version reconciliation, writing changesets, compiling release notes when asked, and
305305
*verifying* release state (`npm view`, `git ls-remote --tags`) are ordinary tasks. What is reserved is the
306306
**release act itself**: ⛔ running `changeset publish` / `pnpm run release`, ⛔ pushing a version tag, ⛔
307-
cutting a GitHub Release, ⛔ pushing a runtime image, ⛔ `workflow_dispatch`-ing `release.yml` or any other
308-
publish-capable workflow, ⛔ **approving a pending `release` environment deployment** (ADR-0125: that click IS
309-
the publish authorisation, and a deployment waiting for hours is the system working, not a state you clear), and
310-
⛔ merging — or queueing, or arming auto-merge on — the **Version Packages** PR (`chore: version packages`).
311-
That PR is bot-authored and standing-open by design: it is regenerated six-hourly by `release.yml`'s `version-pr`
312-
job — never on a push to `main`; push drives the publish lane — so "green, current, and nobody has objected"
313-
is its permanent resting state, not a signal that it is due. When you find a publish nobody ordered — a tag or
314-
an npm version that simply appeared — ⛔ do not "repair" it with a counter-publish: file it as an incident for
315-
the maintainer.
307+
cutting a GitHub Release, ⛔ pushing a runtime image, ⛔ `workflow_dispatch`-ing `release.yml` in any lane but
308+
the fleet-write relay's refresh stroke (`refresh_version_pr: true` alone), or any other publish-capable workflow,
309+
⛔ **approving a pending `release` environment deployment** (ADR-0125: that click IS the publish authorisation,
310+
and a deployment waiting for hours is the system working, not a state you clear), and ⛔ merging — or queueing,
311+
or arming auto-merge on — the **Version Packages** PR (`chore: version packages`). That PR is bot-authored and
312+
standing-open by design: it is regenerated six-hourly by `release.yml`'s `version-pr` job — never on a push to
313+
`main`; push drives the publish lane — so "green, current, and nobody has objected" is its permanent resting
314+
state, not a signal that it is due. When you find a publish nobody ordered — a tag or an npm version that simply
315+
appeared — ⛔ do not "repair" it with a counter-publish: file it as an incident for the maintainer.
316316

317317
**The existence of a path to a release is not authorization to walk it, and the YAML is not a barrier that
318318
holds against you.** The publish job is triggered by the **push that lands the Version Packages PR** and held at

0 commit comments

Comments
 (0)