Skip to content

docs(skills): guard both useAuth members in the auth-permissions example - #9374

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-9350-useauth-guard-both
Sep 13, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-9350-useauth-guard-both

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #9350

What changed

skills/objectui/guides/auth-permissions.md, the fence under "useAuth hook", read user.name behind an isAuthenticated-only early return. AuthProvider in @object-ui/auth computes isAuthenticated as user !== null && session !== null only when auth is enabled and not in preview mode; in guest mode (enabled: false) and in preview mode it hardcodes true while user stays null, so the example threw a TypeError in exactly the two modes a reader without an auth backend is in. The shipped UserMenu component already guards both members.

  • The early return now reads if (!isAuthenticated || !user) return LOGINBUTTON; where LOGINBUTTON stands for the login-button element the fence returns (JSX tag shapes are spelled in words in this body because the platform strips tag-shaped fragments). The returned element is unchanged.
  • One sentence above the fence says why a signed-in-looking context can still carry no user and points at UserMenu as the shipped shape. The prose moved with the example, as AGENTS.md requires.
  • An empty-frontmatter changeset declares that nothing published by a package moves.

Not done, by the grading (comment 5651956190): no os:check marker on this fence. It is a fragment by construction (three undeclared placeholders: the spinner, the login button and the button), and marking it would red check:skill-examples for reasons that are not this defect. Whether to make it self-contained is a separate question, noted below and not filed.

Acceptance, both directions

Measured on this branch at 67cccb0 with the gate's own scoped build in place: pnpm exec turbo run build $(node scripts/check-skill-examples.mjs --build-filter) --concurrency=2, 29 of 29 tasks successful, run under the shared verify lock (VERDICT command-exit 0, held 141s, waited 0s).

Before = the base (69aa9c0) content of the guide on that same built tree; After = 67cccb0.

  • node scripts/check-skill-examples.mjs --measure, TS18047: 'user' is possibly 'null' rows for this file: 1 (auth-permissions.md:49:20) before, 0 after.
  • TS18047 rows across the whole corpus: 1 before, 0 after.
  • [semantic] diagnostic rows across the whole corpus (positive control, not a silent zero): 270 before, 269 after.
  • Semantic phase summary line, both sides: 97 of 121 ts fence(s) judged, 79 failed (the fence still fails on its three undeclared placeholders before and after, so the failed-fence count does not move while the diagnostic-row count drops by exactly one).
  • Count of isAuthenticated || !user in this file: 0 before, 1 after.
  • Count of useAuth in this file (lit control): 5 before, 5 after.
  • Prose sentence present (signed-in-looking context): 0 before, 1 after.

The before column is an ablation leg run after the commit: git checkout 69aa9c0 -- skills/objectui/guides/auth-permissions.md on the built tree (on-disk proof: 365 lines, old guard 1, new guard 0, prose 0), then --measure, then git checkout HEAD -- FILE. Restoration is proven by git hash-object equal to the HEAD blob (dd4e4633…), git diff HEAD --stat empty and git status --porcelain empty. The very first --measure attempt, before the build, exited 2 (PRECONDITION NOT MET) and is not a reading.

