Skip to content

Commit cfc55af

Browse files
committed
docs: what withdraws a public form, and what does not
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6
1 parent 1d6bdd5 commit cfc55af

2 files changed

Lines changed: 17 additions & 4 deletions

File tree

‎.changeset/public-form-withdrawal-kill-switch.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
'@objectstack/metadata-core': minor
55
---
66

7-
A public form's intake withdrawal at any metadata layer now holds: layering can only narrow anonymous intake, never re-open it
7+
A public form's explicit intake withdrawal at any metadata layer now holds: layering can only narrow anonymous intake, never re-open it
88

99
Clause-②: yes (widening)
1010

11-
- **Anonymous form doors.** `GET /forms/:slug` and `POST /forms/:slug/submit` serve a public form only when no metadata layer they read withdraws it. A form withdrawn at one layer (`sharing.enabled: false` or `sharing.allowAnonymous: false`, or its public link named by a sharing that does not open it) answers `404 FORM_NOT_FOUND` on both doors and creates no record, whatever another layer says. A form open at every layer is served as before, and a form only an organization carries is still served there.
12-
- **Organization-scoped saves.** A `view` save that would re-open a public form another layer withdrew is refused with `403 NOT_OVERRIDABLE`, and the message names the remedy: publish the form from its environment-wide definition. An organization-scoped edit that keeps the form withdrawn, or that leaves its intake as it is, is still accepted.
13-
- **`@objectstack/metadata-core`** exports the shared judgement, `anonymousFormIntakeWithdrawnIn` and `anonymousFormWithdrawnSlugs` (new, additive public exports), which both the doors and the save path read.
11+
- **What counts as a withdrawal.** A withdrawal keeps the form's `publicLink` and sets `sharing.enabled: false` or `sharing.allowAnonymous: false`. It closes the same form only: the same view, at the same place in it (`form`, the same `formViews` entry, or its `config`), across its metadata layers. A different view that uses the same public slug is a different form, and the two never close each other. Removing the `sharing` block, clearing or changing the `publicLink`, or deleting the view at one layer is not a withdrawal. A sharing that names no public link withdraws nothing, whether it is a raw body or a schema-parsed one whose `enabled`/`allowAnonymous` defaults read `false`.
12+
- **Anonymous form doors.** `GET /forms/:slug` and `POST /forms/:slug/submit` serve a form only when no layer they read withdraws it. A withdrawn form answers `404 FORM_NOT_FOUND` on both doors and creates no record, whatever another layer says. A form that is open at every layer is served as before. A form that only an organization carries is still served there.
13+
- **Behaviour change.** Before this release, an organization overlay that published a form the environment-wide (package) definition withdrew was honoured: the doors served the organization's copy. That is reversed on purpose. The environment-wide withdrawal now wins, and the organization's copy cannot re-open the form.
14+
- **Organization-scoped saves.** A `view` save in the organization the doors read is refused with `403 NOT_OVERRIDABLE` if it would leave open a form that the environment-wide definition withdraws. This holds even when the organization's copy was already open before the withdrawal, so re-saving that overlay is refused too. A container-shaped body (`formViews`, `form`) is judged the way the list read expands it. The message names the remedies: save the overlay withdrawn, or publish the form from its environment-wide definition. An organization-scoped save that keeps the form withdrawn is still accepted. Rollback and commit-revert restores are not gated by this judgement yet. The doors still keep such a form closed.
15+
- **`@objectstack/metadata-core`** exports the shared judgement `anonymousFormIntakeWithdrawnIn` (a new, additive public export). Both the doors and the save path read it.

‎content/docs/ui/public-data-collection.mdx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ System-managed anchors (`owner_id`, `organization_id`, audit columns, `id`) are
5252

5353
Set `sharingModel: 'private'` on the object so submissions are staff-scoped after creation. The public path only ever **inserts**; it never lists.
5454

55+
### 4. Withdraw a public form
56+
57+
To stop taking submissions, keep the form's `publicLink` and clear a switch: `enabled: false` or `allowAnonymous: false`. Both anonymous endpoints then answer `404 FORM_NOT_FOUND`, and nothing is created.
58+
59+
A withdrawal is a kill switch across metadata layers. If the environment-wide definition withdraws the form, an organization's copy of the same view cannot open it again: the endpoints keep answering not found, and an organization-scoped save that would leave the form open is refused with `403 NOT_OVERRIDABLE`. To publish the form again, save it environment-wide with both switches on. An organization's copy can always withdraw the form for itself.
60+
61+
Only an explicit withdrawal closes the form:
62+
63+
- **It closes the same form only.** A withdrawal applies to the view that carries it, at the same place in that view (`form`, the same `formViews` entry, or the view's own `config`), across its layers. A different view that uses the same public link (for example, another app's "contact us" form) is a separate form. It neither closes this one nor is closed by it.
64+
- **Removing is not withdrawing.** Removing the `sharing` block, clearing or changing the `publicLink`, or deleting the view at one layer does not withdraw the form at the other layers. A form that only an organization publishes stays open there. To close a form for good, keep the link and set `enabled: false` or `allowAnonymous: false`.
65+
5566
## Why
5667

5768
Authorization is **derived from the declaration**, not configured separately — so the grant can't drift wider than the form. There is no standing "anonymous can write to this object" rule to misconfigure: the only thing the public can do is create one record through one whitelisted form. This is the difference from Airtable, where interfaces can't be shared publicly at all (only forms can) — here the same FormView metadata renders both internally (authed) and publicly (anonymous) through one renderer.

0 commit comments

Comments
 (0)