Skip to content

docs(skills): read the published positions in the PermissionProvider example, and let the compiler hold that fence - #8502

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8393-auth-guide-positions
Sep 8, 2026
Merged

docs(skills): read the published positions in the PermissionProvider example, and let the compiler hold that fence#8502
os-zhuang merged 1 commit into
mainfrom
claude/issue-8393-auth-guide-positions

Conversation

@hotlong

@hotlong hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #8393

The PermissionProvider wiring example in skills/objectui/guides/auth-permissions.md passed userRoles={user?.roles || ['viewer']}. AuthUser has had no roles member since objectui#5424 (framework ADR-0090 D3 renamed it to positions with no deprecation window), so that read resolves through the type's index signature as unknown and a reader copying the line into a typed app gets a TS2740.

Two changes. The second is why the first cannot come back.

  1. The read becomes user?.positions — the published spelling. No compatibility alias, no shadow of the retired member; packages/auth/src/types.ts forbids both.
  2. The fence now carries the check:skill-examples opt-in marker (the HTML-comment marker whose spelling lives in MARKER in scripts/check-skill-examples.mjs), so the gate compiles this fence against the BUILT dist/*.d.ts on every run.

Measurements

The card's probe, re-run on this branch

Assumption re-measured, not inherited. Package closure built first — turbo run build over the gate's own --build-filter set, 29 tasks, exit 0 — then two lines compiled against the freshly built packages/auth/dist/index.d.ts, one of them a control:

const userRoles: string[] = user?.roles || ['viewer'];          // line 4
const userPositions: string[] = user?.positions || ['viewer'];  // line 5 — the control

Exactly one diagnostic, on the guide's line, with the control line clean:

__probe_roles.ts(4,7): error TS2740: Type '{}' is missing the following properties from type 'string[]': length, pop, push, concat, and 29 more.

The instrument was lit and positions is the working spelling. (The card measured "and 28 more" on its own probe; the count moves with the TypeScript lib version and is not load-bearing.)

Why this fence, and what marking cost

The card left the marking decision open. node scripts/check-skill-examples.mjs --measure judged the fence unmarked and named exactly what stood between it and green — two TS2304 lines, both about names the fence used without introducing:

[semantic]  skills/objectui/guides/auth-permissions.md:184:20  TS2304: Cannot find name 'useAuth'.
[semantic]  skills/objectui/guides/auth-permissions.md:192:8   TS2304: Cannot find name 'AppContent'.

Three added lines close both and the gate goes green, so the fence is marked: the marker line, import { useAuth } from '@object-ui/auth';, and declare function AppContent(): null; as a one-line stand-in for the reader's own app tree. No example prose was widened to make the gate happy.

Marking then surfaced one more copy-then-compile failure on the same fence, which only the compiler could have found:

[semantic]  skills/objectui/guides/auth-permissions.md:193:7  TS2322: Type 'AuthUser | null' is not assignable to type '{ [key: string]: unknown; id: string; } | undefined'.

useAuth() hands back AuthUser | null; PermissionProviderProps.user (packages/permissions/src/PermissionProvider.tsx:29) is optional but not nullable. Repaired in place as user={user ?? undefined} — same defect class as the card (an example that fails on copy), mechanical, one modified line, zero net lines, inside this claim's declared file surface, and inside the same gate family. It is not a lenient consumer-side alias: nothing is renamed or coerced, and no second dialect is created.

The mark is real — ablation, both legs proven on disk

One-shot proof, run from the committed tree, restore leg pointed at HEAD, trap armed with absolute paths:

leg on-disk proof gate exit verdict line
mutation — retired read put back anchored counts user?.positions 1 to 0, user?.roles 0 to 1; blob e8b4c065 differs from HEAD blob c38c16c7 1 Semantic phase: 14 of 14 ts fence(s) judged, 1 failed. with [semantic] skills/objectui/guides/auth-permissions.md:192:7 TS2740: ...
restore blob back to c38c16c7 = the HEAD blob, git diff HEAD empty 0 Semantic phase: 14 of 14 ts fence(s) judged, 0 failed. / Every marked skill example holds up against the built types.

So the repository's own gate now produces, unaided, the diagnostic the card had to build a standalone probe for. Marked ts population 13 to 14 (floor 13 — growth is the direction the floor is shaped to allow).

The runtime half — measured in-repo, and what that reading is not

The card attributed this half to objectui#5424 rather than measuring it, and asked whoever picked it up not to inherit it. There is an in-repo reading: packages/auth/src/__tests__/workspaceAdminPositions-5389.test.tsx:77 holds the verbatim user object returned by GET /api/v1/auth/get-session on a real single-tenant server (published @objectstack/cli / @objectstack/plugin-auth 17.1.0), and lines 188 to 195 pin it:

expect(Object.prototype.hasOwnProperty.call(V17_PERMISSION_SET_ADMIN, 'roles')).toBe(false);
expect((V17_PERMISSION_SET_ADMIN as { positions?: string[] }).positions).toEqual(['user', 'platform_admin']);

That suite runs green on this branch (193 tests across the four files run). ⚠️ Read it for what it is: a captured payload held by a pin, not a live server booted for this PR. It is stronger than the card's attribution and weaker than a fresh live capture. Consequence for a reader who silences the type error instead of changing the spelling: the retired key is absent on the wire, so user?.roles || ['viewer'] yields ['viewer'] for every signed-in user unconditionally — a permission example that grants the narrowest role to everyone while looking wired.

Line budget

Both readings taken at HEAD after the final commit, 2f2dbe4.

reading before (67299bf) after delta
skills/objectui/guides/auth-permissions.md 362 365 +3
whole published package (16 markdown files under skills/) 4584 4587 +3

The before figure for the package was measured on a git archive of the base commit, not derived by subtraction; it agrees with the 4584 recorded after objectui PR #8427. Budget for this card was +4 on each; the net cost is the marker plus the two lines the marker needs, and the two content edits are line-neutral.

Gates

Run locally on this branch, exit code captured before any pipe, verdict lines quoted from each gate's own output.

command exit verdict line
pnpm check:skill-examples 0 Every marked skill example holds up against the built types.
node scripts/check-skill-examples.mjs --self-test 0 ✓ check-skill-examples self-test: 59 cases pass
pnpm check:skill-eval-tokens 0 Every must_contain token is taught by its own skill bundle.
pnpm check:skills-paths 0 ✅ check-skills-paths: OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined).
pnpm check:control-bytes 0 ✅ check-control-bytes: OK (scanned 6719 tracked text file(s); skipped 85 binary).
pnpm exec vitest run over the three skills-gate suites plus workspaceAdminPositions-5389.test.tsx 0 Test Files 4 passed (4) / Tests 193 passed (193)
pnpm type-check:scripts 0 clean tsc -p tsconfig.scripts.json
pnpm lint:root 0 ✖ 32 problems (0 errors, 32 warnings) — all 32 pre-existing, none in this diff
node scripts/check-changeset-presence.mjs 0 ✅ No source or published contract of a released package changed in this range, so no changeset is owed.
node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/auth-permissions.md 3 ⛔ GOVERNED — 1 of 1 path(s) are on a governed surface: skills/** x1

Changeset: none. The gate above is the authority in this repository and it says none is owed — one markdown file under skills/, no published source and no manifest contract moved.

pnpm lint:root is a narrowed run and the narrowing is measured, not assumed: eslint's own config restricts every block to files: ['**/*.{ts,tsx}'] with no markdown processor configured, this diff contains zero .ts/.tsx files, and no type-aware rule is wired that could let a markdown edit move any TypeScript file's verdict. The repo-wide farm belongs to CI.

