skills(objectstack-ui): state the one-app-per-package cap in App Navigation (GOVERNED — token ceiling raised to the landed count under a maintainer ruling) - #17310
Conversation
…gation
The App Navigation section tells an author to `register under
defineStack({ apps: [...] })` and stops there. Nothing in the published
bundle says the array holds one entry: measured across `skills/`, the
one-app rule appears 0 times in any spelling, while `defineStack` appears
54 times and `App.create` 3 — the zeros are readings, not an empty corpus.
An agent authoring `*.app.ts` from this skill therefore has no reason not
to write one app per audience, and finds out at `defineStack` load time,
after the design is written.
All three `ADR-0019` citations in the published bundle point at
`0019-approval-as-flow-node`, so this one names its record explicitly.
⚠️ This overruns the published-skills token ratchet by 59 tokens
(3874 against a ceiling of 3815, headroom measured at 0 before writing).
The ceiling is deliberately NOT touched and nothing else is trimmed to
pay for it — both are maintainer decisions. See the PR body.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
…e-app-rule-ui-skill
…landed count The one-app-per-package cap added to `skills/objectstack-ui/SKILL.md` sits 41 tokens over the shrink-only ratchet. The maintainer ruled on 2026-09-13 that this PR may grow the file; the ruling is quoted verbatim beside the row and in the PR body, which is the evidence bar the gate's header sets. Re-measured on the merged head rather than copied from the pre-merge CI run: `origin/main` deleted the `page` view-type row from the same file after that run, so the landed count is 3856 (15424 bytes), not the 3874 CI reported. The row is pinned AT that measurement, so it carries zero headroom. No other ceiling moves, and no text was re-wrapped or reflowed to buy tokens. Claude-Session: https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么 —— 两个文件,共 +22 / −1。 ① ② 为什么改 —— ① 是因为这条规则在已发布技能包里一次都没出现过。实测( ② 是因为您已就这 41 个 token 给了裁决,而按门禁自己 header 的规定,「正文引用维护者裁决」是抬上限唯一合法的落地方式。 风险与代价(含回滚) —— 代价是 41 个 token,不是最初报的 59。 席位意见 —— 建议合并。三条理由,都是本席自己对着 diff 和树核验过的,不取报告自述:
你要做的 —— 人工合并(受管面 CI 读数,取于 ⛔ 本席未入队、未挂 auto-merge、未提交任何批准 review。 Generated by Claude Code |
Item 2 of #16565. This PR does not close that card — item 1/3 ship in the companion PR #17309 and item 4 remains open. No closing keyword is used here.
Authorization for the ratchet raise
check-skills-token-ratchetis shrink-only, and its header sets exactly one evidence bar for the other direction: "The other direction lands only in a PR whose body quotes a maintainer ruling authorizing it." That ruling was given by the maintainer in PM chat on 2026-09-13, authorizing this PR specifically. Reproduced verbatim and untranslated, as the Communication rule requires:This supersedes the earlier revision of this PR, whose title and body said the change was "over the token ratchet by 59" and "needs a maintainer decision", and which deliberately left the ceiling untouched. That decision has now been made. The ceiling is raised in this revision, the PR is no longer red by design, and nothing was re-wrapped, reflowed or trimmed to buy tokens — the raise is paid for with the ruling, which is what the ruling is for.
The re-measurement — the raise is pinned AT the landed count, not at the number CI reported
The
3874in the earlier revision was measured ondc077e17, beforeorigin/mainwas merged in. It is stale, and copying it forward would have granted 18 tokens of headroom this file has not earned.origin/maindeleted thepagerow from this same file's view-type table while this PR sat open:So the arithmetic, re-derived on the merged head
4f22acc38d, in bytes because bytes are what this gate'sceil(utf8 bytes / 4)convention divides:The ceiling moves 3815 to 3856, by 41 rather than 59, and is pinned at the measurement — zero headroom, so the next token added to this file is paid for by deleting one from it, exactly as before. Independently reproducible without running the script, which is the point of the convention:
Exactly one ceiling entry moves. No other row in
CEILINGSis touched, and the raise carries its ruling in a comment on the row itself, in the shape the surrounding rows already use.维护者速读(草稿)
改了什么 —— 两处。①
skills/objectstack-ui/SKILL.md的 App Navigation 一节加三行(与上一版完全相同,未改一字):一个type: 'app'包只暴露一个 App,多受众是它内部用requiredPermissions门控的分组,并把ADR-0019写成指名到app-as-consumer-unit记录。②scripts/check-skills-token-ratchet.mjs里skills/objectstack-ui/SKILL.md这一行的上限由 3815 抬到 3856,并在该行上方按本文件既有格式记下裁决原话与算术。为什么改 —— ① 的理由没变:这是 AI 作者真正会读的地方。发布包里的实测:一个 app 的规则出现 0 次(所有拼写),而
defineStack出现 54 次、App.create3 次 —— 零是读数,不是空语料。② 是因为你已经就这 41 个 token 给了裁决(见上方 Authorization 节),按门禁自己的规则,这正是抬上限唯一合法的落地方式。风险与代价(含回滚) —— 代价是 41 个 token,不是上一版说的 59:
origin/main在本 PR 挂起期间删掉了同文件里的page视图类型行,少了 70 字节。抬到的 3856 是合入后实测值,余量仍为 0 —— 下一个作者往这个文件加一个 token,仍然只能靠在同文件删一个来付账,棘轮没有被放松,只是按裁决上移了一格。回滚成本仍为零:两个文件、一个常量、无代码、无生成物。⛔ 本 PR 没有跳过、禁用或隔离任何测试或门禁。席位意见 —— (留空,待席位定稿)
你要做的 —— 人工合并(治理面,
skills/**)。本 PR 仍不因合并关闭 #16565。The measurement behind the teaching, unchanged
Taken on
origin/mainatb90aff81f2, over the whole published bundle. Every zero carries a same-corpus positive control from the same pass.So the rule is absent in every spelling from a bundle that names
defineStack54 times, and the number an author would follow resolves, in this bundle's own usage, to the wrong record.Changeset: still the label, not a file
Not a changeset — the
skip-changesetlabel, already applied. Measured rather than assumed: no package manifest'sfiles[]containsskills,skills/is not a workspace member, and the root manifest is private. The second file this revision touches,scripts/check-skills-token-ratchet.mjs, is a repo gate script that ships in nothing.pr-automation.ymlspells this exact case out, including that an empty-frontmatter changeset is rejected and that the label is what "names no package" is for.Verification, re-run on the merged head
Gate family derived from this diff by the repo's own deriver, not recalled:
The gate this PR is about, and its self-test, on the merged head:
check-ratchet-remedy-authoritywas run deliberately, because this gate's own source carries a placement warning about prose nearCEILINGSflipping its classification frommarkedtoexcluded. The new comment sits inside the map, far from the declaration, and the classification holds:pnpm lintis a whole-repo scan CI owns; the local reading is a declared narrowing, with the three facts that make it a measurement rather than a skipped step: (a) the linted population is read fromeslint.config.mjsitself —files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']— which does not include.md, and eslint confirms it in its own words forSKILL.md("File ignored because no matching configuration was supplied"); (b) the file count is read from--format json: 2 paths handed in, 1 in population, 0 errors, 0 warnings on it; (c) the invariance claim — this repo runs oneeslint.config.mjswhich never enables type-aware linting for any file (noparserOptions.project, no typed rules; the config says so at itsQUERY_OPTIONS_TEST_GLOBScomment, with its own positive control), so a comment block added to one.mjscannot move the verdict on any untouched file.Control-character self-scan on both touched files, outside
check:nul-bytes:grep -naPover the C0 set plus DEL, zero hits.🤖 Generated with Claude Code
https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug
Generated by Claude Code