Repository navigation
Commit 59e6463
fix(service-storage)!: a stored file's uploader is the user who created it, as an attachment's and a comment's author are (#30170)
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: updated here —
`content/docs/permissions/attachments-access.mdx` now states that a
`sys_file` created through the data API records the signed-in user as
`owner_id`, and that such a create with no signed-in user is refused. No
published skill teaches writing `sys_file.owner_id` (`git grep` over
`skills/` finds none).
## 维护者速读
- **改了什么**:非系统上下文创建 `sys_file` 时,`owner_id` 一律记为当前会话用户(覆盖请求里带的值),与附件
`uploaded_by`、评论 `author_id` 同一规则;会话没有登录用户的创建被拒绝(403
`RECORD_NOT_ACCESSIBLE`)。
- **为什么改**:一个文件的上传者就是创建它的人;出处只在创建时记录一次,之后只读(更新侧已是只读)。
- **风险与回滚**:收窄(BREAKING)。经数据接口创建 `sys_file` 时请求里的 `owner_id`
不再生效;上传接口、系统上下文写入、无会话的裸内核调用都不变;控制台不经数据接口创建 `sys_file`,无需改动。回滚即 revert 本
PR。
-
**先看**:`packages/services/service-storage/src/sys-file-storage-owned-columns.ts`(`recordUploader`
与钩子里 `if (ctx?.session)`
那一行)、`packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts`(数据接口上的新钉)、`.changeset/sys-file-uploader-is-its-creator.md`。
- **两分钟验证**:`pnpm --filter @objectstack/service-storage exec vitest run
src/sys-file-storage-owned-columns.test.ts
src/sys-file-storage-owned-columns.integration.test.ts`,预期 `Test Files 2
passed (2)`、`Tests 38 passed (38)`。
- **需要你做的**:无,席位落地。
## What changes
A stored file's uploader is the user who created it, as an attachment's
and a comment's author are.
- **Create.** A non-system create of `sys_file` records the session's
user as `owner_id`, over whatever the request carries: the stamp the
attachment create hook (`sys_attachment.uploaded_by`) and the comment
create hook (`sys_comment.author_id`) already apply. It lives in the
existing `sys_file` create guard
(`installSysFileStorageOwnedColumnsGuard`), beside the storage-owned
column strip, so `sys_file` keeps one `beforeInsert` registration.
- **Refused.** A non-system create whose session names no signed-in user
is refused with 403 `RECORD_NOT_ACCESSIBLE`, the code and status the
attachment and comment hooks answer their own no-user creates with.
- **Unchanged.** The system context keeps the `owner_id` it supplies:
the upload doors (presigned, chunked, complete) record the session's
user themselves under it, as do imports and migrations. A call with no
session at all (a bare kernel) keeps its value too, which is the
attachment hook's carve-out. Updates are unchanged: `owner_id` is
already read-only after creation, so an update drops a non-system
caller's value and reports it in `droppedFields`.
- **One difference from the attachment hook, by design.** The attachment
hook passes a context-less call entirely. Here the carve-out covers the
uploader only: the storage-owned and file-reference columns are still
dropped from a context-less create, because that strip judges every
write that is not `isSystem` (as the update strip does) and this rule
does not loosen it.
- **Silent, as the attachment and comment stamps are.** The engine
reports `droppedFields` / `onFieldsDropped` only for strips it runs
itself, it runs none on a `sys_` object's create, and the hook contract
carries no dropped-field report: its one caller-facing member,
`ctx.notice`, is a free-text save notice, and neither sibling stamp uses
it. So the overwrite is not reported, exactly as `uploaded_by` and
`author_id` are not.
- **Docs.** `content/docs/permissions/attachments-access.mdx`, and the
`owner_id` comment in `objects/system-file.object.ts`.
## Tests
- **Unit, at the guard** (new `sys-file-storage-owned-columns.test.ts`,
8 cases): the session's user is recorded over another user's id and when
none is named; three no-user sessions (a service principal, an
organization only, an empty user id) are refused 403
`RECORD_NOT_ACCESSIBLE` with nothing logged; a system create keeps its
value; a bare-kernel call keeps its value and still loses the
storage-owned columns; the stamp sits beside the strip.
- **Integration, real engine over sqlite**
(`sys-file-storage-owned-columns.integration.test.ts`, 6 new cases):
single and batch creates record the caller; a no-user create is refused
and writes no row; the upload doors (presign, complete) still record the
uploading user; a system create and a system update keep the uploader
they write; an update leaves the uploader the create recorded.
- **Dogfood, at the data door**
(`attachments-permission-matrix.dogfood.test.ts`, uploader block): an
administrator's `POST /data/sys_file` naming another user answers 201
and records the administrator; control, a system create keeps the
uploader it supplies. The block's existing pins stay the update-side and
upload-door controls: an administrator's PATCH of `owner_id` answers 200
with `owner_id` in `droppedFields`, and (e) the upload door stamps the
uploading member.
- **Ablation, one shot** (fix committed first; mutation through
`scripts/ablation-replace.mjs`, which restored on exit): the stamp line
replaced by a no-op, `service-storage` rebuilt,
`ablation-dist-preflight` proved the line absent from `dist/`. Red as
predicted: 5 of 38 in the two guard files (the three unit and two
integration cases that read the stamp) and the new dogfood pin; every
control stayed green. Restore leg: blob equal to HEAD, `git diff HEAD`
empty, rebuilt, preflight proved the mutation absent and the line
present in `dist/`, both runs green again.
## Acceptance notes
- `sys_file.owner_id` has no update-time ownership-transfer path: since
the rule that made a row's uploader read-only after creation, the update
strip drops a non-system caller's `owner_id`, the holder of the
ownership-transfer grant included (measured on `origin/main` 917f024:
PATCH 200, `droppedFields` names `owner_id`, stored value unchanged).
This PR keeps that; only the system context records a different
uploader.
- No UI or service creates `sys_file` through the data API (`git grep`
over `ui/` and `packages/` finds the one writer,
`StorageMetadataStore.createFile`, under the system context), so no
caller changes.
## Verification
Readings on `3aa77b9a7f` (`origin/main` 232d3bb merged in; it touches
no package) unless stated. The head `89c379ba6e` adds one comment-only
commit (the guard's docblock on `ctx.notice`). At that head, the guard
tests read `Test Files 2 passed (2)`, `Tests 38 passed (38)`, and the 49
gate families derived for the changed file all exit 0 (`dispatch-gates
--ran`: `49 run, 0 NOT-MEASURED`).
<details><summary>service-storage: full suite and typecheck</summary>
- `pnpm --filter @objectstack/service-storage exec vitest run
--maxWorkers=2` under `scripts/pm/os-verify-lock.sh`: `Test Files 72
passed (72)`, `Tests 1341 passed (1341)`, lock `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/service-storage typecheck`: exit 0,
including `check:test-typecheck: OK`; `tsc --listFiles` on
`tsconfig.test.json` lists the guard and both test files.
- `pnpm --filter @objectstack/dogfood typecheck`: exit 0; its program
lists `attachments-permission-matrix.dogfood.test.ts`.
</details>
<details><summary>Derived gates (dispatch-gates --commands, 94
families)</summary>
- 91 exit 0, each exit code captured before any pipe.
- 3 NOT MEASURED, each answered PREREQUISITE NOT MET (exit 3) and
measured nothing: `check:skill-examples` (no built `client-react`
declarations in this worktree), `check:dual-build-cjs-loads` (seven
packages outside the dogfood build closure have no `dist/`),
`check:type-check-debt` (its closure build never got a turn on the
shared verify lock). CI's `Lint & Repo Gates` runs all three on a full
build.
- `dispatch-gates --ran`: `94 derived famil(ies) accounted for — 91 run,
3 NOT-MEASURED`, exit 0.
</details>
<details><summary>Dogfood battery</summary>
- Full `pnpm --filter @objectstack/dogfood test` on 3aa77b9 under
os-verify-lock: VERDICT command-exit 0; Test Files 417 passed | 3
skipped (420); Tests 3464 passed | 28 skipped (3492)
</details>
<!-- signed: security#1 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9a53fc9 commit 59e6463
7 files changed
Lines changed: 281 additions & 10 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- qa/dogfood/test
- services/service-storage/src
- objects
| 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 | |
|---|---|---|---|
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
182 | | - | |
183 | | - | |
184 | | - | |
185 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
186 | 190 | | |
187 | 191 | | |
188 | 192 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
716 | 716 | | |
717 | 717 | | |
718 | 718 | | |
719 | | - | |
720 | | - | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
721 | 723 | | |
722 | | - | |
| 724 | + | |
| 725 | + | |
723 | 726 | | |
724 | 727 | | |
725 | 728 | | |
726 | 729 | | |
727 | 730 | | |
728 | | - | |
| 731 | + | |
729 | 732 | | |
730 | 733 | | |
731 | 734 | | |
| |||
756 | 759 | | |
757 | 760 | | |
758 | 761 | | |
| 762 | + | |
759 | 763 | | |
760 | 764 | | |
| 765 | + | |
761 | 766 | | |
762 | 767 | | |
763 | 768 | | |
| 769 | + | |
764 | 770 | | |
765 | 771 | | |
766 | 772 | | |
| |||
815 | 821 | | |
816 | 822 | | |
817 | 823 | | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
818 | 837 | | |
819 | 838 | | |
820 | 839 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
186 | 190 | | |
187 | 191 | | |
188 | 192 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
27 | 33 | | |
28 | 34 | | |
29 | 35 | | |
| |||
435 | 441 | | |
436 | 442 | | |
437 | 443 | | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
438 | 507 | | |
| 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 | + | |
0 commit comments