Phase (c), qbit side, of QRun-IO/qqq#1136 (spec §4 row 5, §6.1): attachments of any type on any record.
Storage and table:
collaboration_attachment table, audited at FIELD level, listed in the nav as "Attachments".
Bytes go through qbit-object-storage (0.1.0-RC.2) into the configured bucket (default collaboration), at attachments/{table}/{recordId}/{uuid}/{fileName}.
Uploads stream through a bounded temporary file. They enforce the per-table size limit (default 100 MB) and blocked extensions (default none), and compute a sha256.
AttachmentsService:
upload, list (newest first, with canDelete), rename/describe, delete;
downloadUrl(id, inline) returns a 5-minute presigned GET that is never stored.
Previews, actions and cleanup:
Previews use the FILE_PREVIEW adornment with presigned URLs, the same pattern object storage uses. Object storage has no thumbnail generator, so there are no generated thumbnail files. I'll update the spec to match.
Delete attachments is a record action with streamed ETL Review/Result. Users delete their own attachments; collaboration.admin can delete anyone's.
AttachmentCleanup.onParentsDeleted is ready to wire to core's RecordReferenceCleanupInterface.
The home gets an Attachments card with the count and total size.
Security stand-in.security/ParentRecordAccess stands in for core's record-reference security (QRun-IO/qqq#1138), which is not released yet. It is marked for replacement:
The parent must be visible (GetAction as the user); an invisible parent gives "Not found".
Writing needs <table>.attach or table EDIT.
Admin browsing is restricted to rows whose parent table the user can READ. Counts are approximate where rows are post-filtered.
Not in this PR: the UI API endpoints. They wait for the extension point (#1128) in an RC.
Test plan
./mvnw -o verify: 38 tests, 96.8% instruction and 100% class coverage, checkstyle clean. The tests use an in-memory storage adapter; a MinIO run is not included because Docker was unavailable in the sandbox.
## Summary
Phase (c), qbit side, of QRun-IO/qqq#1136 (spec §4 row 5, §6.1): attachments of any type on any record.
**Storage and table:**
- `collaboration_attachment` table, audited at FIELD level, listed in the nav as "Attachments".
- Bytes go through qbit-object-storage (0.1.0-RC.2) into the configured bucket (default `collaboration`), at `attachments/{table}/{recordId}/{uuid}/{fileName}`.
- Uploads stream through a bounded temporary file. They enforce the per-table size limit (default 100 MB) and blocked extensions (default none), and compute a sha256.
**`AttachmentsService`:**
- upload, list (newest first, with `canDelete`), rename/describe, delete;
- `downloadUrl(id, inline)` returns a 5-minute presigned GET that is never stored.
**Previews, actions and cleanup:**
- Previews use the FILE_PREVIEW adornment with presigned URLs, the same pattern object storage uses. Object storage has no thumbnail generator, so there are no generated thumbnail files. I'll update the spec to match.
- **Delete attachments** is a record action with streamed ETL Review/Result. Users delete their own attachments; `collaboration.admin` can delete anyone's.
- `AttachmentCleanup.onParentsDeleted` is ready to wire to core's `RecordReferenceCleanupInterface`.
- The home gets an Attachments card with the count and total size.
**Security stand-in.** `security/ParentRecordAccess` stands in for core's record-reference security (QRun-IO/qqq#1138), which is not released yet. It is marked for replacement:
- The parent must be visible (GetAction as the user); an invisible parent gives "Not found".
- Writing needs `<table>.attach` or table EDIT.
- Admin browsing is restricted to rows whose parent table the user can READ. Counts are approximate where rows are post-filtered.
**Not in this PR:** the UI API endpoints. They wait for the extension point (#1128) in an RC.
## Test plan
- [x] `./mvnw -o verify`: 38 tests, 96.8% instruction and 100% class coverage, checkstyle clean. The tests use an in-memory storage adapter; a MinIO run is not included because Docker was unavailable in the sandbox.
- [ ] CircleCI `test_only`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase (c), qbit side, of QRun-IO/qqq#1136 (spec §4 row 5, §6.1): attachments of any type on any record.
Storage and table:
collaboration_attachmenttable, audited at FIELD level, listed in the nav as "Attachments".collaboration), atattachments/{table}/{recordId}/{uuid}/{fileName}.AttachmentsService:canDelete), rename/describe, delete;downloadUrl(id, inline)returns a 5-minute presigned GET that is never stored.Previews, actions and cleanup:
collaboration.admincan delete anyone's.AttachmentCleanup.onParentsDeletedis ready to wire to core'sRecordReferenceCleanupInterface.Security stand-in.
security/ParentRecordAccessstands in for core's record-reference security (QRun-IO/qqq#1138), which is not released yet. It is marked for replacement:<table>.attachor table EDIT.Not in this PR: the UI API endpoints. They wait for the extension point (#1128) in an RC.
Test plan
./mvnw -o verify: 38 tests, 96.8% instruction and 100% class coverage, checkstyle clean. The tests use an in-memory storage adapter; a MinIO run is not included because Docker was unavailable in the sandbox.test_only.🤖 Generated with Claude Code