Local trial merge against origin/main at 67299bf: git merge-tree --write-tree exits 0 and writes tree f6d05dee — no conflicts.

维护者速读(草稿)

席位起草,维护者定稿。

改了什么 — 权限指南里那段 PermissionProvider 接线示例,原来读的是 user.roles。这个成员在 objectui#5424 已经退役(框架 ADR-0090 D3 把它改名 positions,没有过渡期)。示例改成读 positions;同时给这段示例挂上编译门禁的标记,让机器以后替我们盯着它。挂标记顺带暴露了同一段示例里第二处照抄即报错的地方(把可能为 null 的 user 传给一个不接受 null 的属性),一并就地修好。三行净增:一行标记、一行示例本来就漏掉的 import、一行占位组件声明。

为什么改 — 指南是 AI 写 ObjectUI 代码时读的第一份材料。这一行不是「写得不够好」,是照抄进 TypeScript 工程里直接编译不过;真要绕过编译错误硬跑,后果更糟:退役的键在会话接口上根本不存在,于是每个登录用户都被判成最低权限的 viewer,而界面看上去是接好的。它能活到今天,正因为这段示例没被任何门禁编译过 —— 所以这次不只改字,还把它纳入门禁。

风险与代价(含回滚) — 风险很低:改的是一份发布给使用者的 markdown 指南,不是运行时代码,没有任何已发布包的源码或发布契约移动(changeset 门禁判定为不欠)。唯一的持续代价是这段示例从此被编译门禁盯着,以后改它要保持能编译 —— 这正是我们要的。回滚就是 revert 这一个提交,没有数据迁移、没有版本联动。已用消融实验证明门禁真的会因为这一行变红(把退役写法放回去,门禁 exit 1 并点名该行),不是挂了个不干活的标记。

