Skip to content

Commit 53ef752

Browse files
docs(skills): objectstack-api says the form's own publicFormGrant authorizes a public submit and the guest_portal name admits nothing (#24262)
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 #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 - The docs page also says which parts of a registered `guest_portal` set the masker reads (its field permissions, and the capabilities it holds against a field's `requiredPermissions`). 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. - The old text named `anonymous: true` on the context; the new text does not. The key has no reader (card #24130's measurement: no non-test property read of `anonymous` in `packages/`), 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](https://claude.ai/code/session_01QBcLjVCmcWbZEk6qhYMib2)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0d34066 commit 53ef752

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): `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 (#24239)
6+
7+
Clause-②: no
8+
9+
`skills/objectstack-api/SKILL.md` described the anonymous form endpoints as running under a synthetic `{ permissions: ['guest_portal'], anonymous: true }` execution context and named nothing else, so an author reading it inferred that a `guest_portal` permission set authorizes the submit and declared one, or concluded that the form falls open without one. Neither is true: the submit is authorized by the form's own declaration (ADR-0056 Option A) — the route hands the engine a `publicFormGrant` scoped to the form's target object, which admits the create and the read-back of the row it made and nothing else, decided before any permission set resolves. The `guest_portal` name on the context admits nothing; where a deployment registers a set under it, that set only feeds the grant's result masker. The passage now says so, matching `content/docs/ui/public-data-collection.mdx` and the `registerFormEndpoints` docstring in `@objectstack/rest`. Text only; nothing an author writes changes, and the form routes' behaviour is unchanged.

‎skills/objectstack-api/SKILL.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,11 @@ GET /api/v1/forms/:slug # returns form spec + restricted objectSchema
8484
POST /api/v1/forms/:slug/submit # whitelist-filtered INSERT, no auth header
8585
```
8686

87-
These bypass `enforceAuth`, run under a synthetic
88-
`{ permissions: ['guest_portal'], anonymous: true }` execution context, and
89-
are intended for Web-to-Lead / Web-to-Case style flows. The framework
87+
These bypass `enforceAuth` (Web-to-Lead / Web-to-Case flows). The form's own
88+
declaration authorizes the submit: a `publicFormGrant` on its target object
89+
(create and read-back of its new row, nothing else), decided before any permission
90+
set resolves. The submit context's `guest_portal` name admits nothing and needs
91+
no set; a set so named only feeds the grant's result masker. The framework
9092
strips fields outside the form's `sections[].fields[]` list; a
9193
`beforeInsert` hook on the target object should stamp safe defaults
9294
(`status='new'`, `lead_source='web'`, …) and `delete` privileged keys

0 commit comments

Comments
 (0)