Repository navigation
Commit f2b6cf2
feat(gates): the changeset level follows the metadata protocol —
Fixes #25105
Clause-②: no (tooling and rules text: no published package's accept set
or public surface moves)
Metadata-protocol: none (the declaration line this PR introduces;
nothing a metadata author writes against changes)
## What this PR does
Maintainer ruling A (2026-10-21), as the card files it: the changeset
level is `minor` only when the **metadata protocol** changes — ① added
(a new authorable key, accepted value or enum member, metadata type), ②
narrowed or retired, ③ semantic (meaning or default of an authorable
key) — and `patch` for everything else, REST, drivers, services,
console, `feat:` and `fix!:` alike; non-protocol breaking changes keep
their BREAKING banner and ADR-0087 disposition; release notes are
grouped by what the reader must do. One PR rather than two: the gate and
the rule prose land together, so no in-flight PR meets a level rule
AGENTS.md does not state.
### 1. The declaration — a sibling line, not a third arm
(`scripts/pm/clause2-line.mjs`)
Every PR body and changeset body states its kind on a line of its own
beside the `Clause-②:` line:
```text
Metadata-protocol: added | narrowed | semantic | none
```
The dev's call the card leaves open is **sibling line**, and the reason
is structural: every declaration already written (`Clause-②: no`, `yes
(widening)`, `no (narrowing)`) keeps reading byte-identically —
`readClause2Line`'s predicate, value class and arm reader are untouched,
and the six existing readers (`check-adr-0087-registration`,
`check-changeset-no-major`, `objectui-changeset-digest`,
`pm/check-half-states`, `pm/post-stamped`, `pm/record-recognisers`) see
exactly the readings they saw before. `readMetadataProtocolLine` reuses
the same key-line shape, decoration tolerance, value-token calibration
(first token after the colon, trailing reasoning accepted, an
alternation or an unfilled template `malformed`) and the same describing
/ inline-key / spelling near-miss triage, parameterised on the key and
the closed value set, so the two lines cannot drift in what they accept.
The module still imports nothing (both gates stage it into a throwaway
repo and the level gate runs before `pnpm install`). One token per line;
where several kinds apply the author names the highest in breaking-ness,
`narrowed` over `semantic` over `added`.
### 2. The level axis (`scripts/check-changeset-no-major.mjs`)
Both lines are read from the PR body (the event payload, no token,
re-read on `edited`), exactly where the `Clause-②:` line was read. The
verdicts, every one with its own sentence and its own one-line check-run
annotation:
| Reading | Verdict | Exit |
|:--|:--|:--|
| a kind (`added` / `narrowed` / `semantic`) and at least one moved
package graded `minor`+ | `clean` / `discharged` (#16361 grain,
unchanged) | 0 |
| a kind, moved packages graded `patch`, none raised | `enforce` | 1 |
| `none`, no introduced entry above `patch`, no baseline moved |
`none-clean` — the BREAKING carriers are stated as untouched | 0 |
| `none` with any introduced `minor`/`major`, on **any** package |
`none-minor` | 1 |
| `none` while `packages/spec/authorable-surface/` or
`authorable-defaults/` shards moved | `none-baseline-moved`, naming the
shard and the kinds that would be true | 1 |
| `added` under `Clause-②: no`; `narrowed` beside a line with no
`(narrowing)` arm | `contradiction` (two reasons, two remedies) | 1 |
| the kind unreadable and material — a `patch` on a moved package, any
`minor`, or a moved baseline | `not-measured-material`, naming which
line(s) are missing and spelling all four kinds | 1 |
| the kind unreadable and nothing a kind or a `none` would grade
differently | `not-measured-moot` | 0 |
What is **not** loosened: the `Clause-②:` line unreadable over a moved
package graded `patch` with no raise is still `not-measured-material`
(the #16776 obligation stays where it was); the major guard, the
protocol-lockstep evidence, the grain (#16361), the depth (#16713), the
root (#16692) and the copy (#15204) legs are inherited unchanged, their
batteries re-driven with `Metadata-protocol: added` where they drove
`Clause-②: yes`. What is re-ruled: `Clause-②: yes` or a `(narrowing)`
arm no longer couples to `minor` — a REST widening or a driver-posture
narrowing under `none` is `none-clean` at `patch`. Neither line is
inferred from the other: a `yes` is not read as `added`, an arm is not
read as `narrowed`; the contradictions go the other way only.
`not-declared` is gone rather than renamed: under ruling A a bare `no`
decides nothing about the level. New: a ruling-A battery (37 assertions,
floor 30) drives the real reader and real temp repositories; the roster
floor is 21 → 22. Self-test: 455 assertions.
### 3. Readers of the one reader, touched only as far as the extension
requires
- `scripts/check-adr-0087-registration.mjs`: `Metadata-protocol:
narrowed` is the **fifth** breaking signal (a protocol narrowing is a
break by definition, and ruling A keeps the carriers on it); `added`,
`semantic`, `none` and a malformed kind add no signal — convention-rot
fixtures pin both directions. Self-test: 548 assertions.
- Untouched, by measurement: `pm/record-recognisers.mjs` (the review
face keys on the `Clause-②` arm, which keeps its meaning),
`pm/post-stamped.mjs` (claim keyed-line refusals; the claim template is
out of this claim), `pm/check-half-states.mjs` (reads the claim's
`Clause-②:` only).
### 4. Console refresh grades `patch`
(`scripts/objectui-changeset-digest.mjs`)
The pin-bump changeset is `patch` whatever objectui declared over the
range — in RC pre-mode too, since the console never changes the metadata
protocol and the platform's version line follows it alone.
`declaredLevel` is still derived and reported (the entry lines carry it,
the body says "declared `X`, recorded as `patch`" when they differ),
`CONSOLE_BUMP` still overrides, and the generated file carries
`Metadata-protocol: none` so the gate and the release notes read it. The
#14178 deepen test's discriminator moved from the bump word to the
record itself (breaking entry, ADR-0087 prompt, no degraded list), which
is what that card was about. Self-test: all checks passed (end-to-end
through `bump-objectui.sh` and through the real ADR-0087 gate).
### 5. Release notes grouped by what the reader must do
(`scripts/release-github-releases.mjs`)
The mechanism: the existing GitHub Release body path. `changeset
version` keeps writing CHANGELOG.md as it does (RELEASE-OWNED,
untouched); `planRelease` now posts that section **regrouped** — the
same bullets, byte for byte, none dropped — under the five headings in
the ruling's order, each read from the declarations the entry carries:
需手动处理 (breaking — `narrowed`, the `(narrowing)` arm, a BREAKING banner
or a `!` summary — with no `registered` ADR-0087 conversion), 自动迁移
(breaking with a `registered` conversion `objectstack migrate meta`
applies), 新增能力 (`added`, or a non-breaking `Clause-②: yes` widening),
语义变化 (`semantic`), 其他修复 (the rest: `none`, dependency bumps, the
pre-rule stock). Empty sections are omitted; the truncation path is the
same as before. The splitter is fence-aware and only a column-0 `- `
opens a bullet, so an entry's own FROM → TO tables, nested lists and
fenced examples stay inside it (pinned on the real `16.1.0` section:
same bullet count before and after). The breaking signals mirror the
ADR-0087 gate rather than importing an 8,000-line module into the
release lane. Self-test: 103 assertions, floor 14 → 15.
`annotate-shipped` (`release-pending-publish.mjs`) was measured and is
not the release-notes path — it writes "Shipped in" lead lines into
unconsumed changesets — so it is untouched. The docs release page is
curated by hand (`check-release-section-coverage` generates nothing on
purpose); `docs/releases-maintenance.md` now says a curated section uses
the same five headings.
### 6. The prose (Tier H)
- `AGENTS.md` Post-Task Checklist item 3 restated from the ruling's
table — **line-neutral at 1117** (the ratchet's ceiling, headroom 0):
the paragraph stays 18 lines at ≤119 bytes by condensing its own
retirement-kit sentences (the detail stays in the
`spec-property-retirement` skill it already defers to); no re-wrap
elsewhere, no ceiling raise asked.
- `.github/workflows/pr-automation.yml` "WHICH LEVEL" (inside the quoted
heredoc of `Require a changeset`): the table, the `.strict()` rationale,
the carriers, the gate's verdicts. The step's own `::error::`
annotation, `$MERGE_BASE` wiring and trigger types are untouched (the
self-tests that pin them are green).
- `docs/releases-maintenance.md`: the console grade and the five
sections.
## Measured against the dispatch hypotheses
- **H1** (re-measured at `5050442bb9`): `.changeset/config.json` fixed
group as described; the AGENTS.md `Clause-②` sentence sat at
`:1087–1088`, "WHICH LEVEL" at `:754`; `authorable-surface/` is 14
shards and `authorable-defaults/` 13, key names and defaults only — the
card's reading holds.
- **H2** (readers, `git grep clause2-line`): exactly the six files the
card names. The import-free property is kept; every existing reader's
verdict on today's spellings is unchanged (the Clause-② reader's code
path is byte-identical in behaviour; its three generalised helpers
default to their old arguments).
- **H3** (who depends on `yes ⇒ minor`): one mechanism — `judgeLevel` in
`check-changeset-no-major.mjs` (and the prose that restates it:
`renderLevel`, "WHICH LEVEL", AGENTS.md). `record-recognisers` (review
face) and the ADR-0087 gate read the arm, not the level; the digest's
`major ⇒ minor` downgrade was the sibling coupling and is now `⇒ patch`.
The `Clause-②` value keeps its meaning everywhere else.
- **H4** (transition, read-only over the 12 open PRs' bodies and the 13
changeset files they introduce): 2 PRs introduce no changeset (the
Version Packages PR and the `skip-changeset` shard-timings PR) and are
unaffected. The other **10 all turn `not-measured-material`** on
landing, because none carries a `Metadata-protocol:` line and each
introduces an entry the kind decides on; one PR-body line clears the
reading with no push. Declared truthfully (my reading, for the seat and
the authors, not a verdict): 3 keep `minor` as protocol changes (#24947
`added`; #25074 and #25052 `narrowed`); 2 are `patch`-only and clear
with `none` (#25103, #25100); 4 carry a non-protocol `minor` that ruling
A grades `patch` and would read `none-minor` until regraded (#25101
posture, #25086 runtime refusal, #24940 and #24893 DDL); 1 is borderline
(#25099: a platform object's declared option set — `narrowed` if a
platform object's field options count as the protocol an author writes
against). No other PR was edited.
- **H5** (out of claim): the spellings that must learn the new line are
`.claude/skills/pm-dispatch/SKILL.md:314` (`Clause-②: yes | no` in the
claim template), `.claude/agents/os-dev.md:277` and `:280`,
`.claude/skills/pm-dispatch/references/execution-duties.md:70`,
`contract-review.md:8–19`, `landing-operations.md:11` — listed in the
report with `carrier: seat`. Until they do, dispatched PRs will meet
`not-measured-material` and clear it by one body line.
## Verification
Final head `99eaf8350`; every exit code captured before any pipe (`cmd >
log 2>&1; EXIT=$?`).
- Self-tests of the edited scripts: `check-changeset-no-major
--self-test` 455 assertions (the first run after the gate change failed
77 fixtures that pinned the old `yes ⇒ minor` coupling — the fixtures
were updated, none deleted; every battery stays at or above its floor);
`check-adr-0087-registration --self-test` 548; `check-empty-changeset
--self-test` 170 (the byte-identical entry-regex pin across the four
readers still holds); `objectui-changeset-digest --self-test` all checks
(end-to-end through `bump-objectui.sh` and the real ADR-0087 gate);
`release-github-releases --self-test` 103; `pnpm
check:pm-dispatch-gates` 1913/1913 (fast tier).
- Real scans on this branch against `origin/main`:
`check-adr-0087-registration --base origin/main` exit 0 (the
convention-rot fixtures run on every real scan, `narrowed` among them);
`check-changeset-no-major --base origin/main` exit 0 (locally the level
axis takes its `no-pull-request` lane, by design).
- `eslint --no-inline-config` on the five edited scripts: exit 0.
- Derived union, `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` off the merge base (8 paths): 64 commands,
all run locally, all exit 0 at the final head. Two needed a second pass
and neither was a finding: `pnpm --filter @objectstack/lint run
check:doc-formula-expressions` exited 3 (PREREQUISITE NOT MET,
`@objectstack/formula`/`lint` unbuilt in a fresh worktree) and exits 0
after `turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` under the verify lock; `pnpm
check:pm-dispatch-gates` failed 1 of 1913 on the `no-path-population`
marker `check:release-body` used to carry, which the new
`pm/clause2-line.mjs` import contradicts by the harvester's own rule —
the marker is dropped (the reader IS that family's one path population),
1913/1913 after. `--ran` reconciliation with recorded exit codes: 64
derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.
- CI-owned, not run here: `pnpm lint` (repo-wide), `test:repo`, `Test
Core`; the diff touches no `packages/**` path, so the package build
closure and package tests are n/a, as are `check:dev-prereqs` and
`check-platform-checklist --changed-from` (no exported symbol renamed or
deleted — `not-declared` was a verdict string, not an export). No
ablation: the diff is gates, a workflow and docs, whose pins are the
self-tests above, driven red → green by the change.
- Dogfood battery: n/a — no `!` in the title and no `(narrowing)` arm.
- Changeset: `skip-changeset` — nothing published moves (every changed
path is `scripts/**`, `.github/**`, `docs/**` or `AGENTS.md`; no
package's `files[]` reaches any of them).
## Acceptance notes
- Residual, named rather than silent: the baseline leg sees a changed
key set and a changed default; a refused value, a narrowed enum or a
changed meaning is invisible to it, so the declaration (not the diff)
stays the carrier — stated in the gate's header and in "WHICH LEVEL".
- The kind is read from the PR body for the gate and from the changeset
body for the ADR-0087 gate and the release notes — the same two-carrier
shape the `Clause-②:` line already has; nothing cross-checks the two
copies, as nothing did before.
- `inPreMode` in the digest keeps its export (the RC exemption no longer
reaches the console grade; the function has no other caller in
`scripts/`), noted so a later reader does not take its presence for a
live exemption.
- Not filed, observation only: the release lane's `annotate-shipped`
writes a "Shipped in" lead line above the changeset's declarations; the
release-body splitter reads the whole bullet so the lines are still
found.
## 维护者速读(草稿)
**改了什么。** 按您 2026-10-21 的裁决 A 落地:版本号只跟元数据协议走。每个 PR 和 changeset 在
`Clause-②:` 旁多写一行 `Metadata-protocol: added | narrowed | semantic |
none`;门禁 `check-changeset-no-major` 据此判级 —— 协议加了/收窄了/语义变了,至少一个被改包
`minor`;`none` 一律 `patch`,带 `minor` 即拒,改了
`authorable-surface/`、`authorable-defaults/` 基线却声明 `none` 也拒;`added` 配
`Clause-②: no`、`narrowed` 不带 `(narrowing)` 臂是自相矛盾,拒。ADR-0087 门禁把
`narrowed` 当破坏性信号;console 刷新 changeset 固定 `patch`;GitHub Release
正文按「需手动处理 / 自动迁移 / 新增能力 / 语义变化 / 其他修复」五段重排(CHANGELOG 不动)。AGENTS.md 与
pr-automation.yml 的规则文字同步改写。
**为什么改。** 现状是几乎每次发版都是 minor:95 个待发 changeset 里约一半是
minor,其中只有十来个真的动了元数据协议,版本号说不出协议动没动。裁决 A 把 minor 留给协议变化,`added` 之所以也是
minor,是因为 spec schema 不是 `.strict()`,旧运行时会静默丢掉不认识的键,写新键的 app 要能指一个 minor
底线。
**风险与代价(含回滚)。** ① 过渡成本:现开的 12 个 PR 里 10 个带
changeset,落地当天都会在该门禁上读到「未声明」而红,每个 PR 在正文加一行即清,不用推送;其中按我的判读 4 个是非协议
minor,需按新规改成 patch(报告里逐个点名,由席位和作者定)。② `.claude/**` 的认领模板与 os-dev
不在本卡范围,未改;改之前新派发的 PR 都会先红一次再补行。③ 基线腿只看得见键和默认值,拒值、收窄枚举、语义变化仍靠声明。回滚:整 PR
revert 即可,无数据迁移、无发布面变化(`skip-changeset`)。
**席位意见。**
**你要做的。** Tier H 人合:看一眼 AGENTS.md 第 1084–1101 行与 pr-automation.yml 的
"WHICH LEVEL" 两段规则文字是否就是您要的表述;认可则批准,席位入队落地。另请定 #25099
那类「平台对象字段选项集收窄」算不算元数据协议 `narrowed`(我的建议:算,因为它改的是作者可写的值域)。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VsqhiSrBjB1wJfRNcHPeXN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>Metadata-protocol: line, level axis, console refresh at patch, release notes by reader action (ruling A) (#25114)1 parent ae58b26 commit f2b6cf2
8 files changed
Lines changed: 1347 additions & 295 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
751 | 751 | | |
752 | 752 | | |
753 | 753 | | |
754 | | - | |
755 | | - | |
756 | | - | |
757 | | - | |
758 | | - | |
759 | | - | |
760 | | - | |
761 | | - | |
762 | | - | |
763 | | - | |
764 | | - | |
765 | | - | |
766 | | - | |
767 | | - | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
768 | 788 | | |
769 | 789 | | |
770 | 790 | | |
771 | | - | |
772 | | - | |
773 | | - | |
774 | | - | |
775 | | - | |
776 | | - | |
777 | | - | |
778 | | - | |
779 | | - | |
780 | | - | |
781 | | - | |
782 | | - | |
783 | | - | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
784 | 805 | | |
785 | 806 | | |
786 | 807 | | |
787 | | - | |
788 | | - | |
789 | | - | |
790 | | - | |
791 | | - | |
792 | | - | |
793 | | - | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
794 | 816 | | |
795 | 817 | | |
796 | 818 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1081 | 1081 | | |
1082 | 1082 | | |
1083 | 1083 | | |
1084 | | - | |
1085 | | - | |
1086 | | - | |
1087 | | - | |
1088 | | - | |
1089 | | - | |
1090 | | - | |
1091 | | - | |
1092 | | - | |
1093 | | - | |
1094 | | - | |
1095 | | - | |
1096 | | - | |
1097 | | - | |
1098 | | - | |
1099 | | - | |
1100 | | - | |
1101 | | - | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
1102 | 1102 | | |
1103 | 1103 | | |
1104 | 1104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
43 | | - | |
44 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
| |||
167 | 170 | | |
168 | 171 | | |
169 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
170 | 192 | | |
171 | 193 | | |
172 | 194 | | |
| |||
0 commit comments