席位意见 — (留空,待维护者/评审席位填写)

你要做的 — 这是受管面(发布给使用者的 skills/**),按规程停在 draft 等你合并;你的那次合并动作本身就是审核记录。请看两点:① positions 是不是你要的最终写法(而不是任何形式的兼容别名);② 给这段示例挂编译门禁的标记是否合意 —— 代价是三行,收益是同类错误以后由机器拦住。


Generated by Claude Code

…r example, and let the compiler hold that fence

The `PermissionProvider` wiring example in the auth-permissions guide passed
`userRoles={user?.roles || ['viewer']}`. `AuthUser` has had no `roles` member
since objectui#5424 (framework ADR-0090 D3 renamed it to `positions` with no
deprecation window), so that read resolves through the type's
`[key: string]: unknown` index signature and a reader copying the line into a
typed app gets TS2740 on it.

Two changes, and the second is why the first cannot come back:

- the read becomes `user?.positions`, the published spelling;
- the fence carries the `check:skill-examples` opt-in marker, so the gate now
  compiles it against the BUILT `dist/*.d.ts` on every run.

Marking cost three added lines — the marker, a `useAuth` import the fence used
without importing, and a one-line stub for the `AppContent` placeholder — and
surfaced one more copy-then-compile failure on the same fence: `user={user}`
handed `AuthUser | null` to a prop typed `{ id: string; ... } | undefined`
(TS2322 at the fence's `user=` line). Fixed in place as `user={user ?? undefined}`,
same defect class, no net line cost.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY

hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿 · skills 席,2026-09-08T03:24Z)

改了什么 — objectui 权限指南里 PermissionProvider 接线示例的一行:原来读的是已退役的 user.roles,改读已发布的 user.positions;同一段示例挂上 check:skill-examples 的编译标记,从此每次 CI 都对着构建产物编译它。挂标记连带修了同一段里第二处照抄即报错的地方(useAuth() 返回的 user 可能为 null,而 PermissionProvideruser 属性不收 null,改成 user ?? undefined)。净增 3 行:标记、示例本来就漏掉的 useAuth import、一行 AppContent 占位声明;整包 4584 → 4587 行。

为什么改 — 这段示例是 AI 写 ObjectUI 权限接线时抄的第一份材料。roles 在 ADR-0090 D3 已经无过渡期改名为 positions,照抄这一行进 TypeScript 工程直接 TS2740 编译不过;硬绕过编译错误跑起来更糟:会话接口上根本没有 roles 键,于是每个登录用户都被判成最低权限的 viewer,而界面看上去是接好的。它活到今天只因为没有任何门禁编译过它 —— 所以这次不只改字,还把它纳入门禁。

风险与代价(含回滚) — 只动一份发布给使用者的 markdown 指南,不碰运行时代码;changeset 门禁判定不欠。席位复核:diff 与认领申报的文件面一致(1 个文件);两处改动都是向已发布契约收窄,没有任何兼容别名;消融已证门禁会因退役写法变红(exit 1 点名该行,恢复后 exit 0)。一个代价值得你看一眼:declare function AppContent(): null; 这一行是为让示例能独立编译而加的占位,读者要换成自己的组件树 —— 注释已写明,但它是整包第一处这种写法。CI:27 项绿,1 项(Inert vi.mock Specifier Check,非必需)被 runner 取消、无输出,与本 diff 无关。回滚 = revert 一个提交,无数据迁移、无版本联动。

席位意见 — ACCEPT。两处收窄都对准已发布契约,门禁标记让同类回归以后由机器拦住,+3 行买到的是一段真正能编译的示例。顺带记一笔不在本卡的观察:PermissionProviderProps.user 不收 null 而 useAuth() 返回 null,每个接线点都得写 ?? undefined,属 @object-ui/permissions 的 API 手感问题,不在三类立卡门槛内,留在 PR 的验收备注。

你要做的 — 受管面(skills/**),PR 停在 draft,席位不翻 ready、不入队、不合并;已向 os-zhuang 请审,并把 PR assign 给了 hotlong(作者身份即席位账号,请审会 422)。合并与否请回一个字:是/否


Generated by Claude Code

@hotlong
hotlong requested a review from os-zhuang September 8, 2026 03:23
@os-zhuang
os-zhuang marked this pull request as ready for review September 8, 2026 04:19
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit d65b2ba Sep 8, 2026
30 of 31 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8393-auth-guide-positions branch September 8, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] The auth-permissions guide's PermissionProvider example still reads the retired user.roles — TS2740 against the published AuthUser

3 participants