Skip to content

docs(skills): objectstack-api says the form's own publicFormGrant authorizes a public submit and the guest_portal name admits nothing - #24262

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-24239-public-form-grant-skill
Oct 18, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-24239-public-form-grant-skill

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 at 8eccf519, 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 a guest_portal set 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 a publicFormGrant on its target object (create and read-back of its new row, nothing else), decided before any permission set resolves; the guest_portal name on the submit context admits nothing and needs no set; a set so named only feeds the grant's result masker. The enforceAuth bypass, the Web-to-Lead / Web-to-Case framing, the field allow-list, the beforeInsert defaults 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 ships skills/** as dist/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), the registerFormEndpoints docstring in packages/rest/src/rest-server.ts, and this skill.

Before:

These bypass `enforceAuth`, run under a synthetic
`{ permissions: ['guest_portal'], anonymous: true }` execution context, and
are intended for Web-to-Lead / Web-to-Case style flows. The framework

After:

These bypass `enforceAuth` (Web-to-Lead / Web-to-Case flows). The form's own
declaration authorizes the submit: a `publicFormGrant` on its target object
(create and read-back of its new row, nothing else), decided before any permission
set resolves. The submit context's `guest_portal` name admits nothing and needs
no set; a set so named only feeds the grant's result masker. The framework

