Repository navigation
Commit e2b40a2
fix(rest)!: an anonymous public-form submission carries no file (#25682)
Fixes #25622
Fixes #25572
Clause-②: no (narrowing)
Metadata-protocol: none
## What this changes
Ruling C on #25572 (the maintainer, verbatim: 「25619 C 25495 B」; the
ruling record is on PR #25619): **an anonymous public-form submission
carries no file.**
- **The submit door** (`@objectstack/rest`). `POST /forms/:slug/submit`
refuses a value in a field whose declared type is a file type
(`FILE_REFERENCE_TYPES`: `file`, `image`, `avatar`, `video`, `audio`).
It names the field and refuses before anything is written. The answer is
the door's existing envelope, `400 VALIDATION_FAILED`, with one
`fields[]` entry per file field: the ADR-0114 field code `invalid_value`
and the field's label. If the same submission also leaves a `required`
field empty, both are reported in one envelope. An empty or absent file
field submits as before. Only the fields the form declares are judged,
so the refusal names nothing that `GET /forms/:slug` does not already
publish. No error code is added.
- **The file-reference claim path** (`@objectstack/service-storage`) now
looks a referenced file up within the organization the write acts in. It
uses the channel that already stamps a copied file for that
organization, so the driver's tenant scope for the posture in force
decides what the lookup sees. A referenced file id outside that scope is
kept as written and is neither claimed nor copied, exactly as an id the
platform does not manage. The module writes no organization comparison
of its own. A write that carries no organization, and the `single`
posture, look files up as before.
Not in this PR: the publish-time refusal of a guest-facing form that
declares a file field (#25650, `domain:spec`), and any guest upload door
(ruled out by C).
## Declarations
- `Clause-②: no (narrowing)`. The door refuses a request shape it
accepted, and the claim path reaches fewer files. Nothing is widened.
Both claims carry `Clause-②: no` with no arm; the dispatch asked for the
arm to be judged, and AGENTS.md's Post-Task Checklist step 3 names
`(narrowing)` for this case, so the arm is added.
- `Metadata-protocol: none`. Nothing a metadata author writes changes.
- One `patch` changeset on both packages, `fix(rest)!:`, with the
BREAKING section, the migration line and the ADR-0087 disposition
`not-required (no-migration-prescription)`.
`check-adr-0087-registration` reads it as
`[BREAKING+bang+clause-②-narrowing]` and passes.
## Composition with #25641
#25641 (`domain:spec`, ADR-0138 D5) also edits
`packages/rest/src/form-routes.ts`, in `resolveFormBySlug`, the submit
door's context literal and the imports. This PR's edit sits in the
submit door's `required` judgement, below both hunks, with unchanged
lines between them. A local `git merge-tree --write-tree` of this branch
with #25641's head `08b020c9d3` exits 0 with no conflicted path
(`form-routes.ts` is not a merge-driver path, so the local answer is
GitHub's). Whichever PR lands second merges `main`.
## One existing pin changed, with its subject kept
`public-form-intake-availability.test.ts`, "single posture, walled
object — accepted, and the object is not even read for the predicate",
asserted that the whole submit read only `view` metadata. The submit now
reads the object's declarations once whenever a declared field carries a
value, as the `required` judgement already did for forms with required
entries. The pin now asserts `['view', 'object']`: exactly one object
read, by the field judgement. A predicate that read the object on this
posture would be a second read and would still turn it red.
## Tests
All on head `80aab79ea2`, each package's exit code captured separately:
- `pnpm --filter @objectstack/rest test`: Test Files 321 passed (321),
Tests 6417 passed, 326 skipped. `pnpm --filter @objectstack/rest
typecheck`: exit 0. The test-layer program (`tsconfig.test.json`,
counted with `--listFiles`) compiles both test files this PR touches.
- `pnpm --filter @objectstack/service-storage test`: Test Files 63
passed (63), Tests 1137 passed. `typecheck`: exit 0, and the new test
file is in its test-layer program.
- New suites: `packages/rest/src/public-form-file-field.test.ts` has 11
tests: the refusal by value shape (a file id, an external URL, an inline
file value), a multi-valued field, two fields at once, one envelope with
`required`, four empty/absent controls and the undeclared-field
boundary.
`packages/services/service-storage/src/file-reference-organization-scope.test.ts`
has 6 tests on a real `ObjectQL` and `SqlDriver` under the `isolated`
posture with the hooks installed as the plugin installs them: insert and
update in one organization, two controls in the writing organization
(claim, and a copy stamped for it), and the `single` posture. The
refusal pins assert `code` and `status` (`VALIDATION_FAILED`, `400`)
plus each entry's field, code and label. Message wording is not pinned;
each message is only required to name its label.
- `eslint --no-inline-config` with the root config over
`packages/rest/src` and `packages/services/service-storage/src`: exit 0.
- Full dogfood battery (owed for the `(narrowing)` arm), `pnpm --filter
@objectstack/dogfood test` under the shared lock with no shard and no
path: it was running when this PR opened, and its counts are in the
`os-dev-report` comment on #25622.
## Proof
Each leg mutated through `node scripts/ablation-replace.mjs`, which
verifies on disk that the anchor hit, the blob changed and the restore
matched the HEAD blob. Both subjects resolve through relative `src`
imports in their suites, so no `dist/` is involved.
- **The door.** With the file-type judgement replaced by `continue`, the
6 refusal pins of `public-form-file-field.test.ts` redden (the door
answers `201`), and its 5 control and boundary pins hold. Restored: the
blob `e6855e87be17` equals HEAD's and `git diff HEAD` is empty.
- **The claim path, the lookup before the write.** With this change's
context for that lookup undone, exactly 1 of the 6 organization-scope
pins reddens (the copy pin). Restored: the blob equals HEAD's.
- **The claim path, the claim's own lookup.** With this change's context
undone there, exactly 2 of the 6 redden (the insert and update claim
pins). Restored: the blob equals HEAD's.
- The two controls and the `single`-posture pin held in every leg.
## Gates
- Derived on `80aab79ea2` by `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (no paths; the change set
comes from the merge base): 73 commands, each exit code captured before
any pipe. 72 exited 0 in sequence. `pnpm check:type-check-debt` first
exited 3 (its own closure build never got a turn on the shared verify
lock, so nothing was measured), and its rerun under the lock exited 0
with no entry above its recorded number. `--ran`: 73 derived, 73 run, 0
NOT-MEASURED, 0 UNRUN, exit 0. `check-adr-0087-registration` reads the
changeset as `[BREAKING+bang+clause-②-narrowing]` with an accepted
disposition, and `check-changeset-no-major` reports no major bump.
- `pnpm lint` (the root `eslint . --no-inline-config`) is CI's run.
Locally it was run over the two touched packages only (above).
- The tree note the derivation prints: `80aab79ea2` merged `origin/main`
at `9dac9eb8d6`. Since then `main` has gained refactor moves in
`objectql`, `driver-sql` and `metadata-protocol` and the template pins,
and one derivation input (`scripts/sync-template-versions.mjs`) changed
upstream. The derived list did not change between the two merges. CI
reads the PR's merge ref.
- CI check-runs had not concluded when this PR opened; convergence is
read by the seat.
## Acceptance notes
- `GET /forms/:slug` still serves a declared file field in the form's
sections, so a renderer may show an upload control that a visitor cannot
use. That belongs to the publish half (#25650), which refuses such a
form at publish. It is not changed here.
- The refusal message is inline English beside the `required` message,
which the door renders from the built-in catalog without a locale. No
catalog key is added.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e44a1ff commit e2b40a2
6 files changed
Lines changed: 529 additions & 16 deletions
File tree
- .changeset
- packages
- rest/src
- services/service-storage/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 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
80 | 81 | | |
81 | 82 | | |
82 | 83 | | |
| |||
595 | 596 | | |
596 | 597 | | |
597 | 598 | | |
598 | | - | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
599 | 626 | | |
600 | 627 | | |
601 | | - | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
602 | 633 | | |
603 | 634 | | |
604 | 635 | | |
| |||
608 | 639 | | |
609 | 640 | | |
610 | 641 | | |
611 | | - | |
612 | | - | |
613 | | - | |
| 642 | + | |
| 643 | + | |
614 | 644 | | |
615 | 645 | | |
616 | 646 | | |
617 | 647 | | |
618 | 648 | | |
619 | 649 | | |
620 | | - | |
621 | | - | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
622 | 665 | | |
623 | 666 | | |
624 | 667 | | |
| |||
| 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 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
0 commit comments