fix(skills): stop naming app as an expression root in two published guides - #8906
Conversation
… guides `app` is not a root the runtime binds. objectui#8155 ruled that `@objectstack/formula`'s `SCOPE_ROOTS` is the contract and app-shell aligns to it; PR #8164 shipped that -- `buildExpressionScope` in `packages/app-shell/src/providers/ExpressionProvider.tsx` returns `{ current_user, user, ctx:{user}, os:{user}, data, features }` and no `app`. Two lines in the PUBLISHED skills tree still taught it, and an unresolvable visibility predicate FAILS OPEN on these surfaces, so a reader who followed them authored a gate that silently passes: guides/auth-permissions.md -- the "Expression scope: which roots resolve" table listed `app`, `features` on one row. `features` IS a root and stays, with its `${features.multiOrgEnabled}` example; `app` is dropped. rules/protocol.md -- the `visible` CEL-predicate root list beside an action param read (`features` / `current_user` / `app` / `data`). Surviving roots are spelled in the order `SCOPE_ROOTS` declares them, measured against the installed `@objectstack/formula@17.4.0` (`dist/index.d.ts:215`, the version objectui's `^17.0.0` resolves to): `data` at index 18, `features` at 23, `current_user` at 26 -- so `data` / `features` / `current_user`. The upstream twin objectstack#17203 mirrors that spelling. Both lines were tested against the card's own criterion before editing: each describes the PREDICATE SCOPE, not the React context value. The context value does still carry `app` (`ExpressionProvider` returns `{ user, app, data, features, evaluator }` -- `DashboardView` reads it as a plain value), so the surfaces that describe THAT are correct as they stand and are untouched: the `app.settings.users` nested-`bind` mentions in `rules/protocol.md:215` and `guides/schema-expressions.md:305` (data binding against `dataSource`, a different mechanism) and the `content/docs/guide/console-architecture.md:44` ASCII diagram. Claude-Session: https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么 — 发布给使用者的 skills 树里两行文档:权限指南里「哪些根能解析」那张表删掉 为什么改 — objectui#8155 已裁定引擎的 风险与代价(含回滚) — 两行散文,无代码路径、无生成产物;changeset 门禁自判不欠。次序按 席位意见 — 本席以契约复审档复核通过:两行逐字核过、三处围栏未动、受管判定与 skills 门禁在该提交上全绿、CI 29 项全绿、合并无冲突。建议合并。 你要做的 — 受管面走人工合并:看一眼这两行,认可即批准并合并本 PR。是否合并? Generated by Claude Code |
Fixes #8810
Two lines in the published skills tree stopped being true when objectui#8164 landed the
objectui#8155 ruling:
appis no longer an expression/predicate root, because neither ADR-0068nor
@objectstack/formula'sSCOPE_ROOTSever declared one. An unresolvable visibility predicatefails open on these surfaces, so a reader who followed either line authored a gate that
silently passes — and one of the two lines is in the permissions guide, which is the worst place
to hand somebody a silently-open gate.
Diff is exactly two lines, both under
skills/**. Nothing else.Before / after
skills/objectui/guides/auth-permissions.md:312— a row in the table under### Expression scope: which roots resolve:featuresis a root and stays, with its example unchanged.skills/objectui/rules/protocol.md:283— thevisibleroot list on an action param:The card's own test, answered once per line before editing
objectui#8810 and triage both make this the gating question: is the line describing the React
context value (which still carries
app— fine as written) or the predicate scope (fix)?Answered independently here, and both answers agree with triage's read.
Line 1,
auth-permissions.md:312— PREDICATE SCOPE ⇒ fix. The table's own lead-in sentence is`SchemaRenderer` evaluates every schema expression against a fixed scope:, its first columnheader is
Root, and every sibling row is an evaluator root carrying a${...}example. That"fixed scope" is
buildExpressionScope, measured on this branch atpackages/app-shell/src/providers/ExpressionProvider.tsx:118:No
app. A React context value has no rows in a table of expression roots.Line 2,
protocol.md:283— PREDICATE SCOPE ⇒ fix. The sentence names the mechanism literally(
`visible` is a CEL predicate) and the parenthesis is that predicate's root list. The contextvalue is not a CEL scope, so this line cannot be describing it.
And the counter-example, which is why the test is worth asking. The context value genuinely
does still carry
app— same file,ExpressionProviderreturns{ user, app, data, features, evaluator }, with the in-repo comment"
appis still published on the context value —DashboardViewreads it as a plain value. It isNOT handed to the evaluator". So the three surfaces that describe that are correct as they stand
and are deliberately untouched:
skills/objectui/rules/protocol.md:215andskills/objectui/guides/schema-expressions.md:305—the nested
bindpathapp.settings.usersresolving againstdataSource. Data binding, adifferent mechanism;
bindis not expression-evaluated at all.content/docs/guide/console-architecture.md:44— the ASCII diagram of the context value. Alsooutside
skills/**, and a mixed diff would change this PR's landing route.Wording for the upstream twin (objectstack#17203)
That card is open, spec lane, still queued: its three sites are not reworded yet, so this PR lands
first and states the spelling plainly so the two repos end up with one truth rather than two.
The chosen rule: spell the surviving roots in the order
SCOPE_ROOTSdeclares them. Measured,not recalled — read through objectui's installed dependency rather than any checkout of the
producer, since
^17.0.0is what this repo actually consumes. Resolved to@objectstack/formula@17.4.0,dist/index.d.ts:215, quoted whole:appis absent. Positive controls present in the same read:data(index 18),features(23),current_user(26), plususer(12),os(4),ctx(22) — so the absence is a reading of a livelist, not an empty match.
⇒ The canonical spelling for a list of these three is⚠️
data/features/current_user, andthat is what
protocol.md:283now carries.action.zod.ts:417's parenthesis upstream is the samethree roots plus
appin a different order (current_user/app/data/features), so itmirrors to exactly that string.
page.zod.ts:303/:326are not a clean mirror: their listis
app,features,os.user, andos.useris a path under theosroot rather than a rootitself, so the ordering rule does not settle it — that stays the spec seat's call, and only the
apptoken is common ground. ⛔ Not folded into one card: two repos, two landing routes.Gates
No package source is touched, so there is no build closure to run and no repo-wide
pnpm lint/pnpm testowed here — those are CI's. objectui has nodispatch-gates.mjs, so the list below wasderived by hand from
package.jsonscripts plus every.github/workflows/*.ymltrigger, then runin full. Each gate's own verdict line, not a bare exit code:
check-governed-queue-guard.mjs --self-testOK check-governed-queue-guard self-test: 132 cases passcheck-governed-queue-guard.mjs --test(both paths)⛔ GOVERNED — 2 of 2 path(s) are on a governed surface: skills/** x2 — the published skills catalogcheck-skills-paths.mjs✅ OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined)check-skill-examples.mjsEvery marked skill example holds up against the built types.(14/14 ts fences, 70/70 json, 0 failed)check-skill-eval-tokens.mjs --self-testOK ... 29 cases passcheck-skill-eval-tokens.mjsEvery must_contain token is taught by its own skill bundle.(125 tokens)check-doc-fence-languages.mjs --self-test26 cases passcheck-doc-fence-languages.mjs✅ every TypeScript block in 227 document(s) is fenced ts/tsx/typescriptcheck-control-bytes.mjs✅ OK (scanned 7087 tracked text file(s); skipped 85 binary)check-shell-escape-residue.mjs✅ OK (5/5 root(s) resolved ... skills: 16 file(s), 210 fence(s))check-doc-links.mjsLinks are valid across 17 scan roots.check-changeset-presence.mjscheck-upstream-port-parity.mjs✓ 3 ported file(s) match ... modulo their declared divergencescheck-bash32-floor.mjs✓ 13 tracked shell file(s) ... name no bash 4+ construct--testexiting 3 is the GOVERNED answer, not a failure — it is this PR's routing verdict andthe reason the PR is parked as a draft.
Changeset. objectui has no
skip-changesetlabel, so the gate's own verdict is thedeclaration, quoted verbatim:
⇒ No changeset written, and ⛔ no label applied.
check-skill-examplesneeded a real build (its--build-filterderives 12 packages). Runthrough the container's shared heavy-verify lock: 29/29 turbo tasks successful,
VERDICT command-exit 0 · held the lock 185s · waited 0s, and the gate then ran green against thefreshly built types.
Acceptance notes
skills/**=skills-catalog). Draft at the human terminal:⛔ not flipped ready, ⛔ not enqueued, ⛔ no auto-merge, ⛔ no approving review from this seat.
auth-permissions.md:312andprotocol.md:283are still exact onf52a9d7a,despite
d65b2baabeing the newest touch of the guide. No drift to correct.auth-permissions.md(line 312 outside all of them; nearest is 296–302), 13 inprotocol.md(line 283 outside; nearest is 266–275). Both nearest fences are byte-unchanged, and
check-skill-examplesconfirms the whole marked population still compiles.actually read
skills/**markdown —check-skills-paths,check-skill-examples,check-skill-eval-tokens,check-doc-fence-languages. All four were derived from the workflowtriggers and run; all four are green. Reported so the next
skills/**card starts from thefull list.
skills/objectui/guides/page-builder.md:329reads"reachable only through the
page/app/utility/home/record" and is a fourthmechanism again: those are page kinds in a routing sentence, not expression roots. Correct as
it stands, out of this card's bounds, and recorded only so the next reader who greps
`app`across this tree does not have to re-derive it. Carrier if it ever needs one: whoever next edits
page-builder.md— objectui#8865 is open on that file today, but on a disjoint concern.维护者速读(草稿)
改了什么 —— 发布给使用者的 skills 树里,两行文档不再把
app列为可以在表达式/谓词里读取的根。一行是权限指南里「哪些根能解析」那张表,一行是
protocol.md里 action 参数visible谓词的根清单。只动这两行,不动代码,不动任何别的文件。
为什么改 —— objectui#8155 已裁定:引擎的
SCOPE_ROOTS是契约,前端向它对齐,而那张表里从来没有app;PR #8164 已经把绑定摘掉了。文档没跟上,于是已发布的指南在教一个运行时不兑现的能力。后果不是报错而是静默放行:照着写的可见性谓词解析不到,在这两个面上读作「是」,门就默默开着。客户是照这棵
skills 树写元数据的,爆炸半径比仓内文档大;而其中一行恰好就在讲权限的那份指南里 —— 那是最不该给出
一个静默放行示例的位置。
风险与代价(含回滚) —— 风险很低:两行散文,没有代码路径、没有生成产物、没有 changeset(门禁自己
判定不欠)。已发布的
features根和它的例子原样保留,只删app一个词。表达式根清单的次序改成按SCOPE_ROOTS的声明次序写,是为了让上游 objectstack#17203 能照抄同一份拼写,不至于两个仓分裂成两份真理。回滚就是把这个 diff 反向打回去,没有任何后续依赖。
席位意见 ——
你要做的 —— 这是受管面(
skills/**),按规矩停在 draft 等你人工合并;你的那次合并动作本身就是审核记录。另外有一件跨仓的事需要你知道:上游 objectstack#17203 的三处还没改,本 PR 先落地并在正文里
写明了选定的拼写(
data/features/current_user),供 spec 席位镜像;其中page.zod.ts那两处含
os.user,不是干净的镜像,留给 spec 席位自己判。Generated by Claude Code