Commit 4daa69d
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)
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.
> ⛔ **GOVERNED SURFACE — do not merge, queue, arm auto-merge, or
approve.** `skills/**` is on the `GOVERNED_SURFACES` register. Landing
this is a `GOVERNED_APPROVERS` act by hand. It is deliberately kept out
of #17309's diff: a mixed diff is governed whole, judged on the file
list and not on proportion.
## Authorization for the ratchet raise
`check-skills-token-ratchet` is 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:
> 「17310 允许增加」
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 `3874` in the earlier revision was measured on `dc077e17`, before
`origin/main` was merged in. It is **stale**, and copying it forward
would have granted 18 tokens of headroom this file has not earned.
`origin/main` deleted the `page` row from this same file's view-type
table while this PR sat open:
```
-| `page` | Mounts a published Page (`pageName`); no rows of its own |
```
So the arithmetic, re-derived on the merged head `4f22acc38d`, in bytes
because bytes are what this gate's `ceil(utf8 bytes / 4)` convention
divides:
```
merge base b90aff8 skills/objectstack-ui/SKILL.md 15260 bytes -> 3815 tokens ceiling 3815 headroom 0
this PR's addition +234 bytes -> 3874 tokens (+59) <- the pre-merge CI number
origin/main's `page`-row deletion -70 bytes
LANDED, merged head 15424 bytes -> 3856 tokens (+41)
```
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:
```
$ wc -c < skills/objectstack-ui/SKILL.md
15424
$ node -e "console.log(Math.ceil(15424/4))"
3856
```
Exactly one ceiling entry moves. No other row in `CEILINGS` is 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.create` 3 次 —— 零是读数,不是空语料。②
是因为你已经就这 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/main` at `b90aff81f2`, over the whole published bundle.
Every zero carries a same-corpus positive control from the same pass.
```
grep -ric over skills/ (BEFORE)
'at most one app' 0
'exactly one app' 0
'only one app' 0
'one app per package' 0
'single app' 0
'app-as-consumer-unit' 0
positive control defineStack 54
positive control App.create 3
positive control ADR-0019 3 <- and all 3 mean the APPROVALS record:
objectstack-automation/SKILL.md x2,
objectstack-platform/SKILL.md x1
negative control 'zzz-nonsense-zzz' 0
```
So the rule is absent in every spelling from a bundle that names
`defineStack` 54 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-changeset` label**, already applied.
Measured rather than assumed: no package manifest's `files[]` contains
`skills`, `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.yml` spells 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:
```
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 44 commands
44 run: 43 exit 0 on the first pass; 1 exit 3 (PREREQUISITE NOT MET, not a finding)
`pnpm --filter @objectstack/lint run check:doc-formula-expressions` exit 3 -> built
`@objectstack/formula` + `@objectstack/lint` as its own fix line prescribes -> re-run exit 0
=> 44 of 44 exit 0. Exit codes captured with redirection BEFORE any pipe.
```
The gate this PR is about, and its self-test, on the merged head:
```
$ node scripts/check-skills-token-ratchet.mjs ; echo EXIT=$?
✓ check-skills-token-ratchet: skills/objectstack-ui/SKILL.md is 3856 tokens (ceiling 3856; headroom 0).
skills/objectstack-ui/SKILL.md 3856 / 3856 (+0)
EXIT=0
$ node scripts/check-skills-token-ratchet.mjs --self-test ; echo EXIT=$?
✓ check-skills-token-ratchet self-test: 65 cases pass.
EXIT=0
```
`check-ratchet-remedy-authority` was run deliberately, because this
gate's own source carries a placement warning about prose near
`CEILINGS` flipping its classification from `marked` to `excluded`. The
new comment sits inside the map, far from the declaration, and the
classification holds:
```
$ pnpm check:ratchet-remedy-authority ; echo EXIT=$?
OK check-ratchet-remedy-authority: 245 scripts swept; 15 mark the expanding remedy ⛔ MAINTAINER-ONLY,
6 turn it down outright, 224 hand out no ratchet-expanding remedy.
EXIT=0
```
`pnpm lint` is 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 from
`eslint.config.mjs` itself — `files:
['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']` — which does not include
`.md`, and eslint confirms it in its own words for `SKILL.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 one `eslint.config.mjs` which never enables type-aware linting for
any file (no `parserOptions.project`, no typed rules; the config says so
at its `QUERY_OPTIONS_TEST_GLOBS` comment, with its own positive
control), so a comment block added to one `.mjs` cannot move the verdict
on any untouched file.
Control-character self-scan on both touched files, outside
`check:nul-bytes`: `grep -naP` over the C0 set plus DEL, zero hits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a0dd872 commit 4daa69d
2 files changed
Lines changed: 22 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
371 | 371 | | |
372 | 372 | | |
373 | 373 | | |
374 | | - | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
375 | 392 | | |
376 | 393 | | |
377 | 394 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
162 | 166 | | |
163 | 167 | | |
164 | 168 | | |
| |||
0 commit comments