Line budget (skills/** readings)

Tokens follow the ratchet's own convention (scripts/check-skills-token-ratchet.mjs: ceil(utf8 bytes / 4), the release-stamp version: line unpriced); lines are wc -l.

reading before (8eccf519) after (e551e42f) delta ceiling
skills/objectstack-api/SKILL.md, lines 415 417 +2 the order's budget: +2
skills/objectstack-api/SKILL.md, tokens 4642 4691 +49 6314 (check:skills-token-ratchet)
package, every SKILL.md under skills/ (10 files), lines 4392 4394 +2 —
package, every SKILL.md under skills/ (10 files), tokens 52555 52604 +49 per-file ceilings; the gate is green

Three 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

  • (a) The passage is the only place in the catalog that says it. At 8eccf519: grep -rn "guest_portal\|publicFormGrant" skills/ hits once, skills/objectstack-api/SKILL.md:88; the control grep -rn "enforceAuth" skills/ hits once, line 87 of the same file. No other skill file carries the claim, so nothing else is fixed here.
  • (b) What runs was not re-measured; the order carries card finding(forms): the CRM example and the submit route's docstring say the guest_portal profile authorizes an anonymous form submit; measured on 17.7.0, it does not #24130's measurement. The wording follows packages/plugins/plugin-security/src/security-plugin.ts (the publicFormGrant branch, about line 2412: it admits insert / find / findOne / count on the grant's object only, before any permission set resolves, and resolves the context's named sets only for the result masker) and the registerFormEndpoints docstring as main has it.
  • (c) check:skill-docs regenerated nothing: the worktree is clean after the run. The generator reads frontmatter; the passage is body text.
  • (d) The file's last main touch is 2c9bf943, the version script's frontmatter re-stamp; the passage read exactly as the card quoted it at 8eccf519.
  • The package ships the text. pnpm --filter @objectstack/skills build (its sync-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; publicFormGrant appears once in packages/skills/dist/skills/objectstack-api/SKILL.md. @objectstack/skills declares no test or typecheck script, 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/main moved 3 commits past the base while this was in flight (fb6fb24e). None touches skills/**, .changeset/** or a gate that reads them (the derivation names scripts/check-route-envelope.mjs and 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.

  • 42 of 43 exit 0 — among them 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-major and check-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 with PREREQUISITE NOT MET (exit 3, unbuilt packages): pnpm --filter @objectstack/lint run check:doc-formula-expressions and pnpm check:lean-entry-closure. After pnpm exec turbo run build --filter=@objectstack/objectql --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2 under 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-closure and check:sourcemap-no-sources-content were 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.
  • 1 NOT MEASURED locally: pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: it loads every package's built output, and 111 packages have no dist/ 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's Lint & Repo Gates runs it on this PR and is the reading of record.
  • --ran reconciliation (every line command :: exit N): 「43 derived famil(ies) accounted for — 42 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)」, 0 UNRUN.
  • Not run locally, CI's: test:repo, the repo-wide pnpm lint, the type-check lanes, and the artifact-roster and wide-population families the derivation lists outside its total.
  • Stale-tree note from the derivation: this tree is 3 commits behind origin/main fb6fb24e. The two derived-from files that changed across that range (scripts/check-route-envelope.mjs and scripts/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

维护者速读(草稿)

改了什么 — 只改一个对外发布的技能文件 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

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Oct 18, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e551e42ff9c67c196caf7a6a5c904105b7999d2e
Local-runs: none
Scope: skills/objectstack-api/SKILL.md + packages/plugins/plugin-security/src/security-plugin.ts (the publicFormGrant admission the passage describes), packages/rest/src/rest-server.ts (the registerFormEndpoints docstring it must match), content/docs/ui/public-data-collection.mdx (the docs page it must match), ADR-0056 Option A and ADR-0138's guest_portal fate row (the recorded contract), card 24130's measurement 6244037824 (read this act)

Face: governed rules text — skills/**, the published catalog (Tier H); no published schema, no narrowing line, Clause-②: no. The .changeset prose is the lane seat's ACCEPT face, read sentence by sentence there. Inputs: card 24239 (body, triage's grade 6244939176, the claim 6245879378, the order 6245884156, the report 6246111739) and PR 24262 (body, file list, the net diff +5/−3 on the skill and +9 on the changeset at the head above); the head's check-runs at this read were 30 success, 6 skipped, 4 in progress — the landing check reads them again, this record judges the diff. Rendered in seat at 2026-10-18T05:33Z; the seat's served tier equals the value of CONTRACT_REVIEW_TIER.

① Derived judgments

  1. The published promise about WHAT AUTHORIZES the submit. BEFORE: 「run under a synthetic { permissions: ['guest_portal'], anonymous: true } execution context」 and nothing else — an author infers a guest_portal set authorizes the submit, or that the form falls open without one. AFTER: 「The form's own declaration authorizes the submit: a publicFormGrant on its target object (create and read-back of its new row, nothing else), decided before any permission set resolves.」 Right: the seat read security-plugin.ts's publicFormGrant branch — it admits the create and the read-back (find / findOne / count) on the grant's object only, before any permission set resolves — and the registerFormEndpoints docstring says the same in the same order (ADR-0056 Option A).
  2. The published promise about the guest_portal NAME. AFTER: 「The submit context's guest_portal name admits nothing and needs no set; a set so named only feeds the grant's result masker.」 Right: the docstring's 「That name admits nothing. Where a deployment registers a set under it, the SecurityPlugin resolves that set as the caller's for the grant's result masker」 and ADR-0138's fate row (kept, for that role). The clause 「needs no set」 is the author-facing correction the card asks for (an AI author no longer declares one).
  3. What the passage dropped: anonymous: true on the context. Right to drop: card 24130's measurement (an engine-side beforeInsert hook sees neither permissions nor anonymous; no non-test property read of anonymous in packages/) means the key teaches nothing an author can use; the docstring does not name it either. The three texts (docs page, docstring, skill) now agree; the skill stays terser, which is its register.
  4. What the passage kept: the enforceAuth bypass, the Web-to-Lead / Web-to-Case framing, the field allow-list, the beforeInsert defaults hook, the walled-posture withholding, both contract pointers. Right: the diff shows the five new lines and nothing else moved (+5/−3 in a 417-line file).
  5. Budget: 415 → 417 lines (the order's +2), 4642 → 4691 tokens (ceiling 6314); the widest new line is 82 characters, the file's existing prose maximum; no re-wrap paid for lines. Whole-package readings reported (10 SKILL.md, +2 lines, +49 tokens). Right. Value density from a customer agent's seat: the two added sentences replace one wrong fact with the one fact an author needs; no example, no section, no expansion.
  6. Public surface: @objectstack/skills publishes skills/** as dist/skills/** (the dev's build read the corrected text in the tarball tree once). The published accept set does not move: text only, the form routes untouched.

② Semver level

'@objectstack/skills': patch in .changeset/24239-public-form-grant-skill.md, the shape of the lane's previous skills changeset; Check Changeset success on the head; Clause-②: no on body line 2 and in the changeset. Right: a correction of published prose in a released package is a patch, never skip-changeset.

③ Boundary flags

  • Dev open_questions 1 (spend a third line for which parts of a registered guest_portal set the masker reads — field permissions and the capabilities held against requiredPermissions): A, keep as landed. The author-facing fact (the name admits nothing and needs no set) is in; the masker's inputs are a deployment-side detail the docs page already carries beside the passage; the +2 budget was the seat's and stands. No patch round.
  • Dev observation (Acceptance notes): the dropped anonymous: true — judged in ①.3; no card.
  • Dev deviations D1–D5 read: check:dual-build-cjs-loads NOT MEASURED locally (111 unbuilt packages; CI's Lint & Repo Gates is the reading of record — the lane completed success on the head at this read); origin/main not merged forward (the 3 commits past the base touch no file or gate of this PR; the merge queue rebuilds on the current main); trailer and footer forms per AGENTS.md; the size/s label is the Auto Label actor's; the masker clause left out — all accepted.
  • needs:contract-review: not owed; this record is the at-tier record on the current head.

Implemented-by: claude/issue-24239-public-form-grant-skill
Reviewed-by: session_01QBcLjVCmcWbZEk6qhYMib2

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:对外发布的技能 skills/objectstack-api/SKILL.md 讲公开表单(匿名提交)的那一段,3 行改成 5 行(文件 415→417 行,4642→4691 token,上限 6314)。原来说端点「在合成的 { permissions: ['guest_portal'], anonymous: true } 上下文下运行」就没了;现在说清楚:提交由表单自己的声明授权——运行时派生一个只限该表单目标对象、只允许创建与读回刚建那一行的 publicFormGrant,在任何权限集解析之前就决定;上下文里的 guest_portal 名字不授予任何权限、也不需要声明权限集;部署若注册了同名权限集,只用于结果脱敏。其余(enforceAuth 旁路、字段白名单、beforeInsert 默认值、隔离姿态下线、契约指引)原样。另加 @objectstack/skills 的 patch changeset。

为什么改:文档页与 REST 代码注释(PR #24237)已改成与运行时一致的说法,技能是第三份文本、还在说旧话;AI 作者读了会以为必须声明 guest_portal 权限集才能提交(CRM 示例就是这样被误导的),或反过来以为不声明表单就敞开——两者都不对。发布的承诺必须与运行的契约一致(北极星第 4 条)。

风险与代价(含回滚):纯文本,不改运行时;席位逐句对照了 security-plugin.ts 的 publicFormGrant 分支(只放行 insert/find/findOne/count、在权限集之前)与 registerFormEndpoints 注释。+2 行、+49 token,整包同增;所有读该文件的门禁绿。回滚 = revert 本 PR。

席位意见:ACCEPT。席内达档契约复核 PASS(6246139662,head e551e42f)。dev 问的「要不要再花一行写脱敏器读权限集的哪些部分」席位答 A:不加,作者要的事实(该名字不授权、不需要权限集)已在,脱敏细节文档页就在旁边。head 43 个 check 34 绿、9 预期跳过、0 红。

你要做的:skills/** 是 Tier H:读上面 Before/After 两段,无误则由授权账号 approve(已向 os-zhuang、hotlong 请审);批准后由席位经队列落地,不需要您手动合并。

席位核对

Seat domain:skills#1 · session_01QBcLjVCmcWbZEk6qhYMib2 (sunhaolin) · read 2026-10-18T05:41Z · head e551e42ff9c67c196caf7a6a5c904105b7999d2e · verdict: ACCEPT (Tier H — four-piece set: this record, needs-user-decision on the PR, review requested from both approver accounts, listed as awaiting a human merge in the round report; the seat lands it on an authorized APPROVED review)

Implemented-by: claude/issue-24239-public-form-grant-skill
Reviewed-by: session_01QBcLjVCmcWbZEk6qhYMib2

  1. Shape. Draft PR against main; body line 1 Fixes #24239, line 2 Clause-②: no (copied from claim 6245879378). The only closing keyword in the body is that line; PR docs(rest,app-crm): the public form submit is authorized by its declaration, not by a guest_portal set #24237 and card 24130 appear beside no closing verb. File list: skills/objectstack-api/SKILL.md (+5/−3), .changeset/24239-public-form-grant-skill.md (+9). mergeable true at this read.
  2. Premise and acceptance, read by the seat. The passage read as the card quoted it at 8eccf519 (the dev's grep: one hit in the catalog, control enforceAuth hits); the Done-when holds — publicFormGrant named as what authorizes the submit, the guest_portal name's role stated, the text matching the docs page and the docstring (contract review 6246139662 ①.1–①.4). Line budget +2 held; token 4691/6314; no re-wrap.
  3. Gates — check-runs pinned to head e551e42f. 43 completed: 34 success (Lint & Repo Gates lanes, Type Check · lanes, Test Core 1–6/6, Build Core, Governed Surface Queue Guard, Check Changeset, the four claim checks, Dogfood Regression Gate), 9 skipped all in the roster (check-expected-skips.mjs --pr 24262 OK, exit 0; Check Changeset re-ran on the label write and is success), 0 failed. check-governed-merges.mjs --pr 24262: GOVERNED, Tier H, skills/** ×1, +14/−3 under the 3000-line threshold. The dev's local subset: 42 of 43 derived families exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, declared; CI's lane success), --ran reconciled.
  4. Changeset — read sentence by sentence (prose face, the seat's ACCEPT). '@objectstack/skills': patch: right (published prose correction). First line: 「objectstack-api states that a public form's submit is authorized by the form's own publicFormGrant, and that the guest_portal name on the submit context admits nothing and needs no permission set」: matches the diff. Clause-②: no: right. Body paragraph: the wrong inference (declare a set / falls open) — matches the card; the running contract (ADR-0056 Option A, the grant's scope, decided before any set resolves) — matches security-plugin.ts L2412–L2425; the masker role of a set named guest_portal — matches the docstring; 「matching content/docs/ui/public-data-collection.mdx and the registerFormEndpoints docstring in @objectstack/rest」 — right; 「Text only; nothing an author writes changes, and the form routes' behaviour is unchanged」 — right.
  5. Report hygiene. mcp_calls 0; api_writes 2 relay strokes (pr_create with assignee; the report comment), no PATCH/PUT/DELETE; two git push. Deviations D1–D5 answered in the contract review ③. The size/s label is the Auto Label actor's.
  6. Out of scope. Two Acceptance notes (the dropped anonymous: true, judged ①.3; the masker-inputs clause, answered A); neither a card.
  7. Landing (Tier H). Pre-landing check ①: the ## Contract review PASS record 6246139662 names this head. Check ②: every check green or an expected skip. The PR stays draft: needs-user-decision is on it and review is requested from os-zhuang and hotlong; on an authorized APPROVED review the seat clears the label, flips ready and arms auto-merge; a later push does not spend the approval. subscribe_pr_activity is live on this PR since 2026-10-18T05:31Z.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 18, 2026 06:30
@os-zhuang
os-zhuang enabled auto-merge October 18, 2026 06:30
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 53ef752 Oct 18, 2026
47 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-24239-public-form-grant-skill branch October 18, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

3 participants