Skip to content

Commit 1a23054

Browse files
docs(skills): name the complete public-form opt-in the anonymous form endpoints read (#21650)
Fixes #21567 Clause-②: no ## What The published `objectstack-api` skill's public-form section named two of the three `sharing` keys the anonymous form endpoints read, and the `objectstack-ui` skill's UI-assembly row repeated the omission. An AI following either authors a form that both `GET /api/v1/forms/:slug` and `POST /api/v1/forms/:slug/submit` answer `404 FORM_NOT_FOUND`. Both now state the complete opt-in as the merged rule reads it, plus the walled-posture condition and its remedy. Skill text only (`skills/**`, Tier H). Nothing under `packages/**` is touched; the shipped migration prose in `packages/spec/src/migrations/**` stays as written (triage ruling 5967394091, "Not here"). ### The rule the text now describes (read on `origin/main` 15fe567) - `packages/spec/src/ui/sharing.zod.ts:95` — `enabled: z.boolean().default(false).describe('Enable public sharing')`; `:102` — `allowAnonymous: z.boolean().optional().default(false)`. - `packages/metadata-core/src/anonymous-form-intake.ts:66-68` — the three checks (`s.enabled !== true`, `s.allowAnonymous !== true`, `publicLink` a non-empty string); `:57-59` — `publicFormSlug` folds `/forms/x`, `forms/x` and `x` into one slug. - `packages/rest/src/rest-server.ts:10807` and `:10977` — both doors answer `404 FORM_NOT_FOUND` whenever `resolveFormBySlug` returns null, which it also does for a form the posture withholds (`:10781-10789`). - `packages/metadata-core/src/anonymous-form-intake.ts:212` — a wall is in force only on a posture `postureEnforcesWall` admits (`group` / `isolated`); `:223-225` — the remedy text: declare `tenancy: { enabled: false }` on an object whose rows belong to no organization. - `packages/metadata-protocol/src/runtime-authoring-gate.ts:404` — `PUBLIC_FORM_INTAKE_UNAVAILABLE = 'public-form-intake-unavailable'`, a `warning` the authoring gate raises for a `view` write (save and publish). ### Every `skills/**` site that describes the opt-in Sweep at 15fe567: `git grep -n -i` over `skills/` for `allowAnonymous`, `publicLink`, `anonymous form`, `public form`, `forms/:slug`, `Web-to-Lead`, `web-to-case`, `guest_portal`, `walled`. | Site (line numbers at 15fe567) | Verdict | |:--|:--| | `skills/objectstack-api/SKILL.md:77-78` — "Any `FormView` declared with `sharing.allowAnonymous: true` and a `publicLink` slug is auto-mounted at:" | **changed** — names all three keys, the `enabled` default, and the `404 FORM_NOT_FOUND` answer when one is missing | | `skills/objectstack-api/SKILL.md:94` — "with `allowAnonymous` / `publicLink`" | **changed** — now `enabled` / `allowAnonymous` / `publicLink` | | `skills/objectstack-api/SKILL.md:85-94` — the paragraph after the route block | **changed** — one sentence names the walled-posture condition, the `public-form-intake-unavailable` warning on save and publish, and the `tenancy: { enabled: false }` remedy | | `skills/objectstack-ui/SKILL.md:208` — the UI-assembly table's public-form row | **changed** — the row now shows `sharing: { enabled: true, allowAnonymous: true, publicLink: 'slug' }` | | `skills/objectstack-data/SKILL.md:188` — "public forms" in the list of surfaces a field's conditional rule applies to | already correct — names the surface, not the opt-in | | `skills/objectstack-data/references/_index.md:57`, `skills/objectstack-ui/references/_index.md:48` | already correct — file pointers to `sharing.zod.ts`, no opt-in prose | No other hit; `guest_portal` and `Web-to-Lead` occur only inside the edited section. ### Readings | File | Lines before → after | Tokens before → after (ceiling) | |:--|:--|:--| | `skills/objectstack-api/SKILL.md` | 428 → 434 (+6, the +6 net-line budget) | 4634 → 4758 (6319; headroom 1561) | | `skills/objectstack-ui/SKILL.md` | 310 → 310 | 3854 → 3854 (3856; headroom 2) — bytes 15415 → 15415; the edited row is 179 bytes before and after | | Bundle: sum of every `skills/*/SKILL.md` | 4397 → 4403 | — | Tokens are the ratchet's own convention, `ceil(bytes / 4)`; the gate's lines at 4adec02: "skills/objectstack-api/SKILL.md is 4758 tokens (ceiling 6319; headroom 1561)" and "skills/objectstack-ui/SKILL.md is 3854 tokens (ceiling 3856; headroom 2)". The `objectstack-ui` row paid for its three keys inside the same line: the label "Public / anonymous form" is now "Public form" (the cell still carries `allowAnonymous`), and "/ Web-to-Case" and "Auto-exposed" are dropped (the API skill's section keeps "Web-to-Lead / Web-to-Case"). No other line of that file moved; the sibling card #21537 holds the subforms example at :72-85. `origin/main` was still 15fe567 when this PR was opened, so no merge was owed. ## Tests All 24 gate families that `node scripts/pm/dispatch-gates.mjs --commands` derives for this diff were run locally at `4adec025`, each exit captured before any pipe. `--ran` reconciliation: "24 derived famil(ies) accounted for — 24 run, 0 NOT-MEASURED (a DERIVED zero — all 24 recorded an exit code and none of them is 3)". Verdict lines: - `node scripts/check-skills-token-ratchet.mjs` exit 0 — "✓ check-skills-token-ratchet: 54 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted."; `--self-test` exit 0 — "65 cases pass". - `node scripts/check-doc-route-spelling.mjs --advisory` exit 0 — "✓ route-spelling guard (advisory): population clean — every shape-matched literal spells its ledger row."; `--self-test` exit 0. - `pnpm check:skill-identifier-liveness` exit 0 — "Leg 1: 457 citation(s) over 53 published file(s) checked against 118365 implementation word tokens (0 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)." - `pnpm --filter @objectstack/spec run check:skill-docs` exit 0 — "✅ Skill docs in sync" (after `pnpm --filter @objectstack/spec build` under the verify lock, `VERDICT command-exit 0`). - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` — first run exit 3, `PREREQUISITE NOT MET` (`@objectstack/formula` and `@objectstack/lint` not built; "Nothing was measured"); after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the lock, exit 0. - `pnpm check:corpus-claim-drift`, `check:doc-authoring`, `check:role-word`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:nul-bytes`, `check:agent-test-spelling`, `check:cross-package-test-inputs`, `check:driver-memory-census`, `check:gitlink-declared`, `check:pm-governed-merges`, `check:refd-timer-probe`, `check:watch-hint-literal`, `node scripts/check-ci-filter-parity.mjs`, `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`), `node scripts/check-comment-mask-corpus.mjs` — all exit 0. - Control-character self-scan over both files (the `grep -naP` spelling from the agent rules): clean. Not run locally, CI owns them: the repo-wide `pnpm lint`, the type-check lanes and the package test suites — no package source changed. ## 维护者速读(草稿) **改了什么:** 两份对外发布的技能页(`objectstack-api`、`objectstack-ui`)里描述「匿名公开表单」开关的句子,原来只写了 `allowAnonymous: true` 和 `publicLink` 两个键。现在补齐第三个键 `enabled: true`(schema 默认是 `false`),写明三者缺一则两个匿名端点都答 `404 FORM_NOT_FOUND`,并加一句:在 `group` / `isolated` 这类带租户墙的部署姿态下,目标对象若按组织列隔离,表单同样不对外提供,保存与发布时会收到 `public-form-intake-unavailable` 警告,补救是对不归属任何组织的对象声明 `tenancy: { enabled: false }`。 **为什么改:** 运行时规则已在 PR #21566 落地到 `main`:三个键齐全才服务。技能文本是 AI 写元数据的直接依据,按旧文本写出来的表单会被两个端点同时拒绝,而作者不知道为什么。本 PR 让文本与已合并的规则、与 `SharingConfigSchema` 的默认值完全一致。 **风险与代价(含回滚):** 纯文本改动,不碰 `packages/**`,无 changeset(`skip-changeset`)。`objectstack-ui/SKILL.md` 的 token 棘轮只剩 2 的余量,所以那一行用同一行内的删字付账(行标签「Public / anonymous form」改为「Public form」,去掉「/ Web-to-Case」与「Auto-exposed」);`objectstack-api/SKILL.md` 净增 6 行,在 PM 给的 +6 预算内。回滚:revert 本 PR 即可,没有派生产物。 **席位意见:** **你要做的:** 这是 Tier H 受管面(`skills/**`),需要你的 APPROVED review;批准后由席位落地。请顺带看一眼 `objectstack-ui` 那一行的措辞取舍是否接受(标签缩短、去掉 Web-to-Case)。 ## Acceptance notes - Noted, not filed (code comments, not authoring guidance; carrier: none): `examples/app-showcase/src/ui/views/inquiry.view.ts:10` and `examples/app-showcase/src/data/objects/inquiry.object.ts:10`, and the header comments of `packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts:5` and `showcase-public-form-withdrawal.dogfood.test.ts:7`, narrate the opt-in with the two-key wording; the example view itself declares all three keys (`inquiry.view.ts:70-73`), as does `examples/app-crm/src/views/lead.view.ts:124-126`. - `CHANGELOG.md:607` carries the pre-rule wording — release-owned history, never edited in a code PR. - `content/docs/ui/forms.mdx:23` and `:234` already name `sharing.enabled: true` + `sharing.allowAnonymous: true` + `publicLink`; nothing owed there. - The slug-normalisation fact (`/forms/x`, `forms/x` and `x` are one slug) is not in the skill text — it did not fit the +6 line budget; both example apps spell `publicLink: '/forms/contact-us'`, which the UI row's `publicLink: 'slug'` placeholder admits. - The commit's author/committer name is `objectstack-fleet[bot]` (set with `-c user.name` on this one commit; the container default is `Claude`); the trailer pair is the model-free form the pre-push hook accepted. --- _Generated by [Claude Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_ Co-authored-by: objectstack-fleet[bot] <noreply@anthropic.com>
1 parent eea82af commit 1a23054

2 files changed

Lines changed: 11 additions & 5 deletions

File tree

‎skills/objectstack-api/SKILL.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ are **client** contracts — nothing in a stack declares them.
7474

7575
### Public (anonymous) Form Endpoints
7676

77-
Any `FormView` declared with `sharing.allowAnonymous: true` and a
78-
`publicLink` slug is auto-mounted at:
77+
A `FormView` is served by the anonymous form endpoints only when its
78+
`sharing` declares all three: `enabled: true` (the schema default is
79+
`false`), `allowAnonymous: true` and a `publicLink` slug. Miss any one and
80+
both endpoints answer `404 FORM_NOT_FOUND`. The form is then mounted at:
7981

8082
```
8183
GET /api/v1/forms/:slug # returns form spec + restricted objectSchema
@@ -88,10 +90,14 @@ are intended for Web-to-Lead / Web-to-Case style flows. The framework
8890
strips fields outside the form's `sections[].fields[]` list; a
8991
`beforeInsert` hook on the target object should stamp safe defaults
9092
(`status='new'`, `lead_source='web'`, …) and `delete` privileged keys
91-
(`owner`, `internal_notes`, …). For the full contract, read
93+
(`owner`, `internal_notes`, …). On a walled posture (`group`/`isolated`), a
94+
form on an object walled by an organization column is withheld too — both
95+
endpoints `404`, and save / publish warn `public-form-intake-unavailable`;
96+
declare `tenancy: { enabled: false }` on an object whose rows belong to no
97+
organization to offer it again. For the full contract, read
9298
`node_modules/@objectstack/spec/src/ui/view.zod.ts` (`FormViewSchema`) and
9399
`node_modules/@objectstack/spec/src/ui/sharing.zod.ts` (`SharingConfigSchema`
94-
with `allowAnonymous` / `publicLink`).
100+
with `enabled` / `allowAnonymous` / `publicLink`).
95101

96102
---
97103

‎skills/objectstack-ui/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ Use this CRM-style structure as the canonical UI assembly reference:
205205
| UI Surface | Typical Location | Pattern to Follow |
206206
|:--|:--|:--|
207207
| Multi-view object UI | `src/views/*.view.ts` | Define default `list` + `form`, then named `listViews` / `formViews` for scenarios |
208-
| **Public / anonymous form** | `src/views/*.view.ts` (formView with `sharing.allowAnonymous: true`) | Web-to-Lead / Web-to-Case. Auto-exposed at `GET/POST /api/v1/forms/:slug` |
208+
| **Public form** | `src/views/*.view.ts` (formView with `sharing: { enabled: true, allowAnonymous: true, publicLink: 'slug' }`) | Web-to-Lead at `GET/POST /api/v1/forms/:slug` |
209209
| App navigation | `src/apps/*.app.ts` | Use grouped nav trees, `viewName` shortcuts, and `requiresObject` for capability-aware visibility |
210210
| **Analytics dataset** | `src/datasets/*.dataset.ts` | One per object you want reportable — dashboards and reports bind a **declared** dataset by name, so an object without one has no analytics face |
211211
| Dashboards | `src/dashboards/*.dashboard.ts` | Combine KPI + chart + table widgets with shared `dateRange` and `globalFilters` |

0 commit comments

Comments
 (0)