Skip to content

Commit cf5f224

Browse files
committed
Merge origin/main into claude/issue-19546-grading-contract-amendments
Resolves the SKILL.md conflict in the triage-seat filing rules: #19513's report-only-instrument line is kept verbatim, and this branch's compressed Acceptance-notes reader question sits beside it. The filing gate's instrument clause now points at `references/instrument-discipline.md` instead of restating the no-dev half #19513 landed. Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE Co-authored-by: Claude <noreply@anthropic.com>
2 parents c7cca67 + b3615f1 commit cf5f224

13 files changed

Lines changed: 383 additions & 85 deletions
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
**BREAKING (published artifact narrows)** — `packages/spec/json-schema/**` now states the `constructor` / `prototype` field-name ban that `ObjectSchema.fields` has always enforced, so a validator reading the published files stops answering PASS on `{"constructor":{"type":"text","label":"R"}}` at `data/Object.properties.fields` — a document the runtime refuses by name (#19346; #18670 item 2, through the `banned-keys` arm).
6+
7+
Clause-②: yes (narrowing)
8+
9+
**No arm joins the closed list.** The ban is over a FINITE list of two names, which is exactly what the existing `banned-keys` arm expresses, so this is a call site moving onto a declared pattern rather than a new public-contract decision. What moved is WHERE the refusal is written: from a `.refine()` on the record's KEY schema to a record-level `bannedKeys(['constructor', 'prototype'])` inside the existing `refuseRecordProtoKey(...)` wrapper. A key-schema `.refine()` is a `custom` check, and `z.toJSONSchema()` has no arm for one, so that rule reached the runtime and never the file.
10+
11+
**The rows retired, by name.** `packages/spec/dropped-refinements.baseline.json` goes from 204 entries / 569 sites to **204 entries / 560 sites** — nine site deletions, no entry deletions (every one of the nine schemas keeps other rows), and **0 sites added anywhere**:
12+
13+
| ledger entry | row deleted |
14+
|:---|:---|
15+
| `api/AssembledInstalledPackage` | `manifest.objects.element.fields.out.keyType` |
16+
| `api/GetInstalledPackageResponse` | `data.options[1].manifest.objects.element.fields.out.keyType` |
17+
| `api/InstalledPackageAtEitherStage` | `options[1].manifest.objects.element.fields.out.keyType` |
18+
| `api/ListInstalledPackagesResponse` | `data.packages.element.options[1].manifest.objects.element.fields.out.keyType` |
19+
| `api/ObjectDefinitionResponse` | `data.fields.out.keyType` |
20+
| `data/Object` | `fields.out.keyType` |
21+
| `system/ChangeSet` | `operations.element.options[3].object.fields.out.keyType` |
22+
| `system/CreateObjectOperation` | `object.fields.out.keyType` |
23+
| `system/MigrationOperation` | `options[3].object.fields.out.keyType` |
24+
25+
Generator census after: **560 dropped across 204 published schemas, 366 projected** — 224 `non-blank-string`, 129 `required-one-of`, **11 `banned-keys`** (2 before), 2 `dependent-required` — 9 undecidable. Across the published tree, **1524 of 1535 files are byte-identical**: the nine carriers above each gain the ban and lose their matching `x-dropped-refinements` row, and the remaining two are the bundle (`objectstack.json`) and the build-input hash.
26+
27+
**⛔ The set of documents the runtime accepts does not move.** The arm is EXACT rather than approximate: a JSON object's properties are exactly its own enumerable string-keyed ones and `propertyNames` judges exactly those names, and `bannedKeys` reads OWN properties and never `key in value` — which is what the key schema judged too, since a record's key loop only ever visits own keys. It is presence and never value: a banned key present with a `null` value is present on both sides. Measured with ajv 8 (draft 2020-12) on the generated `data/Object.json`, before and after, the verdict vector moves in one direction only — `{"constructor": …}` and `{"prototype": …}` go `true` to `false`, while an ordinary document and the near-miss controls `{"constructors": …}` and `{"to_string": …}` are accepted on both sides.
28+
29+
**⚠️ What DOES move is the refusal's location, and a consumer will see it at BOTH layers** — the raw zod issue, and the published `{field, code, message}` envelope every REST / data-API client reads (ADR-0114, built by `api/zod-issues-to-fields.ts`). Measured on this tree by parsing `{"name":"lead","label":"Lead","fields":{"title":{…},"constructor":{…}}}` with the schema before and after:
30+
31+
| layer | | before | after |
32+
|:---|:---|:---|:---|
33+
| raw zod issue | `path` | `['fields', '<the offending key>']` | `['fields']` |
34+
| raw zod issue | `code` | `invalid_key` | `custom` |
35+
| raw zod issue | where the reason text sits | nested one level down, under zod's fixed "Invalid key in record" | the issue's own `message` |
36+
| published envelope | entries | **2** | **1** |
37+
| published envelope | `field` | `fields.constructor` on both entries | `fields` |
38+
| published envelope | `code` | `invalid_shape` (zod's "Invalid key in record") **and** `invalid_value` (the reason) | `invalid_value` alone |
39+
40+
⚠️ `invalid_shape` is a member of the published `FieldErrorCode` vocabulary and it no longer appears for this refusal at all. A client that branched on `invalid_shape` to detect a rejected field NAME must branch on `invalid_value` at `field: "fields"` instead, and must stop expecting two entries where it now receives one.
41+
42+
The message text is unchanged and still names both reserved words in full. The fix for a consumer that keyed on the old shape: match the issue at path `fields` with code `custom` — `field: "fields"`, `code: "invalid_value"` in the envelope — and read its `message` directly, instead of descending into an `invalid_key` issue's nested `issues[0]`. This is the cost of the projection: the closed list can only publish a RECORD-level predicate, and `.refine()` carries no per-key path, so a located-per-key refusal and a published refusal cannot both be had from one rule. The ban list is closed and two names long, so the slot is still named and the two candidate keys are both named in the message.
43+
44+
**⛔ `__proto__` is untouched, and it is a third name rather than a third case.** Its guard is `refuseRecordProtoKey`'s `z.preprocess` on the raw input, because zod's record parser skips that one name with an unconditional `continue` ABOVE the key schema — no schema, and therefore no projection, can ever see it. It holds no ledger row and gains no published keyword here. This change reaches two of the three names, never three.
45+
46+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author can write is removed, renamed or re-spelled: no spec key, no export and no config field changes, and the set of metadata documents the runtime accepts is exactly what it was. What changed is a machine-readable DECLARATION catching up with the runtime it always described, plus the shape of the issue the refusal raises -- so there is nothing for `objectstack migrate meta` to rewrite and no stored representation to convert. -->

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
180180
- 定时器文本 ⛔ 不写结论、不含未经重读即可执行的祈使句。
181181
- 放行认门禁 job 的结论(`completed: success`),⛔ 不认聚合读数。
182182
- advisory 门禁红着进 main 是共享损伤,任何车道发现都立即止血并立单,见 landing-operations B。
183+
- 仪器纪律(硬门禁面、只报告面、新增授权、工具位)见 `references/instrument-discipline.md`。
183184
- dev 自己死了不等于维护者中止:子代理消失是正常死法,走接管(见认领节)。
184185
- 维护者中止只认原话或宿主回报 stopped by the user,⛔ 不据推断立无重启条件的门。
185186
- 共享基础设施修复入队前按症状复查 main,不按 issue 号。
@@ -318,8 +319,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
318319
- 工具加载纪律只约束分诊 fresh session 的开局;执行座位与 dev 不受约束。
319320
- 两级盘点:小时轮以 `since` 窗口读增量,锚 = 座位贴上一份收班简报的时间戳。
320321
- 查重缓存住席内:新会话全量拉,其后 `since` 增量;⛔ 不建分支、不共享、不依赖备份。
321-
- 每日一 fire 跑全仓全量对账并归集日频职责。
322-
- 选层按 fire 时刻,⛔ 不用计数器;简报写明本轮跑的层。
322+
- 每日一 fire 跑全仓全量对账并归集日频职责;选层按 fire 时刻 ⛔ 不用计数器,简报写明层。
323323
- 从不更新的卡不入窗;老化欠账归半状态巡查不归小时轮。
324324
- 每条枚举比对返回数与 `totalCount`,不等 ⇒ 报 `sweep INCOMPLETE` 点名缺口,⛔ 永不报干净。
325325
- Backlog sweep 是常设职责,每 fire 扫任一析取命中的卡。
@@ -357,6 +357,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
357357
- (c) 元数据 = 由写它的人以外的人存储并再作者化的键:React prop 不是,存储的视图配置是。
358358
- 把作者引向运行时会兑现却让事情更糟的元数据的警告不在 (c) 内;记为边界,⛔ 不扩类。
359359
- (a) 须今天可达,(c) 须具名生产者;观察、休眠、零拉动 ⛔ 不立卡、不进汇总卡、无配额。
360+
- 只报告的仪器,报错不配 dev:猜意图的只印读数,误报席位一句推翻,⛔ 不立卡不派 dev。
360361
- Acceptance notes 前先答「哪个 PR 或人会碰到它」;答不出 ⇒ 仍然关,理由写「承接者:无」。
361362
- 先修复:正文被 sanitizer 截断的卡不可派发,评论修复指令后跳过。
362363
- 停摆指令判据必须比其它分类更硬(双读取),事后证伪同处公开作废。
@@ -431,8 +432,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
431432

432433
## 执行座位职责
433434

434-
- 每轮巡检第一判据:先读半状态巡查锚(`half-state-patrol.yml` 置顶 issue)。
435-
- 锚上点名本道卡/PR/座位贴的 H 行逐行处置,再做其余判据;锚行未处置 ⛔ 不开新派发。
435+
- 半状态巡查按需跑(分诊席每日对账可调),H 行是读数不是前提;⛔ 不因锚行停派发。
436436

437437
### 候选与批次
438438

@@ -533,7 +533,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
533533
- 机械放宽判别门禁落地前,入队前的席内契约复核是方向自述的补偿控制。
534534
- 豁免够不到的地板只有维护者裁决能设。
535535
- 席位档策略:skills 车道外的执行席与分诊席默认判断档会话。
536-
- 条款②复核按车道:spec 与 skills 席达档席内审;未达档 ⛔ 不自审,起隔离达档子代理。
536+
- 条款②自审资格按车道:spec 与 skills 席达档席内审;未达档 ⛔ 不自审,起隔离达档子代理。
537537
- 降档出口两条:额度耗尽豁免,与主动预降(余量吃紧可预先降档)。
538538
- 额度耗尽豁免仅当契约复审档实测不可用才落默认判断档,⛔ 不再往下。
539539
- 降档的档位与理由记入认领评论 Container & model 行;档位逐次派发显式传参,永不省略。
@@ -633,8 +633,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
633633
- ④ 轮次报告单列 awaiting a human merge。
634634
- 已入队才读到本条 ⇒ 转 draft 与 disable 都做;出队以阳性探针答,ref 缺席只旁证。
635635
- skills 车道自有 PR:纯代码面如 `scripts/pm/` 由本席按达档自审(清单不减)后落地。
636-
- 受管面两层:Tier H(规则层)= `AGENTS.md`+`CLAUDE.md`+`docs/adr/**`+`docs/NORTH-STAR.md`+发布 `skills/**`。
637-
- Tier S = `.claude/**` 全树;Tier H 四件套等人批;Tier S 经席内达档复核 PASS 在案后 ready → 入队。
636+
- 受管面两层:Tier S = `.claude/**` 全树,余皆 Tier H 等人批;S 经达档复核 PASS 后 ready → 入队。
638637
- 路径面干净的才转 ready → 入队;队列是唯一被认可的落地路径,⛔ 永不队列外合并。
639638
- 入队资格:每 check 绿或预期 skip,⛔ 非必查子集;名单 check-expected-skips.mjs 只判 objectstack。
640639
- 非必查红是真缺陷或坏门,归 PM 入队前处置;第三种按设计而红,三条全立才可带红入队:
@@ -644,17 +643,18 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
644643
### 入队与落地
645644

646645
- 细则见 `references/landing-operations.md`,落地窗口查阅。
646+
- fork PR = 提案,席位永不放行;采纳 diff 内部落地,见 `references/external-contributions.md`。
647647
- 条款②入队闸门:翻 ready / 入队前先取 PR 实际 diff;diff 是事实,卡片语义是预测。
648648
- `--tier` 嫌疑行是提示非裁定;双肢命中任一 ⇒ 无达档条款②复核 PASS 在案 ⛔ 禁止入队。
649649
- 路径肢 = diff 触及契约面 `packages/spec/src/**`,含 error-code-ledger 与 `*.zod.ts` 契约 schema。
650650
- 声明肢 = 认领评论声明 `Clause-②: yes`,与路径无关;错误的 `no` 是可审计的假申报。
651-
- 交付后复核只 spec 与 skills 车道欠,每轮达档:席内审或起子代理;双肢命中即 spec 车道。
651+
- 交付后复核按面欠 ⛔ 不按车道(五面见 `references/contract-review.md`);双肢命中即 spec 车道。
652652
- 子代理起不来 ⇒ 复核缺席,PR 留 draft 队列外等档;唯一旁路是维护者亲审,逐次为准。
653653
- PASS ⇒ 同席剥标并引记录、ready、auto-merge;FAIL ⇒ 补丁轮;⛔ 免复核不放行。
654654
- 真正设计分叉照旧进决策箱,席内复核 ⛔ 不替代维护者裁定。
655655
- 外部评审链降为可选事后审计,非放行前提。
656656
- `needs:contract-review`(恒英文)由席位同笔挂:PR 一现即挂 PR;报告先到则先挂卡。
657-
- `Clause-②: yes` 认领同笔卡上挂标;开 PR `PM_SWEEP_REPO=仓 check-clause2-carriers --pair N` 0 才请审。
657+
- `Clause-②: yes` 认领同笔卡上挂标;开 PR 跑 `--pair N`:只确定性行红才挡请审,C5 只印读数。
658658
- 挂标后复核完成前短暂停靠;⛔ 不前瞻预挂。
659659
- 契约复核的适用面、载体纪律、资格与归属、降档保险丝见 `references/contract-review.md`。
660660
- 碰生成物的 PR 入队前先同步 + 整体重生成:四步序 `bash scripts/pm/os-regen-merge.sh`。

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,38 @@
1212
- 机械地板 claim 时可查树:新导出符号或已发布载荷上的新键恒 `yes`,锁达档契约复核。
1313
- conformance 类 ⛔ 不机械化:填充已声明字段、在两个已发布码之间重选输入类都需判断。
1414
- claim 拿不准 ⇒ 按 `yes` 挂标走达档契约复核;⛔ 不建全量分类学与 claim 时决策程序。
15-
- PR 与卡双载体同笔挂:PR 一存在即挂;报告先于 PR 到达则先挂卡侧,ACCEPT 时补齐 PR 侧。
16-
- 同笔在卡上记一行条款②认定;停靠只防误入队,⛔ 无等外部席位的无限期状态。
15+
- 同笔在卡上记一行条款②认定,PR 侧 ACCEPT 补齐;停靠只防误入队,⛔ 无无限期等外席态。
1716
- 前瞻的条款②事实住卡上裁决与分诊评论、`Clause-②` 申报和 `--tier` 输出。
1817
- 开着的载体恒 = 真实待审;载体不迁移:⛔ 不迁 PR review 或 Request Changes,⛔ 不为迁移留门。
1918
- FAIL 同 PASS 剥双载体:同笔留卡上交接评论(引复审、独立性对、欠改);卡态与 assignee 不动。
2019
- 重挂前先查裁决:闸门标签缺失 ⇒ 先 grep 卡评论找复审结论;`get_reviews` 读空 ≠ 未复审。
2120
- PASS + 无标 + head 未动 = 已清标不是被剥;head 后移或无结论才重挂;清标缺引记录即半态。
2221

23-
## 复核归属与资格(按车道)
22+
## 复核归属与资格(按面)
2423

25-
- 归属派发席,交付后收集复核当轮完成;只 spec 与 skills 车道欠,新 spec 工作恒归 spec 席。
26-
- 按车道:spec 与 skills 席审契约增量;达档席内审,未达档 ⛔ 不自审,起达档子代理。
24+
- 欠不欠按面判 ⛔ 不按车道:diff 碰下列任一面即欠达档复核,交付后当轮完成。
25+
- 复核面 = 出货给用户或 agent 的五处:`content/docs/**`、`apps/docs/**`、CHANGELOG/`.changeset` 散文。
26+
- 加已发布 schema(`packages/spec/src/**` 非测试)与 governed 规则文本(统一定义见 SKILL.md)。
27+
- 五面皆不碰 ⇒ CI 加席位自读 ⛔ 不起第二个 agent;谁跑 = 派发席,达档者席内审。
2728
- 复核记录 = 一条评论落 PR 或卡,席内与子代理同形;散文、dev 自评、`os-dev-report` 恒不算。
2829
- 同形 = `## Contract review` 题头、所审 head sha 独占码段、①②③ 逐项、独立性对、PASS/FAIL。
2930
- 同形含首行 `Served-tier:`:值写常量名 `CONTRACT_REVIEW_TIER`;无此行不成裁决,模板见 `--template`。
3031
- ① derived judgments 逐项:diff 引出的接受集与公开面变化逐条点名判对错。
3132
- ② semver 定级与 changeset 声明一致;③ 边界旗:dev 挂旗与 `open_questions` 逐旗答复或升级。
33+
- 复核形状:只读 diff 与卡片,check 结论取 head 的 check-runs,⛔ 永不本地重跑派生门禁族。
3234
- 独立性件(契约真分叉、dev 挂旗)与保险丝只免席内审,不免复核:起隔离达档子代理。
3335
- 只喂卡片、既有裁决与 PR 本体,⛔ 不喂派发令与派发席自己的结论;简报写成对抗性。
3436
- 隔离复核子代理暂存全写按所审 PR 命名的 `<scratchpad>/pr-<n>/`,⛔ 不读非本轮自写的暂存。
35-
- 独立性对(机读):`Implemented-by:` 写产 diff 者身份;`mode:subagent` 记分支,`mode:remote` 记 session id。
36-
- `Reviewed-by:` 写渲染或采纳裁决的席位 session;隔离复核子代理无 session,记采纳它的席位。
37+
- 独立性对(机读):`Implemented-by:`/`Reviewed-by:` 取值见 `--template`;子代理记采纳它的席位。
3738
- 两者同 session ⇒ 报 SELF-REVIEW;只 `mode:remote` 可达,`mode:subagent` 席内审是设计;值紧跟冒号。
3839
- 清标即落地:PASS ⇒ 同席同笔剥双载体;清标同笔落 PR provenance 评论,引记录 id 与所判 head。
3940
- 轮次报告设复审清单专节,形状与代裁清单同为强制审计。
40-
- 落地前检三条:① 达档条款②复核 PASS 在案,即 PR 或卡上同形复核记录(spec、skills 每轮)。
41+
- 落地前检三条,过则 Tier S 入队、Tier H 等人批:① 达档条款②复核 PASS 在案(同形记录)。
4142
- ② 双载体已清,逐对机读 `PM_SWEEP_REPO=仓 node scripts/pm/check-clause2-carriers.mjs --pair N`。
42-
- 0 = 双肢一致、无放宽 tell、head 上有记录;4 = 任一不成立;3 = 环境答不了,⛔ 不作干净。
43-
- 放宽 tell 由 `scripts/pm/check-widening-tells.mjs` 判,`no` 撞新键/成员/导出/登记即拒,附 file:line。
43+
- 0 = 确定性行全清;4 = 任一不成立,只确定性行红才挡落地;3 = 环境答不了 ⛔ 不作干净。
44+
- 确定性行 = 记录在案、`Served-tier:`、双载体一致、认领形;C5 放宽 tell 只报告,归复核裁。
4445
- ③ PR check 全绿,⛔ 非 required 子集;例外:merge-base 同签名的红不计、按设计而红见 SKILL.md。
4546
- 签名 = 失败步 + 首错行,读 base check runs 的 API ⛔ 不凭口述;主干红止血立单不变。
46-
- Tier H(规则层)等维护者的字;受管路径全在 Tier S 面(`.claude/**`)者达档过本三条入队。
4747

4848
## 降档保险丝(机读)
4949

0 commit comments

Comments
 (0)