Commit 99736a0
feat(storage): exclusive field-reference file ownership — ADR-0104 D3 wave 2 (PR-3) (#3527)
* feat(storage): exclusive field-reference file ownership — ADR-0104 D3 wave 2 (PR-3)
A file/image/avatar/video/audio field holding a sys_file id now records its
owner on the file itself: sys_file.ref_object / ref_id / ref_field name the
single (object, record, field) slot that references it, maintained on the
engine write path — claimed on insert, reconciled on update, released when the
owning record is deleted.
Field references are EXCLUSIVE, unlike attachments. The attachments surface
deliberately shares one file across many sys_attachment join rows; a field
reference is owned by at most one slot, and writing an already-owned id into a
second slot copies the bytes into a fresh sys_file rather than sharing the row.
Two properties follow, and both exist so a mistake cannot become unrecoverable:
1. A file's read authorisation derives from exactly one parent record instead
of the union of every referrer's — so copying a private record's file id
into a world-readable record, which generated code does without any
visible error, cannot silently widen who can read the bytes.
2. Release is an OBSERVED transition ("my one owner let go"), never an
inferred absence ("I counted and found nobody"). With no count, no
miscount can authorise a delete — and a freshly-built ledger's ignorance
of pre-existing references cannot either.
Deletes nothing. This records and releases ownership; it never tombstones, and
the scope==='attachments' guardrail that keeps field-referenced files out of
the reap entirely is untouched, so shipping it cannot reclaim any bytes.
Enabling collection must extend releaseOwnership AND the sys_file reap guard's
sweep-time re-verify in the same change — half of it is worse than none, since
tombstoning while the guard still re-verifies only sys_attachment (always empty
for a field file) makes every release a guaranteed byte delete.
Also lifts isFileIdToken into @objectstack/spec/data as the single arbiter of
"is this stored string an opaque file id or a legacy/external URL?", replacing
the private copy in the engine read resolver so the reader and the writer
cannot drift into claiming files nobody expands, or vice versa.
Dormant until a field actually holds an id token: objects without file-class
fields, inline-blob values and URL-shaped values all exit before any I/O.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* fix(storage): write \u0000-free same-slot check
check:nul-bytes caught a raw NUL used as an impossible-record-id sentinel in
the copy-on-claim same-slot comparison. A raw NUL makes ripgrep treat the whole
file as binary, so it silently drops out of code search and every grep-based
lint. Replaced with an explicit null check plus a named isSameSlot helper,
which says what it means and is reused by claimFile.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 21ca1d5 commit 99736a0
12 files changed
Lines changed: 1301 additions & 15 deletions
File tree
- .changeset
- docs/adr
- packages
- objectql/src
- services/service-storage/src
- objects
- spec
- src/data
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | | - | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
481 | 482 | | |
482 | 483 | | |
483 | 484 | | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
484 | 589 | | |
485 | 590 | | |
486 | 591 | | |
487 | 592 | | |
488 | 593 | | |
489 | 594 | | |
490 | | - | |
491 | | - | |
492 | | - | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
2142 | 2142 | | |
2143 | 2143 | | |
2144 | 2144 | | |
2145 | | - | |
2146 | | - | |
2147 | | - | |
2148 | | - | |
2149 | | - | |
2150 | | - | |
2151 | | - | |
2152 | | - | |
2153 | 2145 | | |
2154 | 2146 | | |
2155 | 2147 | | |
| |||
2190 | 2182 | | |
2191 | 2183 | | |
2192 | 2184 | | |
2193 | | - | |
| 2185 | + | |
2194 | 2186 | | |
2195 | 2187 | | |
2196 | 2188 | | |
| |||
0 commit comments