Line budget (published skills/**)

  • Whole file skills/objectui/guides/auth-permissions.md: 365 → 367 lines (net +2, ceiling +2; 3 insertions, 1 deletion; no re-wrap).
  • Whole catalog, sum of skills/**/SKILL.md in objectui: 137 → 137 (one file, untouched).
  • Token gate pnpm check:skill-eval-tokens, before and after identical: exit 0; Scanned 1 skill bundle(s) under skills: 11 eval file(s), 33 eval(s), 125 must_contain token(s), scored against 16 guide file(s).; Red under the chosen oracle: 0 (0 beyond the baseline).

Gates run locally (exit captured before any pipe; verdict lines quoted)

Derived by hand for objectui (there is no dispatch-gates.mjs in this tree): root package.json check:* scripts whose source reads skills/, plus the .md-scanning and .changeset-scanning workflow steps.

  • pnpm check:skill-examples — exit 0 — Semantic phase: 14 of 14 ts fence(s) judged, 0 failed. / Every marked skill example holds up against the built types.
  • pnpm check:skills-paths — exit 0 — OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined).
  • pnpm check:skill-eval-tokens — exit 0 — Every must_contain token is taught by its own skill bundle.
  • pnpm check:doc-fences — exit 0 — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript (rest of the line elided).
  • pnpm check:control-bytes — exit 0 — OK (scanned 7540 tracked text file(s); skipped 85 binary).
  • pnpm check:shell-escape-residue — exit 0 — OK (5/5 root(s) resolved … skills: 16 file(s), 210 fence(s) …).
  • pnpm check:doc-types — exit 0 — Every documented component type is registered.
  • pnpm check:upstream-port-parity — exit 0 — 11 ported file(s) match objectstack-ai/objectstack modulo their declared divergences.
  • node scripts/check-changeset-presence.mjs — exit 0 — No source or published contract of a released package changed in this range, so no changeset is owed. (the empty-frontmatter changeset is declared anyway, per the docs-only convention).
  • check-changeset-claims, check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite — exit 0 each.
  • node scripts/check-new-cross-file-line-citations.mjs — exit 0 — 0 new citation(s), enforcement report-only.
  • node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/auth-permissions.md — exit 3 — GOVERNED — 1 of 1 path(s) are on a governed surface: skills/** x1 (the path face, not a failure).
  • NOT MEASURED: pnpm check:doc-snippets exited 2 (PRECONDITION NOT MET: a different 34-package closure is unbuilt), and its own header states that skills/objectui/** is not claimed by that gate, so it is not a derived gate for this diff.
  • Not owed: no package source changed, so no package test or typecheck closure; pnpm lint is the repo-wide run CI owns and this diff has no lintable source.

Serial check

No open objectui PR touches skills/**: re-read at 07:58Z today, the 8 PRs updated after the PM's 06:58Z reading (#9367, #9371, #9369, #9349, #9364, #9368, #9360, #9366) list no skills/ file.

Acceptance notes (noted, not filed)

  • Whether to make the useAuth fence self-contained (declare the spinner, login-button and button placeholders) and then mark it os:check is a separate question by the grading; today it is a fragment by construction, like the other unmarked fences in this file that --measure lists. 承接者:无 — no queued PR or person is on this file.
  • The second useAuth use in this file (the marked PermissionProvider setup fence) reads user?.positions and user ?? undefined; it does not have this defect and was not touched.
  • Same class as objectui#9311 (the dataSource member in data-integration.md; its PR fix(skills): guard the DataSource read in the marked data-integration example #9352 landed 2026-09-13T06:29Z).

维护者速读(草稿)

改了什么:发布给使用者的 skills 指南 skills/objectui/guides/auth-permissions.md 里,useAuth 示例的早退守卫从只看 isAuthenticated 改为同时看 isAuthenticated 和 user(与仓内已发布的 UserMenu 组件一致),并在示例上方加一句说明为什么看起来已登录的上下文仍可能没有 user。净增 2 行;另加一个空 frontmatter 的 changeset 声明不发版。

为什么改:AuthProvider 在 guest 模式与 preview 模式下把 isAuthenticated 硬编码为 true,而 user 仍是 null。照抄原示例的读者(尤其是没接后端、正在这两种模式下试用的人,以及按指南写代码的 AI)会在运行时撞 TypeError。这是发布示例与已发布组件对同一契约的读法不一致,已发布组件是对的。

风险与代价(含回滚):只改文档示例与一句散文,不动任何包源码,不发版;check:skill-examples 等门禁本地全绿。回滚就是 revert 这一个 commit。

席位意见:

你要做的:受管面(skills/**),PR 停在 draft;你确认后合并即可,不需要额外的批准点击。

Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8


Generated by Claude Code

The `useAuth` fence under "useAuth hook" read `user.name` behind an
`isAuthenticated`-only guard. `AuthProvider` hardcodes `isAuthenticated`
to `true` in guest mode and in preview mode while `user` stays `null`,
so the example threw a TypeError in exactly the two modes a reader
without an auth backend is in. Guard both members the way the shipped
`UserMenu` does, and add one sentence beside the fence saying why a
signed-in-looking context can still carry no user.

The fence stays unmarked: it is a fragment by construction (three
undeclared placeholders), and marking it would red the gate for reasons
that are not this defect.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Contract review

Head: 67cccb09c (objectui PR #9374, card objectui#9350) — read at 2026-09-13T08:01Z by the skills seat at the contract-review tier (served model claude-fable-5-1, get_session external_metadata.last_served_model at 2026-09-13T07:35Z).

① derived judgments: the diff makes ONE falsifiable claim on a published skill — in guest mode and preview mode isAuthenticated is true while user is null, so the example must guard both — checked on objectui origin/main 69aa9c0: packages/auth/src/AuthProvider.tsx :197–:199 const isAuthenticated = (enabled && !isPreviewMode) ? user !== null && session !== null : true and packages/auth/src/UserMenu.tsx :43 if (!isAuthenticated || !user): correct. The probe direction was proven both ways by the dev on one built tree (check-skill-examples.mjs --measure: the TS18047 row at :49:20 present with the base content, absent with the head content, restore proven by blob hash). No accept-set or public-surface change; no clause-② carrier script exists in objectui, so the pair gate is not applicable — declared Clause-②: no, no widening in the diff.
② semver: docs-only; objectui's convention is the empty-frontmatter changeset, present (.changeset/9350-auth-permissions-useauth-guard-both.md, ---\n---), and check-changeset-presence.mjs reads no changeset owed.
③ boundary flags: none raised (open_questions: []); the fence stays an unmarked fragment by the grading's instruction — three noted, not filed items carry no bearer and are accepted as noted.

Implemented-by: claude/issue-9350-useauth-guard-both (mode:subagent)
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS. Governed surface (published skills/**): no gate carrier was hung (the content limb is reviewed here); the PR stays a draft for the maintainer's hand merge.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么 —— 发布技能 skills/objectui/guides/auth-permissions.md 里 useAuth 示例的一行守卫:从只看 isAuthenticated 改成 !isAuthenticated || !user,并在示例上方加一句解释——guest 模式和 preview 模式下 AuthProvider 把 isAuthenticated 写死为 true 而 user 仍为 null,所以读 user 要单独守。365 → 367 行(预算 +2 内);附一个空 frontmatter changeset(objectui 文档改动的正确形态)。

为什么改 —— 照抄这个示例的读者在没有后端的 guest / preview 模式下会直接 TypeError——而那正是原型阶段最常见的两种模式;仓库自己发运的 UserMenu 早就两个都守。修法与它对齐。

风险与代价(含回滚) —— 纯文档面,不动任何包;回滚 = revert 单文件 3 行 + 删 changeset。

席位意见 —— 收。本席在 head 67cccb0 上亲核:守卫替换 1/0、解释句在、AuthProvider :197–:199 与 UserMenu :43 一致;dev 在同一棵已构建树上用 check-skill-examples --measure 证明了红→绿(旧内容 1 行 TS18047、新内容 0,按 blob 哈希还原);真门禁 check:skill-examples exit 0;契约复核 PASS 记录 5652098578。CI 在读时 24 绿 7 在跑,Lint / Type Check / 队列守卫已绿。

你要做的(一个动作) —— 受管面(发布 skills/**):CI 转绿后 approve 并合并。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 13, 2026 08:02
@os-zhuang
os-zhuang enabled auto-merge September 13, 2026 08:02
@os-zhuang
os-zhuang disabled auto-merge September 13, 2026 08:03
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读(补记:检查全部跑完)

读数 —— head 67cccb09,2026-09-13T08:18Z:35 个 check 全部完成 —— 31 绿、3 跳过、1 红:Doc Snippet Type Check。

那一红不是本 PR 的 —— 同名检查在 main 上自 edea22a5(PR #9310,2026-09-13T04:59Z 起)就红,之后每次 push 都红;本 PR 与 main 最新一跑(69aa9c01)的失败签名逐字相同:同一步 node scripts/check-doc-snippet-types.mjs,同样三处 TS2740(content/docs/guide/schema-rendering.md:99 / :434、packages/react/README.md:73),首行错误一致。它不在 main 的必过集里(必过集为 Lint / Type Check / Build & E2E / Test 四分片 / Build Docs / Changeset Declaration,已现读 rulesets)。承接卡是 objectui#9346(domain:ui,pm:blocked 等 objectui#9308;修它的 PR 是 #9369,ui 席在飞)。按你今早在 #17934 上接受的形状(同名且同签名的 base 继承红不计入「全绿」),本 PR 自己的面是干净的。

你要做的(一个动作) —— 你已 approve、翻 ready、开过又关了 auto-merge;若关它是因为那一红,现在可以重新挂 auto-merge(队列只看必过集)或直接合;本席不代你挂。


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Sep 13, 2026
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit ffc4c44 Sep 13, 2026
35 of 36 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-9350-useauth-guard-both branch September 13, 2026 10:12
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…urce` expression root (objectstack-ai#9378)

Fixes objectstack-ai#9370

⛔ **GOVERNED SURFACE** (`skills/**`, `GOVERNED_SURFACES`
`skills-catalog`). This PR parks as a **draft by design** and needs an
APPROVED review from an authorized approver (`os-zhuang`, `hotlong`). ⛔
Not flipped ready, not enqueued, no auto-merge. The precedent on this
exact surface is objectstack-ai#9352 / card objectstack-ai#9311, merged as `28be0786d`.

⚠️ **Merge after objectstack-ai#9369, and this PR states which world it measured in.**
Re-derived from my own tree at base `69aa9c017`, not assumed:
`SchemaRenderer.tsx` still carries the `data: dataSource` binding, and
`SchemaRendererContext.tsx` still reads `const dataSource =
context?.dataSource` inside `useDataScope`. **Both halves are still LIVE
on this base** — objectstack-ai#9369 is open, not merged. So these pages become true
the moment objectstack-ai#9369 lands and are ahead of the code until then. Every
evaluator measurement below is from that same pre-objectstack-ai#9369 tree, which is
the right place to take it: the evaluator itself is what objectstack-ai#9369 stops
feeding, and the evaluator's own behaviour does not move. Sequencing is
a maintainer decision, not something this PR can enforce.

## What moved

The maintainer ruled objectui#9308 option B on 2026-09-13:
`SchemaRenderer` stops publishing the injected `DataSource` adapter as
the expression root `data` (b1), and `useDataScope` — what a node's
`bind` resolves through — reads the ambient scope a host publishes via
`PredicateScopeProvider` instead of walking that adapter (b2). The
published skill package still taught both halves.

Three files, matching the shape objectstack-ai#9369 used for
`content/docs/guide/schema-rendering.md` and `packages/react/README.md`:
publish the host values under real names through
`PredicateScopeProvider`, read them by those names, and state that
`dataSource` is the **adapter** and not a root.

| file | what changed |
|---|---|
| `guides/schema-expressions.md` | the scope table, a new COMPILED
wiring example, the migration callout, the `bind` resolution prose, the
bound-list comment, debugging-checklist item 3 |
| `guides/data-integration.md` | the architecture diagram's channel
split, "Static data" rewritten onto the scope channel as a COMPILED
example, the `useDataScope` resolution sentence, the `data` root claim |
| `rules/protocol.md` | both measured tables' WIRING restated, the
`bind` comment and nested-path sentence, plus a callout carrying the
retirement and pointing at the verdict flip |

⛔ Untouched, as the card requires: the `object-*` reader-list paragraph
and the `data-table` `bind` pothole (objectui#6575 still rules
`data-table` does not read `bind`).

## ⭐ The part that is not a renaming

A migration note that only swaps provider names is wrong here, because
the **verdict moved**. Measured by me on the built evaluator
(`packages/core/dist`), with firing controls — ⛔ and the `true` on a
missing root belongs to the **predicate layer**, not to the evaluator:

| scope, expression | `evaluateExpression` | `evaluateCondition` |
|---|---|---|
| CONTROL `{ data: { status: 'draft' } }`, `${data.status}` | `"draft"`
| `true` |
| CONTROL same scope, `Status: ${data.status}` | `"Status: draft"` |
`true` |
| CONTROL same scope, `${data.nope}` — present root, absent member |
`undefined` | `false` |
| `{ data: {} }` — adapter-shaped, `${data.status == 'draft'}` | `false`
| `false` |
| `{ data: undefined }`, same predicate | `false` | `false` |
| `{}` — no `data` root, `${data.status}` | `"${data.status}"` | `true`
|
| `{}` — no `data` root, `Status: ${data.status}` | `"Status:
${data.status}"` | `true` |
| `{}` — no `data` root, `${data.status == 'draft'}` | `"${data.status
== 'draft'}"` | `true` |

The third control is the discriminating one: a root that is PRESENT with
an absent member yields `undefined`, while a root that is MISSING yields
the template's own source characters. So the two layers answer a missing
root differently, and each has its own reader-visible symptom:

- **predicate layer** — fails soft to `true`. A `"visible":
"${data.status == 'draft'}"` authored from these pages was **hidden on
every row** and is now **shown on every row**; spelled `"hidden"` it
flips the other way.
- **interpolation layer** — does not fail soft at all. A `content` built
from a missing root paints the literal characters `${data.status}` on
screen.

The callout carries one column per layer and tells the reader to read
both. It also states that re-publishing `data` restores the OLD
always-`false` verdict rather than fixing the gate, and points row gates
at `record` — the runtime-layer root under ADR-0089 D3.

## What I decided about the ~two dozen `${data.…}` examples

Measured, not estimated: `schema-expressions.md` carries **21** lines
spelling `${data.` (one of them is the census's own scope-table row).
Repo-wide under `skills/`: 21 here, 14 in `rules/protocol.md`, 5 in
`page-builder.md`, 4 in `testing.md`, 2 each in `data-integration.md`
and `auth-permissions.md`, 1 in `architecture.md`, plus 4 in
`evals/*.json`.

**Decision: keep every one of them verbatim and make them reachable,
rather than rewrite them to bare roots.** The page's new wiring example
publishes a root literally named `data`, and the scope table says in a
row that every `${data.*}` example on the page assumes exactly that.
Three reasons this is the right divergence from objectstack-ai#9369's `content/docs`
shape:

1. **A pin renders those exact spellings.**
`skill-guide-provider-envelope.test.tsx`, as re-derived in objectstack-ai#9369,
publishes `scope = { data: PROVIDER }` and renders `${data.customers}` /
`${data.label}`. Rewriting the guide spellings would move that pin's
subject from inside a second pull request — the "green alone, red
together" shape.
2. **ADR-0089 D3 does not forbid the name.** `CANONICAL_ROOT_BY_LAYER`
puts `data` at the metadata layer. What was retired is the renderer
AUTO-publishing the adapter there, not the name.
3. **Most of those examples are about something else** — which text keys
carry expressions, type preservation, the troubleshooting section. The
root name is scaffolding, and churning 20 lines of scaffolding on a
governed surface buys the reader nothing.

⛔ Nothing was silently rewritten: the only expression text this PR
changes is the two `dataSource = {…}` comments that named the retired
wiring.

## Re-measurement of the card's per-line census

The card measured on the objectui#9308 branch. Re-measured against
`origin/main` `69aa9c017` — **every cited line still lands on the cited
text**:

| cited | on `origin/main` `69aa9c017` | verdict |
|---|---|---|
| 112 | `| Top-level data fields | \`SchemaRendererProvider dataSource\`
| …` | exact |
| 113 | `| \`data\` | Alias for dataSource root | …` | exact |
| 302-303 | `dataSource = { customerNames: … }`,
`useDataScope("customerNames")` | exact |
| 305 | `**Nested paths work:** … resolves
\`dataSource.app.settings.users\`` | exact |
| 393 | `// ✅ Bound data, already node-shaped: dataSource = { rows: … }`
| exact |

`premise_still_valid: true`.

## Enumeration beyond the census (⛔ out of scope for this PR)

`--measure` judges every candidate fence, marked or not, so a page can
be wrong where no gate looks. Reading the prose as well turned up two
more:

- **`guides/auth-permissions.md`** carries the same retired claim in its
own words — a scope table row `| \`data\` | the \`dataSource\` passed to
\`SchemaRendererProvider\` |`, the sentence "Keys of the `dataSource`
object are reachable only under the `data.` root", and "With no host
scope mounted, `data` and `page` are all you get". ⛔ Out of scope here:
**PR objectstack-ai#9374 holds that file**, so an in-place edit would collide. Filed
as objectstack-ai#9379.
- **`guides/testing.md` Pattern 5** asserts `getByText('Secret')` for a
node gated on `${userRole !== "admin"}` — a BARE root that no channel
publishes, before or after the ruling. Copy the example and the
assertion fails. Different defect class from this card, and independent
of the ruling — it is wrong on `main` today. Filed as objectstack-ai#9380.

Noted, not filed: the `${data.*}` examples in `page-builder.md`,
`architecture.md`, `i18n.md`, `project-setup.md` and `evals/*.json` are
in the same reachable-only-if-the-host-publishes class; they are not
falsifiably wrong and the skills lane that takes the auth-permissions
card is their natural carrier.

## Verification

Reproduce-first, as required — the harness is trustworthy before any red
or green from it is read:

```text
$ node scripts/check-skill-examples.mjs --self-test     # BEFORE the build
EXIT=2 — PRECONDITION NOT MET: the self-test's type-check leg needs the workspace built

$ ...os-verify-lock.sh -c 'turbo run build $(--build-filter) --concurrency=2'
VERDICT command-exit 0 · held the lock 44s · waited 0s

$ node scripts/check-skill-examples.mjs --self-test     # AFTER the build
EXIT=0 — ✓ 59 cases pass
```

Gate, verbatim, before and after this diff:

```text
BEFORE (exit 0)
Scanned 20 guide(s) under skills, .claude/skills: 121 ts/tsx/typescript fence(s), 70 json/jsonc fence(s).
Marked: 14 ts fence(s) (floor 13), 70 json fence(s) (floor 70)
Semantic phase: 14 of 14 ts fence(s) judged, 0 failed.
JSON phase:     70 fence(s) parsed, 0 failed.
Every marked skill example holds up against the built types.

AFTER (exit 0)
Scanned 20 guide(s) under skills, .claude/skills: 122 ts/tsx/typescript fence(s), 70 json/jsonc fence(s).
Marked: 16 ts fence(s) (floor 13), 70 json fence(s) (floor 70)
Semantic phase: 16 of 16 ts fence(s) judged, 0 failed.
JSON phase:     70 fence(s) parsed, 0 failed.
Every marked skill example holds up against the built types.
```

⭐ The marked ts population moves **14 to 16** and the json population is
unmoved at 70 — both floors are SHRINK-ONLY and neither is breached. The
two new marked fences are the wiring examples, so the thing this card is
about is now COMPILED against the built `dist/*.d.ts` rather than only
read.

Derived by hand from `package.json` plus `.github/workflows/` (this repo
has no `dispatch-gates.mjs`); every one run on this tree:

| gate | exit |
|---|---|
| `check-skill-examples.mjs --self-test` | 0 |
| `check-skill-examples.mjs` | 0 |
| `check-skills-paths.mjs` | 0 — 88/89 stated paths resolve, 1
pre-existing baselined |
| `check-skill-eval-tokens.mjs --self-test` /
`check-skill-eval-tokens.mjs` | 0 / 0 |
| `check-control-bytes.mjs` | 0 — 7539 tracked text files |
| `check-shell-escape-residue.mjs` | 0 — 16/16 skills documents under a
declared root |
| `check-new-cross-file-line-citations.mjs` | 0 — 0 new citations |
| `check-changeset-presence.mjs` | 0 |
| `check-doc-links.mjs` | 0 |
| `check-governed-queue-guard.mjs --self-test` | 0 — 185 cases |

Package tests that READ these three documents (`markdown-test-inputs.mjs
--changed` names all three as test inputs, so CI runs the full shards):
reported below the fold once the shared verify lock grants a turn.

Line readings for the governed surface, as the contract requires:

| reading | before | after | delta |
|---|---|---|---|
| `guides/schema-expressions.md` | 569 | 635 | +66 |
| `guides/data-integration.md` | 485 | 513 | +28 |
| `rules/protocol.md` | 341 | 354 | +13 |
| whole published package `skills/**` | 5201 | 5308 | +107 (+2.1%) |

The added lines are the correction itself: one migration callout, two
now-compiled wiring examples, and the sentences that replace the retired
claims. No re-wrap was used to buy lines.

## Changeset

**None owed**, by measurement rather than by inheritance:

1. `node scripts/check-changeset-presence.mjs` — "Compared the working
tree with `69aa9c017` (merge-base with origin/main): 3 file(s) changed,
0 of them published source of a package the release covers, 0 of them a
manifest whose published contract moved … No source or published
contract of a released package changed in this range, so no changeset is
owed." (exit 0)
2. Independently: **0 of 43** workspace manifests mention `skills` in
`files` / `exports` / `main` / `module` / `types` / `bin`. Positive
control on the same scan: 38 manifests list `dist` in `files[]`. Nothing
under `skills/` is shipped by any npm package.
3. The merged precedent on this surface, `28be0786d` (objectstack-ai#9352), changed
one `skills/` file and carried no changeset.

⚠️ One inconsistency worth a maintainer's eye rather than my guess: open
PR objectstack-ai#9374, also a single `skills/` guide, DOES carry an empty-frontmatter
changeset declaring "no package is released by this change". Both forms
pass the gate. If the empty-frontmatter declaration is the house style
for this surface, say so and I will add one.

## Governed-queue-guard verdict on this diff (verbatim)

```text
$ node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/schema-expressions.md skills/objectui/guides/data-integration.md skills/objectui/rules/protocol.md
⛔ GOVERNED — 3 of 3 path(s) are on a governed surface:
   skills/** x3 — the published skills catalog
     - skills/objectui/guides/schema-expressions.md
     - skills/objectui/guides/data-integration.md
     - skills/objectui/rules/protocol.md

   One governed path governs the WHOLE pull request — proportion is not a question.
   ⛔ Do not flip it ready, enqueue it, or arm auto-merge. Park it as a DRAFT and leave the merge
      to the maintainer; a human merge IS the review record for a governed surface.
   The merge-queue run of "Governed Surface Queue Guard" refuses this diff unless an APPROVED review by an
   authorized approver (GOVERNED_APPROVERS: os-zhuang, hotlong) is on the pull request — on
   whichever commit it was left (maintainer ruling 2026-09-04).
exit 3
```

## Clause-② — contract review

`needs:contract-review` is hung on this PR in the same stroke as opening
it, mirroring the card. Published `skills/**` making falsifiable
contract-semantics claims about which roots a tier binds. ⛔ The
governed-surface human merge does **not** substitute for it; the two
stack, and neither limb is mine to clear.

## Inherited reds — ⛔ not from this PR

- `Bundle Analysis` is red on `main` (arrived with objectstack-ai#9316; a maintainer
decision).
- `Doc Snippet Type Check` is red on `main` until objectstack-ai#9369 lands. This diff
touches no document that gate scans (`content/docs` + package READMEs +
root `README.md`, explicitly not `skills/**`).

## Acceptance notes

- ⛔ No test was skipped, quarantined or weakened. No assertion was
loosened. The marked-fence floors both hold and the ts population grew.
- The card's "the pins are green while the prose beside them is wrong"
warning was taken literally: the six `packages/components` pins objectstack-ai#9369
re-derived were read for what they assert about these files before a
byte was changed, and every string they pin is intact — `Rule: Keys Live
on the Node`, the `hoists every key onto\s+the node` regex, the absence
of the two retired sentences, the `no md matches "instead of
\`props.\`"` class guard, and the `"bind": "customerNames"` list example
in each of the three guides.
- Noted, not filed: `rules/protocol.md`'s two measured tables still cite
`origin/main` `f1c27f037` as the commit they were measured on. This PR
restates their WIRING, not their outcomes, and says so inline; a
re-measurement on a current commit is a separate piece of work, and the
`skill-guide-provider-envelope` pin is what actually holds those
outcomes today. Carrier: whoever next re-derives that pin.

## 维护者速读(草稿)

### 改了什么
把发布中的 `skills/objectui` 三份指南从「`dataSource` 就是表达式根 `data`、`bind` 走
`dataSource`」改成 ruling 之后的真实通道:宿主用 `PredicateScopeProvider`
发布作用域,`dataSource` 只是取数适配器。另外给两个接线示例加上 `os:check` 标记,让它们第一次真的被编译校验。

### 为什么改
2026-09-13 的裁决(objectui#9308 option B)已经把这两件事退役,但发布给 AI
读的技能包还在教它们。更要紧的是:这不是改名。同一条 `data.*` 门,旧接线下恒判 `false`、新接线下因为根缺失走
fail-soft 恒判 `true` —— 一个 `visible` 门从「每行都藏」变成「每行都显」。只换 provider
名字的迁移说明会让读者踩正这一脚。

### 风险与代价(含回滚)
- **排序风险(主要):** objectstack-ai#9369 还没合。在它合进去之前,这三页描述的是尚未落地的行为。⚠️ 建议排在 objectstack-ai#9369 之后合。
- 保留了全部 `${data.*}` 示例原文,只让接线示例发布一个叫 `data` 的根 —— 因为
`skill-guide-provider-envelope` 这个 pin 正在渲染这些拼写,改写它们会在另一个 PR 的盲区里挪动 pin
的被测主体。
- 回滚代价:纯文档,`git revert` 一笔即可,不影响任何已发布包(本仓 43 个 manifest 无一发布 `skills/`)。

### 席位意见
(留空,待 `os-zhuang` / `hotlong` 定稿)

### 你要做的
1. 确认合并顺序:先 objectstack-ai#9369,再本 PR。
2. 作为受管面的人工合并方给出 APPROVED review(这一道是 `check-governed-queue-guard`
机械要求的)。
3. clause-② 的 `needs:contract-review` 需要一份书面复核记录后才清 —— ⛔
两道保障叠加,人工合并不替代它。
4. 顺带定一句话:本仓单文件 `skills/` 改动到底要不要写空 frontmatter changeset(objectstack-ai#9352 没写、objectstack-ai#9374
写了,门禁两边都放行)。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ)_


---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <jack@objectstack.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(skills): the auth-permissions.md useAuth example reads user.name behind an isAuthenticated guard that guest mode and preview mode hardcode true

3 participants