Skip to content

Commit 4daa50d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-23210-extension-master-detail
2 parents f974432 + a7a135d commit 4daa50d

45 files changed

Lines changed: 2098 additions & 350 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
---
4+
5+
fix(metadata-protocol)!: a delete naming one package keeps the item's other rows registered, a draft save that names no package of an item two or more packages hold is refused `409 RESOURCE_CONFLICT`, and the delete's `_lock` refusal carries the named package's reason (ADR-0048)
6+
7+
Clause-②: yes (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no authorable spelling, export or stored shape is removed, renamed or re-shaped, and no stored row is rewritten, converted or deleted, so there is nothing for `objectstack migrate meta` to rewrite. What narrows is one runtime write door: a draft save through the metadata API that names no package, of an item whose active rows two or more packages hold, is refused; the remedy is in the request a caller sends (name the package with `?package=`), never a rewrite of a package source or a stored row. What widens is the runtime registry after a delete that names one package: the item's remaining stored row stays registered, so data CRUD keeps serving an object the item read serves. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no exported declaration is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
10+
11+
**BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes.
12+
13+
A `sys_metadata` row is keyed by its package (ADR-0048), so one item can hold one row per package. Since the save and the delete name their package (`?package=` on the REST door, `packageId` in process), three paths still did not take the package they were handed.
14+
15+
**The registry after a delete naming one package.** FROM: `DELETE /api/v1/meta/object/my_object?package=com.acme.a` of an object two packages hold removed `com.acme.a`'s row and the receipt said the other package's row remains, and the item read served that row, but the registry entry was retired as if the deleted row were the whole item. Data CRUD then answered `404 OBJECT_NOT_FOUND` for the object until the next boot or save, and on a kernel whose registry holds the stored rows, registry lookups missed an item of any other type the same way. TO: when a row of the item is still stored after a delete, that row is registered again, as a boot registers it, and nothing is retired. Deleting the last row still retires the item.
16+
17+
**A draft save that names no package.** FROM: `PUT /api/v1/meta/view/my_view?mode=draft` with no `?package=`, of an item whose active rows two packages hold, answered `200` "(env-wide, state=draft)" and stored the draft in whichever package the database returned first. With one package and a package-less row beside it, the draft went into the package or stayed package-less by the same order. TO: the draft inherits the package of the item's active rows when they are bound to one package, whatever the storage order (a package-less row beside it is not a second package). When they are bound to two or more, the save is refused `409 RESOURCE_CONFLICT`, writes nothing, and names the packages with the `?package=` remedy, exactly as the direct save and the delete answer. A draft read that names no package serves the package-unbound draft's version for such an item.
18+
19+
**The delete's `_lock` refusal.** The delete's lock gate now takes the package the delete names, as the save's does. The verdict is unchanged: the lock is the strictest of every package that ships or stores the item, so a delete naming a package is refused whenever the save naming it is. What changes is the prose: when several packages declare the strictest lock, the `ITEM_LOCKED` refusal of a delete naming a package carries that package's reason, where it carried the reason of the package whose id sorts first.
20+
21+
**Remedy.** For an item two or more packages hold, send the draft save with `?package=` naming the package whose draft it is (`packageId` in process). Nothing else asks anything of a caller.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
fix(skills): the automation skill's two reference files write a CRUD `filter` operand as a CEL value envelope or the `{today}` query placeholder; the single-brace `{…}` filter spelling leaves them
6+
7+
Clause-②: no
8+
9+
`skills/objectstack-automation/references/examples-flows.md` and `references/state-machines-and-approvals.md` no longer teach `'{TODAY()}'`, `'{TODAY() + N}'` or `'{record.id}'` at an `update_record` filter operand — spellings the platform refuses (`expression-invalid`) at `objectstack validate`, `registerFlow` and the executor, so an author who copied the example got a refused node. The examples now write what the platform accepts: the query placeholder `'{today}'` for today's date, a CEL value envelope `{ dialect: 'cel', source: 'isoDate(daysFromNow(N))' }` for a one-day window at a date offset, and `{ dialect: 'cel', source: 'record.id' }` for the triggering record's id. The prose names the CEL value envelope where it named a template token (and no longer says a CEL envelope is compared as a literal at a filter operand), and the time-word cheat sheet names the CEL envelope and the `{{ }}` text hole where it named the retired `{…}` template.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/plugin-security': patch
3+
---
4+
5+
refactor(plugin-security): the audience-anchor binding gate on `sys_position_permission_set` is deleted
6+
7+
Clause-②: no
8+
9+
`SecurityPlugin`'s private engine-middleware gate that refused binding a high-privilege permission set to the `everyone` or `guest` position guarded writes to `sys_position_permission_set` only. C3 stage 8 (#23222) unregistered that object, so no write has reached the gate since. The gate, its call in the middleware and its three unit cases are removed.
10+
11+
No behaviour changes, and nothing exported changes. The anchor tier itself (`describeAnchorForbiddenBits` from `@objectstack/spec/security`) still applies wherever a binding is made: when an `everyone` suggestion is confirmed (`confirmAudienceBindingSuggestion`), and on every derivation of the `everyone` anchor. No door binds a set to `guest`.

‎.claude/agents/os-dev.md‎

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,18 @@ model: opus
135135
- ② 受影响包自己的 `pnpm test` / `pnpm typecheck`,用 `--filter` 圈定。
136136
- 受影响包 = 本包;import 方只在公开面变化时欠测试:spec 契约、发布的 `exports`、线上形状。
137137
- 公开面字节不变 ⇒ 只欠本包测试与派生门禁,⛔ 不给每个 import 方补测试。
138-
- ③ 派发词点名的门禁族,加上你看得出被牵连的。
138+
- ③ 读到改动文件的门禁族 = `--commands` 派生清单(按 diff 收窄);派发词清单与全农场归 CI。
139139
- 新 fake engine ⇒ `check:engine-double-contract`;新错误码 ⇒ `check:error-code-casing`。
140-
- `.claude/agents/**` ⇒ `check:agent-model-declared`;任何编辑 ⇒ `check:nul-bytes`。
141-
- ④ 派发词的门禁清单是线索不是规格,当天取的也会漏族;点名的跑绿后按实际改动重推。
140+
- ④ 报告 `gates`:CI 跑的每族引 PR check-runs(名+结论),⛔ 不本地重跑;`--ran` 只对账本地子集。
142141
- 推导用 `node scripts/pm/dispatch-gates.mjs --commands` 不传路径,脚本自己从 merge-base 取变更集。
143-
- 变更集含未提交与未跟踪;跑完把已跑命令清单交给 `--ran <file>` 对账,未跑的族它会点名。
142+
- 变更集含未提交与未跟踪;跑完把已跑清单交给 `--ran <file>` 对账,未跑的本地族它会点名。
144143
- ⛔ 别自己 `git diff` 出一份清单喂它:两点 `origin/main..HEAD` 按此刻的 origin/main 求值。
145144
- 分支切出后落地的姊妹 PR 文件会算到你头上:退出码 0、门禁只多不少,报告的清单却假。
146145
- 浅检出上脚本响亮拒绝而非给错清单,照它说的加深。
147-
- 补跑它新增而你的 diff 触及的,报告里点名新增项;另一半归 PM,在你报告后读真实结论。
148-
- ⛔ 不据此跳过点名族:它们仍是你欠的便宜一半;你不再欠的是报告前等 CI。
146+
- 派生点名却不在 PR check-runs 的族(选择器跳过或仅 push/schedule):本地读数即证据,报告标明。
147+
- 本地独有族(CI 只跑其 `--self-test`,不跑扫描):`check:dev-prereqs`、`check:skill-frame-freshness`。
148+
- 二者判本机状态(`packages/spec/dist` 过期否、检出对 origin/main 新鲜否),CI 新检出无可判。
149+
- 派生不点名二者;触 `packages/spec` 构建或四轴框架文件时本地跑,读数只进报告,留在本地。
149150
- ⑤ diff 编辑了门禁/工具脚本 ⇒ 该脚本自己的测试套件在派生族之外另欠。
150151
- 派生答哪些门禁读你改的文件,不答哪些测试测这个脚本。
151152
- 跑同目录点名它的 `*.test.ts`,加同包 tests 下 `git grep` 该脚本文件名的全部命中。
@@ -155,12 +156,6 @@ model: opus
155156
- 那边的清单从该仓自己的 `package.json` 与 `.github/workflows/` 手工推导。
156157
- 加 `--repo <owner>/<name>` 申报本次答案该属于哪个仓,不匹配即拒绝并同时点名两个仓。
157158
- 仓级扫描(以 `pnpm lint` = `eslint . --no-inline-config` 为首)是 CI 拥有的运行,永不是你欠的。
158-
- 前台上限之内跑得完就可以跑(轻载约 2 分钟;争用下会被 cap 杀,见资源纪律规则 6)。
159-
- 默认交付形态是已证明的收窄:定向跑受影响文件,并证明这次收窄没有排除任何东西。
160-
- 三件证据一起写进报告,缺一不可:① 受检总体读自 eslint 自己的配置,不是你的猜测。
161-
- ② 文件数读自 `--format json` 输出的计数;③ 配置对未触碰文件的不变性声明。
162-
- 不变性声明形如:type-aware linting 未启用 ⇒ 你的 diff 移不动任何未触碰文件的判定。
163-
- 三件都在,收窄就是一次测量;缺一件,它只是没跑;两者在报告里必须分得开。
164159
- 并集在最终 commit 之后跑,并从那次运行引用 `git rev-parse --short HEAD`。
165160
- 报告的 `tests` 字段和 PR 正文两处都要引;门禁日志不带 sha,最后 commit 前的并集量的旧树。
166161
- 迟到的 commit 挪动的恰是 ratchet 读数;复核后任何一次 push,都在新 head 上重跑并集。
@@ -243,6 +238,7 @@ model: opus
243238
- 变多:删掉一个喂计数的读取,能让下游门禁多出一个发现。
244239
- 反转:canonical-first 的 `??` 链,非法拼写落到 schema 的具名拒收,规则绿、schema 红。
245240
- 报告你实际观察到的方向;永不硬套模板的预设。
241+
- 消融一行为一次,仅当 diff 改了测试钉住的行为;配置、脚本、workflow、文档 ⛔ 不消融。
246242
- ablation 的成立条件是解析路径,不是套件名:被测主体经依赖的 `exports` 解析。
247243
- 那指向该包的 `dist/`,不是 `src/`;且没有 vitest alias 把 specifier 拉回源码。
248244
- 这批 pair 的台账是 `scripts/check-test-source-alias.mjs` 的 `KNOWN_UNALIASED_TEST_IMPORTS`。
@@ -276,6 +272,7 @@ model: opus
276272
- 分区 pin `test/vitest-tiers-partition.test.ts` 在某个测试文件两层皆无或皆有时变红。
277273
- 用 `git push -u origin claude/issue-<n>-<slug>` 推上去,网络失败退避重试;pre-push 拒卡片 trailer。
278274
- Draft PR 指向 `main`,正文首行 `Fixes #<n>`;合并不应关卡时用 `Part of #<n>`,并说明留下哪一半。
275+
- PR 开出后合 main 只因冲突、main 致 CI 红、出队,后只跑 §10 复检;dogfood 只在终 head 跑一次。
279276
- 认领带 `Clause-②:` 行才照抄进正文行首:`Check Changeset` 读正文不读卡;无则零写,⛔ 不自造。
280277
- ⛔ 永不 `Fixes` 一张还在决策箱的卡:合并会静默关掉它,而收件箱过滤只读 open。
281278
- ⛔ 不写否定式的关单句,它照样关掉点名的卡:解析器无视否定,只匹配关键词 + `#<n>`。
@@ -367,7 +364,8 @@ model: opus
367364
"session": "session_<id> — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
368365
"premise_still_valid": true,
369366
"summary": "what was implemented, 2-4 sentences",
370-
"tests": "commands run + pass/fail evidence (real output excerpts); ablation: rebuild + on-disk mutation proof",
367+
"tests": "commands run + pass/fail evidence (real output excerpts); ablation only where a tested behaviour changed: rebuild + on-disk mutation proof",
368+
"gates": "local subset: each command + exit code captured before any pipe + --ran reconciliation; every family CI runs: the PR's check-runs (name + conclusion) cited, not re-run locally",
371369
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
372370
"api_writes": "<n> — REST proxy writes, each endpoint listed",
373371
"open_questions": [

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,13 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
185185
- governed 面同含 `AGENTS.md`+`CLAUDE.md`;`GOVERNED_REPOS` 同治理、行可限仓,执行席恒随落地仓车道。
186186
- 路径面命中 Tier H ⇒ ACCEPT 换终局四件套,混合 diff ⛔ 不按比例判;要拆让 dev 单独开 PR。
187187
- 改动 >3000 行(含生成物)同换终局四件套,⛔ 无 Tier S 例外;读数 = PR additions+deletions。
188-
- ① 复核结论照常写在 issue 上;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。
189-
- ② PR 留给维护者看得见地悬着;终局两条:人工直合即审核记录;授权批准 ⇒ 席位落地。
190-
- 看得见 = ACCEPT 同笔挂 `needs-user-decision` + 贴终稿「维护者速读」评论;①仍是审核记录。
188+
- ① 席位记录一条落 PR,两层同欠:Tier H 置顶速读五段,其下 `## 席位核对`;卡只留指针行。
189+
- ② PR 悬着等人批,同笔挂 `needs-user-decision`;人工直合即审核记录,授权批准 ⇒ 席位落地。
190+
- 指针 `ACCEPT → PR #n comment <id>`;技能面 hunk 须由契约复审档的席复核,档外席先交 skills 席。
191+
- 核对六项(席位读此):形态/前提实测/门禁 check-runs/接受的偏差/范围外去向/落地条款。
191192
- 速读五段固定:改了什么/为什么改/风险与代价(含回滚)/席位意见/你要做的(一个动作)。
192193
- 草稿归 dev:受管面 PR 正文带 `## 维护者速读(草稿)`,中文、业务角度,席位意见留空。
193-
- 终稿 = 席位对照自己读的 diff 校正草稿、填席位意见后贴评论;维护者只读评论。
194+
- 终稿 = 席位对照自己读的 diff 校正草稿、填席位意见,置顶席位记录;维护者只读评论。
194195
- PR 上的标签 = 待维护者审阅,不入六态;与请审同为等人批清单,随获批或撤回判决离开。
195196
- 批准判定单源 = 队列守卫常量 `GOVERNED_APPROVERS`:授权账户 APPROVED 即算,⛔ 不卡 `commit_id`。
196197
- 批准后再推亦不过期;席位落地 = 过落地前检、清标、ready、auto-merge,踢出/变基同法。
@@ -199,7 +200,6 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
199200
- 该账户改为把 PR assign 给它替代通知,另一账户照常请审;轮次报告点名说明走了兜底。
200201
- 请审走免碰 draft 位的 REST 专用路,ready/draft 走中继 op;MCP 兜底已拒;端点见 rest-channel。
201202
- ④ 轮次报告单列 awaiting a human merge。
202-
- 已入队 ⇒ 转 draft 并 disable,即请维护者手动出队(席位无实测手段);出队以阳性探针答。
203203
- skills 车道自有 PR:纯代码面如 `scripts/pm/` 由本席按达档自审(清单不减)后落地。
204204
- 受管面两层:Tier S = `.claude/**` 全树,余皆 Tier H 等人批;S 经达档复核 PASS 后 ready → 入队。
205205
- 入队资格:每 check 绿或预期 skip,⛔ 非必查子集;名单 check-expected-skips.mjs 只判 objectstack。

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
- 复核面 = 契约三面:`Clause-②: yes` 申报、已发布 schema(`packages/spec/src/**` 非测试)、governed
99
规则文本(统一定义见 SKILL.md);`.changeset`/CHANGELOG、`content/docs/**`、`apps/docs/**` 散文面
1010
⛔ 不在其列:派发席按本席档在 ACCEPT 逐句对照 diff 核,ACCEPT 点名所核句。
11+
- 档随面定:已发布 schema 或 governed 规则文本 ⇒ `CONTRACT_REVIEW_TIER`,混合 diff 一处命中即此档;
12+
仅凭 `Clause-②: yes` 申报而欠 ⇒ `TIER_DEFAULT`,由车道席位作复核记录;档恰等不作下限。
13+
- 面读 PR 已提交树的文件清单,同队列守卫判 governed 路径;⛔ 不读席位自述。
1114
- 已发布面以包的 `exports` 映射为准:从入口类型图不可达、且 `exports` 不可寻址的 `.d.ts` 是
1215
出货字节,⛔ 不是已发布接受集,改它不触发 `Clause-②: yes`;legacy node10 深引用在契约之外。
1316
- 三面皆不碰 ⇒ CI 加席位自读 ⛔ 不起第二个 agent;谁跑 = 派发席,达档者席内审。
1417
- 复核记录 = 一条评论落 PR 或卡,席内与子代理同形;散文、dev 自评、`os-dev-report` 恒不算。
1518
- 同形 = `## Contract review` 题头、所审 head sha 独占码段、①②③ 逐项、独立性对、PASS/FAIL。
16-
- 同形含首行 `Served-tier:`:值写常量名 `CONTRACT_REVIEW_TIER`;无此行不成裁决,
17-
模板见 `node scripts/pm/record-recognisers.mjs --template`。
19+
- 同形含首行 `Served-tier:`:值写该面所定档的常量名 `CONTRACT_REVIEW_TIER` 或 `TIER_DEFAULT`;
20+
无此行不成裁决;模板 `node scripts/pm/record-recognisers.mjs --template`,申报面加 `--face declaration`。
1821
- 同形另带 `Local-runs:` 行:`none`,或 `probe — REASON` 具名唯一本地探针;
1922
有行必校,缺行旧记录不改判。
2023
- ① derived judgments 逐项:diff 引出的接受集与公开面变化逐条点名判对错。
@@ -27,7 +30,8 @@
2730
- 独立性对:`Implemented-by:`/`Reviewed-by:` 取值同见 `--template`;子代理记采纳它的席位。
2831
- 轮次报告设复审清单专节,形状与代裁清单同为强制审计。
2932
- 落地前检两条,过则 Tier S 入队、Tier H 等人批:① 达档条款②复核 PASS 在案(同形记录)。
30-
- 例外:纯重生成 head 后移原记录继续管;判据机读已提交树;PR 落 `Regen-provenance:` 行。
33+
- 例外:纯重生成或只动上列散文面的 head 后移原记录继续管;判据机读已提交树;
34+
PR 落 `Regen-provenance:` 行。
3135
- ② PR check 全绿,⛔ 非 required 子集;例外:merge-base 同签名的红不计、按设计而红见 SKILL.md。
3236
- 签名 = 失败步 + 首错行,读 base check runs 的 API ⛔ 不凭口述;主干红止血立单不变。
3337

‎.claude/skills/pm-dispatch/references/cross-repo-coordination.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
- 多车道仓分诊空缺时,会话型执行 PM 可代扫:只做分诊动作,不跨车道认领,贴上注明。
4343
- 代扫在分诊座位有主时立即停止。
4444
- 规则 5:一块板不设第二跟踪器:pm 标签就是状态机,org Project 只是维护者的聚合视图。
45-
- 认领即跟到 MERGED:派发后发现的跨车道面(含 spec)不移卡,认领席借契约复审档隔离复核。
45+
- 认领即跟到 MERGED:派发后发现跨车道面(含 spec)不移卡,复核按面定档,未达档借隔离复核。
4646
- 复核记录带 `Implemented-by:`/`Reviewed-by:`;`pm:retriage` 改路由只对未派发卡。
4747
- 转移落对方队列:目标仓立单带 `pm:queue`、出处行与一行可执行判据,依赖用 `Blocked-by:`。
4848
- 跨座位请求骑在它所关的既有卡上:请求席在自卡写 `Blocked-by:`,并在目标卡留一条评论。

‎.claude/skills/pm-dispatch/references/dispatch-runbook.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@
216216
- 该行原文:基于合并后的代码工作,issue 引用的片段可能已变,先核对当前 main。
217217
- 在飞重叠每轮拦截:main 新落 PR 与在飞申报文件面求交,相交即发四句警告。
218218
- 另一腿:本轮删/改名标识符 grep 每个在飞 PR 的 diff,命中同发,⛔ 不因文件面不相交跳过。
219-
- 四句:合 main 重跑测试矩阵、读对方 diff 重划边界、只补它没覆盖的、被完全覆盖就停下。
219+
- 四句:合 main 只因冲突/main 致红/出队,再 §10 复检、按其 diff 划界、补缺口、全覆盖即停。
220220
- ⛔ 不硬造 diff。中途收窄标准条款须点名替换承接人与时点,缺一 dev 报 blocked 不半执行。
221221
- 全仓 pin 清扫两句原文:① grep 错误码与错误消息全仓扫同语义 pin,一轮翻完,不只改本包。
222222
- ② 翻转后的 pin 必须承重:断言新语义的实质(行数、译文、状态码),不是旧断言已删。

0 commit comments

Comments
 (0)