Repository navigation
feat(gates): the changeset level follows the metadata protocol — Metadata-protocol: line, level axis, console refresh at patch, release notes by reader action (ruling A) - #25114
Conversation
…etadata-protocol:` line beside `Clause-②:` Maintainer ruling A (2026-10-21): `minor` only when the metadata protocol is added to, narrowed or changed in meaning; `patch` otherwise. The one reader (`scripts/pm/clause2-line.mjs`) gains the sibling line, import-free, with the `Clause-②:` reader byte-identical in behaviour. `check-changeset-no-major.mjs` judges the level on the kind: a kind owes `minor` on a moved package, `none` owes `patch` everywhere and may not move an authorable-surface/-defaults baseline, `added` under `no` and `narrowed` without the `(narrowing)` arm are contradictions, and an unread kind that decides the verdict is refused as NOT MEASURED. Fixtures updated; a ruling-A battery added (floor 21 -> 22). Claude-Session: https://claude.ai/code/session_01VsqhiSrBjB1wJfRNcHPeXN Co-authored-by: Claude <noreply@anthropic.com>
…rowed`, the console refresh grades `patch`, the Release body groups by what the reader must do - check-adr-0087-registration.mjs: `Metadata-protocol: narrowed` is the fifth breaking signal; `added`, `semantic`, `none` and a malformed kind add none. - objectui-changeset-digest.mjs: the console refresh changeset is `patch` whatever objectui declared (CONSOLE_BUMP still overrides), says so in the body, and carries `Metadata-protocol: none`. - release-github-releases.mjs: the GitHub Release body is the CHANGELOG section regrouped under 需手动处理 / 自动迁移 / 新增能力 / 语义变化 / 其他修复, read from each entry's declarations; the CHANGELOG itself is untouched. - AGENTS.md changeset rule and pr-automation.yml "WHICH LEVEL" restated from the ruling's table (AGENTS.md line-neutral at 1117). - docs/releases-maintenance.md: the console grade and the five sections. Claude-Session: https://claude.ai/code/session_01VsqhiSrBjB1wJfRNcHPeXN Co-authored-by: Claude <noreply@anthropic.com>
…r — the declaration reader is its one path population The renderer now imports scripts/pm/clause2-line.mjs (ruling A: the readings decide the release-note sections), and a pathy first-party import is a watch hint by the harvester's own rule; the marker and the import contradicted each other (check:pm-dispatch-gates, 1 of 1913), and the import is the true statement — a card that edits the reader owes this self-test. Claude-Session: https://claude.ai/code/session_01VsqhiSrBjB1wJfRNcHPeXN Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read, and nothing else: card #25105 (body, claim comment 6314107974, os-dev-report comment 6315700975 — no ruling comment beyond the body); PR #25114 (body, the 8-file list, the net diff ① Derived judgments
② Semver level
③ Boundary flagsThe dev's
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读 ·
|
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
minoronly 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) — andpatchfor everything else, REST, drivers, services, console,feat:andfix!: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: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.readMetadataProtocolLinereuses the same key-line shape, decoration tolerance, value-token calibration (first token after the colon, trailing reasoning accepted, an alternation or an unfilled templatemalformed) 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 beforepnpm install). One token per line; where several kinds apply the author names the highest in breaking-ness,narrowedoversemanticoveradded.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 theClause-②:line was read. The verdicts, every one with its own sentence and its own one-line check-run annotation:added/narrowed/semantic) and at least one moved package gradedminor+clean/discharged(#16361 grain, unchanged)patch, none raisedenforcenone, no introduced entry abovepatch, no baseline movednone-clean— the BREAKING carriers are stated as untouchednonewith any introducedminor/major, on any packagenone-minornonewhilepackages/spec/authorable-surface/orauthorable-defaults/shards movednone-baseline-moved, naming the shard and the kinds that would be trueaddedunderClause-②: no;narrowedbeside a line with no(narrowing)armcontradiction(two reasons, two remedies)patchon a moved package, anyminor, or a moved baselinenot-measured-material, naming which line(s) are missing and spelling all four kindsnonewould grade differentlynot-measured-mootWhat is not loosened: the
Clause-②:line unreadable over a moved package gradedpatchwith no raise is stillnot-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 withMetadata-protocol: addedwhere they droveClause-②: yes. What is re-ruled:Clause-②: yesor a(narrowing)arm no longer couples tominor— a REST widening or a driver-posture narrowing undernoneisnone-cleanatpatch. Neither line is inferred from the other: ayesis not read asadded, an arm is not read asnarrowed; the contradictions go the other way only.not-declaredis gone rather than renamed: under ruling A a barenodecides 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: narrowedis the fifth breaking signal (a protocol narrowing is a break by definition, and ruling A keeps the carriers on it);added,semantic,noneand a malformed kind add no signal — convention-rot fixtures pin both directions. Self-test: 548 assertions.pm/record-recognisers.mjs(the review face keys on theClause-②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'sClause-②:only).4. Console refresh grades
patch(scripts/objectui-changeset-digest.mjs)The pin-bump changeset is
patchwhatever 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.declaredLevelis still derived and reported (the entry lines carry it, the body says "declaredX, recorded aspatch" when they differ),CONSOLE_BUMPstill overrides, and the generated file carriesMetadata-protocol: noneso 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 throughbump-objectui.shand 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 versionkeeps writing CHANGELOG.md as it does (RELEASE-OWNED, untouched);planReleasenow 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 noregisteredADR-0087 conversion), 自动迁移 (breaking with aregisteredconversionobjectstack migrate metaapplies), 新增能力 (added, or a non-breakingClause-②: yeswidening), 语义变化 (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 real16.1.0section: 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-coveragegenerates nothing on purpose);docs/releases-maintenance.mdnow says a curated section uses the same five headings.6. The prose (Tier H)
AGENTS.mdPost-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 thespec-property-retirementskill it already defers to); no re-wrap elsewhere, no ceiling raise asked..github/workflows/pr-automation.yml"WHICH LEVEL" (inside the quoted heredoc ofRequire a changeset): the table, the.strict()rationale, the carriers, the gate's verdicts. The step's own::error::annotation,$MERGE_BASEwiring 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
5050442bb9):.changeset/config.jsonfixed group as described; the AGENTS.mdClause-②sentence sat at:1087–1088, "WHICH LEVEL" at:754;authorable-surface/is 14 shards andauthorable-defaults/13, key names and defaults only — the card's reading holds.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).yes ⇒ minor): one mechanism —judgeLevelincheck-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'smajor ⇒ minordowngrade was the sibling coupling and is now⇒ patch. TheClause-②value keeps its meaning everywhere else.skip-changesetshard-timings PR) and are unaffected. The other 10 all turnnot-measured-materialon landing, because none carries aMetadata-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 keepminoras protocol changes (feat(spec,lint): a bulk action's confirmText is a template slot over count and its params, judged at validate, compile and the publish gate #24947added; feat(spec)!: refuse a user field whose reference names an object other than sys_user; every user field resolves to sys_user #25074 and fix(lint)!: os build and the object save door refuse a validations[] rule whose predicate reads parent #25052narrowed); 2 arepatch-only and clear withnone(spec: the flows.approvals translation rows read live, and ApprovalRequestRow's label docblocks name the reader's-locale label #25103, fix(platform-objects): the sys_user page's Security tab shows only on the viewer's own record #25100); 4 carry a non-protocolminorthat ruling A gradespatchand would readnone-minoruntil regraded (fix(plugin-security)!: a delegated operation runs only in an organization the delegator's membership backs #25101 posture, fix(objectql)!: under strictReadonlyWrites, a write whose value a stored expression replaces is refused, and nothing is written #25086 runtime refusal, feat(driver-sql,driver-turso)!: 18.x step 7 part 7b-0 — a table the driver creates carries its tenant column NOT NULL #24940 and feat(driver-sql,driver-turso,driver-memory,objectql,cli,metadata-protocol): plain organization key part in new unique indexes; the differ reads COALESCE(tenant) as tenant on a certified database (18.x step 7b-1 + 7b-2) #24893 DDL); 1 is borderline (fix(platform-objects)!: sys_oauth_application.type declares only web and native, the two application types its writer produces #25099: a platform object's declared option set —narrowedif a platform object's field options count as the protocol an author writes against). No other PR was edited..claude/skills/pm-dispatch/SKILL.md:314(Clause-②: yes | noin the claim template),.claude/agents/os-dev.md:277and:280,.claude/skills/pm-dispatch/references/execution-duties.md:70,contract-review.md:8–19,landing-operations.md:11— listed in the report withcarrier: seat. Until they do, dispatched PRs will meetnot-measured-materialand clear it by one body line.Verification
Final head
99eaf8350; every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?).check-changeset-no-major --self-test455 assertions (the first run after the gate change failed 77 fixtures that pinned the oldyes ⇒ minorcoupling — the fixtures were updated, none deleted; every battery stays at or above its floor);check-adr-0087-registration --self-test548;check-empty-changeset --self-test170 (the byte-identical entry-regex pin across the four readers still holds);objectui-changeset-digest --self-testall checks (end-to-end throughbump-objectui.shand the real ADR-0087 gate);release-github-releases --self-test103;pnpm check:pm-dispatch-gates1913/1913 (fast tier).origin/main:check-adr-0087-registration --base origin/mainexit 0 (the convention-rot fixtures run on every real scan,narrowedamong them);check-changeset-no-major --base origin/mainexit 0 (locally the level axis takes itsno-pull-requestlane, by design).eslint --no-inline-configon the five edited scripts: exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackoff 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-expressionsexited 3 (PREREQUISITE NOT MET,@objectstack/formula/lintunbuilt in a fresh worktree) and exits 0 afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder the verify lock;pnpm check:pm-dispatch-gatesfailed 1 of 1913 on theno-path-populationmarkercheck:release-bodyused to carry, which the newpm/clause2-line.mjsimport contradicts by the harvester's own rule — the marker is dropped (the reader IS that family's one path population), 1913/1913 after.--ranreconciliation with recorded exit codes: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint(repo-wide),test:repo,Test Core; the diff touches nopackages/**path, so the package build closure and package tests are n/a, as arecheck:dev-prereqsandcheck-platform-checklist --changed-from(no exported symbol renamed or deleted —not-declaredwas 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.!in the title and no(narrowing)arm.skip-changeset— nothing published moves (every changed path isscripts/**,.github/**,docs/**orAGENTS.md; no package'sfiles[]reaches any of them).Acceptance notes
Clause-②:line already has; nothing cross-checks the two copies, as nothing did before.inPreModein the digest keeps its export (the RC exemption no longer reaches the console grade; the function has no other caller inscripts/), noted so a later reader does not take its presence for a live exemption.annotate-shippedwrites 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