Repository navigation
Commit 3c7785d
fix(rest,metadata-protocol): a public form's intake withdrawal at any metadata layer holds; layering can only narrow intake (#21864)
Fixes #21835
Clause-②: yes (widening)
Fixes a regression introduced after 17.6.0 (with #21420); it should land
before 17.7.0 is cut.
## What
Per the rulings recorded on #21835: a public form's withdrawal is a kill
switch, layering can only narrow anonymous intake, a withdrawal closes
the **same form** only, and only an **explicit** withdrawal counts.
- **Anonymous doors (`GET /forms/:slug`, `POST /forms/:slug/submit`).**
Both use one resolver and judge by the name of the view item they serve.
When an organization is resolved, the env-wide view list beneath it is
read as well. A form is served only when the env-wide item of the same
name does not explicitly withdraw a form in the same slot (nested form,
the same `formViews` key, or the flattened config) or with the same
slug. Other views that share the public slug never close each other.
- **What counts as a withdrawal.** A sharing that keeps its `publicLink`
and sets `enabled: false` or `allowAnonymous: false`. Only an explicit
false counts. Not a withdrawal: an absent switch, a sharing with no link
(raw, or schema-parsed), a cleared link, a removed sharing block, or no
body of the view at that layer. The public data collection docs page has
a "Withdraw a public form" section with these rules.
- **Write door (save and publish).** An org-scoped `view` save or draft
promotion in the organization the doors read is refused with `403
NOT_OVERRIDABLE` when it would leave open a form the env-wide definition
explicitly withdraws. It judges by the stored row: the body is compared
with the env-wide body of the row it is keyed by (the active env-wide
row, else the package artifact), matched by slot or by slug. So renamed
`formViews` keys, `form.name`, slot moves and listViews collision
renames are the same form. It is also judged against the env-wide view
list the way the doors read it, with container bodies expanded.
Re-saving an overlay that was open before the withdrawal is refused. The
message names both remedies.
- **Package-shipped forms.** A package artifact is part of the env-wide
definition, not a separate layer. A package artifact parsed by the stack
schema (strict `defineStack`, the default) carries the schema's default
`enabled: false`, so a shipped form that keeps its link without
switching `enabled` on is an explicit withdrawal and fails closed. An
artifact loaded without that parse (`defineStack(..., { strict: false
})` or a hand-built manifest) is judged as written: a switch it omits is
absent, which is not a withdrawal. The env-wide definition is the
administrator's switch, so an env-wide save may open a form the package
ships closed.
- **Known limit: packages and names.** A withdrawal of a view name
closes that name in every package: when two packages ship a view of the
same name, one package's withdrawal also closes the other package's form
of that name. It may over-close, never under-close. Per-package
precision is tracked in #21934. A publish judges the draft it promotes
under the same package key (the stated one, else the resolved draft
row's own), so with two packages holding a draft of the same view in one
organization, each draft is judged on its own publish.
- **Intentional reversal.** The earlier behaviour in which an
organization overlay re-published a form the package had withdrawn is
reversed. A form with no env-wide word on it stays
organization-publishable (#21420), and the #21473 anchors and #21566
field allowlist are unchanged.
- **Public surface:** `@objectstack/metadata-core` adds one export,
`anonymousFormIntakeWithdrawnIn` (`minor`). `@objectstack/rest` and
`@objectstack/metadata-protocol` are `patch`.
- **Known limit (ruled to stay as is).** The doors match by served item
name, and the write door runs only on an org-scoped save or publish. An
organization overlay stored before the env-wide withdrawal, or restored
by rollback or commit revert, can still be served if it keeps the form
open under a different key or slot than the env-wide definition.
Withdrawing the form in that overlay closes it. Stated in the changeset
and the docs.
## Tests
The first bullet is round 6, the second round 5, the third round 4; the
bullets after them were measured at `e8778acb96` (round 2):
- Round 6 at `7882eef683` (merged origin/main `9dce635337`, merge commit
`46d08189a7`): metadata-core 18 files, 411 passed; metadata-protocol 216
files (3 skipped), 27940 passed, 19 skipped; rest 260 files, 4912
passed, 326 skipped; objectql 375 files, 7469 passed (suites at
`4d5f6c4e61`; the later commits touch docs, the changeset and three
ported dogfood files only). Typecheck green for metadata-core,
metadata-protocol, rest and objectql, test layers included. 97 of 97
derived gates green at `7882eef683`, reconciled with `dispatch-gates
--ran`; `dispatch-gates --self-test` 1976 cases pass. The cross-package
skip of round 5 is removed per the ruling, so a withdrawal of a view
name closes it in every package again. Pins: another package's
withdrawal of the same name closes this package's form too
(metadata-core and the doors); with two packages shipping the same view
name, a row-anchored rename by a package-bound org save is refused
(metadata-protocol), with a withdrawn-save control. Ablation: the two
edited sources set back to their round-5 blobs and rebuilt, markers
proved in `dist/`: 1 red in each of metadata-protocol, metadata-core and
rest; restored to HEAD (`git diff HEAD` empty), rebuilt, markers proved
absent.
- Round 5 at `d8657b5c19`: metadata-core 18 files, 411 passed;
metadata-protocol 216 files (3 skipped), 27938 passed, 19 skipped; rest
260 files, 4911 passed, 326 skipped; objectql 374 files, 7464 passed.
Typecheck green for metadata-core, metadata-protocol, rest and objectql,
test layers included. 97 of 97 derived gates green, reconciled with
`dispatch-gates --ran`. New pins: two packages' drafts of one view in
one organization are each judged on their own publish; one package's
withdrawal of a name closes its own form (metadata-core and both doors;
the cross-package half was inverted in round 6). Ablation: removing the
package key from the publish gate's draft read turned the two-package
pin red, and removing the package comparison turned the cross-package
pin red; both restored to HEAD (`git diff HEAD` empty).
- Round 4 at `79b847042d` (targeted): metadata-core
`anonymous-form-intake.test.ts` 39/39, metadata-protocol
`protocol.org-scoped-write-refused.test.ts` 41/41, rest
`public-form-withdrawal` + `public-form-intake-availability` 43/43.
Typecheck green for metadata-core and metadata-protocol. Docs and
changeset gates green. New pins: a package parsed `false` is a
withdrawal; an env-wide save opens a package-closed form.
- `@objectstack/metadata-core`: 18 files, 394 passed.
- `@objectstack/rest`: 260 files, 4906 passed, 326 skipped.
- `@objectstack/metadata-protocol`: 214 files (3 skipped), 27752 passed,
19 skipped.
- Typecheck green for all three and dogfood.
- Dogfood (real showcase boot): the layered-withdrawal suite 5/5
(including the re-save refusal) and the five sibling public-form suites
20/20.
- Coverage: two views sharing a slug do not close each other (one read,
with and without an organization, and across layers); a cleared link is
not a withdrawal; a parsed link-less sharing is not a withdrawal;
re-saving an already-open overlay is refused; a container-shaped save is
judged after expansion.
- Ablation (source set back to the base blobs, packages rebuilt): 3 / 4
/ 4 tests red across metadata-core / rest / metadata-protocol; restored
to HEAD.
- Gates: 92 of 95 derived run green; `check:skill-examples`,
`check:dual-build-cjs-loads` and `check:type-check-debt` are NOT
MEASURED locally (workspace-wide prerequisites) and left to CI.
## Acceptance notes
- The known limit above (an overlay stored before the withdrawal, or
restored by rollback or revert, with its form under a different key or
slot) is accepted per the ruling on #21835. No provenance or new
protocol query was added.
- Two installed apps publishing the same slug is a separate concern
(slug collision), out of scope here.
- A package artifact loaded without the stack schema's parse (`strict:
false`, a hand-built manifest) is judged as written; giving every load
path the schema's sharing defaults is left as a possible follow-up (see
the round 5 report on #21835).
- An objectql test double now answers the publish gate's draft-row read
(`protocol-publish-package-drafts.test.ts`); that is test-only.
- Maintainer ruling, 2026-10-06: 「撤掉跨包那一改,合并」. The cross-package skip is
removed; per-package precision is tracked in #21934.
- This branch carries three dogfood files ported unchanged from #21935
(`packages/qa/dogfood/test/per-file-cwd.setup.ts`,
`packages/qa/dogfood/test/per-file-cwd.global-setup.ts`,
`packages/qa/dogfood/vitest.config.ts`) so the dispatch-gates self-test
is green here; they merge away once #21935 lands.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f57627b commit 3c7785d
11 files changed
Lines changed: 1069 additions & 26 deletions
File tree
- .changeset
- content/docs/ui
- packages
- metadata-core/src
- metadata-protocol/src
- objectql/src
- qa/dogfood/test
- rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
55 | 75 | | |
56 | 76 | | |
57 | 77 | | |
| |||
Lines changed: 131 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
184 | 185 | | |
185 | 186 | | |
186 | 187 | | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
0 commit comments