Repository navigation
docs(skills): objectstack-api says the form's own publicFormGrant authorizes a public submit and the guest_portal name admits nothing - #24262
Conversation
…horizes a public submit and the guest_portal name admits nothing
The public-form passage of skills/objectstack-api/SKILL.md described the
anonymous form endpoints as running under a synthetic
`{ permissions: ['guest_portal'], anonymous: true }` context and named
nothing else, so an author inferred that a guest_portal permission set
authorizes the submit, or that the form falls open without one. It now
names the declaration-derived publicFormGrant (create and read-back of the
row it made on the form's target object, nothing else, decided before any
permission set resolves) as what authorizes the submit, and says that the
guest_portal name admits nothing and needs no set: one registered under it
only feeds the grant's result masker. Matches the docs page and the
registerFormEndpoints docstring. Text only; a patch changeset for
@objectstack/skills.
Claude-Session: https://claude.ai/code/session_01QBcLjVCmcWbZEk6qhYMib2
Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Face: governed rules text — ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读改了什么:对外发布的技能 为什么改:文档页与 REST 代码注释(PR #24237)已改成与运行时一致的说法,技能是第三份文本、还在说旧话;AI 作者读了会以为必须声明 风险与代价(含回滚):纯文本,不改运行时;席位逐句对照了 席位意见:ACCEPT。席内达档契约复核 PASS(6246139662,head 你要做的: 席位核对Seat Implemented-by:
Generated by Claude Code |
Fixes #24239
Clause-②: no
What changes
One published file and one changeset. No behaviour changes; the form routes are untouched.
skills/objectstack-api/SKILL.md, the public-form passage (lines 87–89 at8eccf519, now 87–91). It said the anonymous form endpoints 「run under a synthetic{ permissions: ['guest_portal'], anonymous: true }execution context」 and named nothing else, so an author read it as "declare aguest_portalset to authorize the submit", or as "the form falls open without one". It now says what runs: the form's own declaration authorizes the submit through apublicFormGranton its target object (create and read-back of its new row, nothing else), decided before any permission set resolves; theguest_portalname on the submit context admits nothing and needs no set; a set so named only feeds the grant's result masker. TheenforceAuthbypass, the Web-to-Lead / Web-to-Case framing, the field allow-list, thebeforeInsertdefaults hook, the walled-posture withholding and the two contract pointers are kept as they were..changeset/24239-public-form-grant-skill.md—@objectstack/skills:patch(the package shipsskills/**asdist/skills/**; measured below).The three texts now say the same thing, each in its own register:
content/docs/ui/public-data-collection.mdx(section 2), theregisterFormEndpointsdocstring inpackages/rest/src/rest-server.ts, and this skill.Before:
After:
Line budget (
skills/**readings)Tokens follow the ratchet's own convention (
scripts/check-skills-token-ratchet.mjs: ceil(utf8 bytes / 4), the release-stampversion:line unpriced); lines arewc -l.8eccf519)e551e42f)skills/objectstack-api/SKILL.md, linesskills/objectstack-api/SKILL.md, tokenscheck:skills-token-ratchet)SKILL.mdunderskills/(10 files), linesSKILL.mdunderskills/(10 files), tokensThree lines became five. Nothing else in the file moved, and no line was re-wrapped to pay for them. The widest new line is 82 characters, which is the file's existing maximum for a prose line (line 180).
Measured
8eccf519:grep -rn "guest_portal\|publicFormGrant" skills/hits once,skills/objectstack-api/SKILL.md:88; the controlgrep -rn "enforceAuth" skills/hits once, line 87 of the same file. No other skill file carries the claim, so nothing else is fixed here.guest_portalprofile authorizes an anonymous form submit; measured on 17.7.0, it does not #24130's measurement. The wording followspackages/plugins/plugin-security/src/security-plugin.ts(thepublicFormGrantbranch, about line 2412: it admitsinsert/find/findOne/counton the grant's object only, before any permission set resolves, and resolves the context's named sets only for the result masker) and theregisterFormEndpointsdocstring asmainhas it.check:skill-docsregenerated nothing: the worktree is clean after the run. The generator reads frontmatter; the passage is body text.maintouch is2c9bf943, the version script's frontmatter re-stamp; the passage read exactly as the card quoted it at8eccf519.pnpm --filter @objectstack/skills build(itssync-catalog, self-test 9 cases then the copy): 「65 file(s) across 10 skill(s) copied from skills/** into dist/skills and read back byte-identical」, exit 0;publicFormGrantappears once inpackages/skills/dist/skills/objectstack-api/SKILL.md.@objectstack/skillsdeclares notestortypecheckscript, so there is no package test to run (a filtered run that matches no script exits 0 and measures nothing, so none is claimed).origin/mainmoved 3 commits past the base while this was in flight (fb6fb24e). None touchesskills/**,.changeset/**or a gate that reads them (the derivation namesscripts/check-route-envelope.mjsand one ADR anchor as changed across the range; neither is in this card's derived list), so the branch was not merged forward.Gates (head
e551e42f)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; the change set from the merge base) derived 43 commands. Every exit code was captured before any pipe (eval "$cmd" > log 2>&1; status=$?) and recorded beside the command for--ran.check:skills-token-ratchet(「54 authored bundle file(s) within their ceilings」; this file 4691 of 6314),check:skill-docs(「Skill docs in sync」),check:skill-identifier-liveness,check:skill-compatibility,check:skill-frame-sync,check:corpus-claim-drift,check:doc-authoring,check:role-word,check:nul-bytes,check:pm-skill-id-lint,check:pm-governed-merges,check:published-files, and the changeset gates (check-adr-0087-registration,check-changeset-no-majorandcheck-empty-changeset, each with--base origin/main;check:changeset-gate-self-tests;check:objectui-changeset;check:pm-changeset-deadline-census). Two of the 42 first refused withPREREQUISITE NOT MET(exit 3, unbuilt packages):pnpm --filter @objectstack/lint run check:doc-formula-expressionsandpnpm check:lean-entry-closure. Afterpnpm exec turbo run build --filter=@objectstack/objectql --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2under the verify lock (「Tasks: 14 successful, 14 total」; lock 「VERDICT command-exit 0 · held the lock 95s」) both exit 0 (lean-entry-closure: 「Admitted set held exactly (15 packages); 6 denied names absent」).check:dts-closureandcheck:sourcemap-no-sources-contentwere re-run over the same 15 built packages (「75/75 declared declaration file(s) present」; 「105 map(s), none embed source text」); their whole-tree reading is CI's.pnpm check:dual-build-cjs-loads— exit 3,PREREQUISITE NOT MET: it loads every package's built output, and 111 packages have nodist/in this fresh worktree. A full-tree build is out of proportion for a five-line text change, so this is a declared narrowing: CI'sLint & Repo Gatesruns it on this PR and is the reading of record.--ranreconciliation (every linecommand :: exit N): 「43 derived famil(ies) accounted for — 42 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)」, 0 UNRUN.test:repo, the repo-widepnpm lint, the type-check lanes, and the artifact-roster and wide-population families the derivation lists outside its total.origin/mainfb6fb24e. The two derived-from files that changed across that range (scripts/check-route-envelope.mjsandscripts/adr-anchors/packages__rest__src__meta-item-write-routes.ts.json) belong to families this card's paths do not name — neither is among the 43 commands — so no command above read an old copy of a script CI will run differently.Acceptance notes
guest_portalset the masker reads (its field permissions, and the capabilities it holds against a field'srequiredPermissions). That clause is not in the skill: it costs a sixth line against a +2 budget, and an author who needs it reads the docs page the passage already sits beside. Noted, not filed.anonymous: trueon the context; the new text does not. The key has no reader (card finding(forms): the CRM example and the submit route's docstring say theguest_portalprofile authorizes an anonymous form submit; measured on 17.7.0, it does not #24130's measurement: no non-test property read ofanonymousinpackages/), so the skill no longer teaches it. Noted, not filed.维护者速读(草稿)
改了什么 — 只改一个对外发布的技能文件
skills/objectstack-api/SKILL.md里讲公开表单(匿名提交)的那一段。原来说这两个端点「在一个合成的{ permissions: ['guest_portal'], anonymous: true }执行上下文下运行」;现在改为说清楚:提交由表单自己的声明授权——运行时为它派生一个只限该表单目标对象、只允许创建和读回刚建那一行的publicFormGrant,在任何权限集解析之前就决定;上下文里的guest_portal这个名字不授予任何权限,也不需要声明权限集;就算部署注册了同名权限集,它也只用于结果脱敏。其余内容(字段白名单、beforeInsert默认值、隔离姿态下的下线、契约文件指引)原样保留。另加一条@objectstack/skills的 patch changeset。为什么改 — 文档页和 REST 代码注释(PR #24237)已经改成与运行时一致的说法,技能文件是第三份文本,还在说旧话。AI 作者读技能时会推断「必须声明一个
guest_portal权限集才能让提交通过」(CRM 示例就是这么被误导的),或者反过来以为不声明表单就会「敞开」——两者都不是运行时的真实行为。发布的承诺必须与运行的契约一致。风险与代价(含回滚) — 纯文本改动,不改任何运行时行为。文件 +2 行、+49 token(上限 6314,现 4691),整个技能包 +2 行、+49 token;所有读这个文件的门禁本地均绿(token 棘轮、skill-docs、identifier-liveness、corpus-claim-drift 等)。风险只在措辞是否准确,已逐句对照安全插件源码和 REST 注释。回滚即 revert 这一个 PR。
席位意见 —
你要做的 —
skills/**是 Tier H 治理面:请读上面的 Before / After 两段,确认措辞与运行时一致;无误则由授权账号 approve,落地由席位经队列完成,不需要你手动合并。Generated by Claude Code