Skip to content

Commit 4daa69d

Browse files
baozhoutaoclaude
andauthored
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

File tree

scripts/check-skills-token-ratchet.mjs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,24 @@ export const CEILINGS = new Map([
371371
// entry's landed count in the same PR, so the split banks its own shrink
372372
// instead of leaving 21,630 tokens of headroom behind for the next author.
373373
// Lowering is always legitimate (see the shrink-only section in the header).
374-
['skills/objectstack-ui/SKILL.md', 3815],
374+
// 3815 -> 3856: the one-app-per-package cap, stated in App Navigation. The
375+
// entry page taught an author how to declare an App but never that a
376+
// `type: 'app'` package ships exactly ONE, so an agent authored a second App
377+
// object and met the cap at `defineStack` load time instead of at authoring
378+
// time — the published teaching is the only place that fact can reach them.
379+
// No existing text is made redundant by it, so there was nothing in this
380+
// file to delete to fund it. Maintainer ruling 2026-09-13, authorizing this
381+
// raise, verbatim and untranslated: 「17310 允许增加」.
382+
// Arithmetic, in bytes because that is what this convention divides. The
383+
// addition is +234 bytes (15260 -> 15494, i.e. 3815 -> 3874, +59 tokens),
384+
// which is the number the pre-merge CI run reported. `origin/main` then
385+
// deleted the `page` row from the same file's view-type table (-70 bytes),
386+
// so the LANDED file is 15424 bytes and the ceiling moves by 41, not 59.
387+
// Pinned AT the measurement taken on the merged head — zero headroom, so
388+
// the next token added here is paid for by deleting one from this file.
389+
// Independently reproducible without running this script:
390+
// ceil($(wc -c < skills/objectstack-ui/SKILL.md) / 4) == ceil(15424 / 4)
391+
['skills/objectstack-ui/SKILL.md', 3856],
375392
['skills/objectstack-upgrade/SKILL.md', 8333], // -2 (was 8335)
376393

377394
// ── the #12392 extension: the rest of the AUTHORED bundle ────────────────

skills/objectstack-ui/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ An **App** groups objects, dashboards, reports, and custom pages into a
159159
structured navigation tree. Build with `App.create({...})` from
160160
`@objectstack/spec/ui` and register under `defineStack({ apps: [...] })`.
161161

162+
**Exactly one App per `type: 'app'` package**`defineStack` refuses a
163+
second at load, per ADR-0019 (app-as-consumer-unit) D3. More audiences are
164+
`requiredPermissions`-gated groups **inside** that one app, never a second app.
165+
162166
### Navigation Item Types
163167

164168
| Type | Properties | Purpose |

0 commit comments

Comments
 (0)