Repository navigation
fix(service-storage): sys_file's file-reference columns are lifecycle-owned - #28454
Conversation
…-owned The five columns that record which record and field hold a file, and which record last let it go (ref_object, ref_id, ref_field, released_ref_object, released_ref_id), take the form the storage-owned columns already have: readonly on sys_file, so the engine's static strip takes a non-system caller's value off an update, and the same create-side guard drops them from a non-system create. The lifecycle's claim, release and copy still write them under the system context. Also states in system-file.object.ts that the presence of key/status is the storage writers' invariant, not the engine's, and trims the guard's unused helper export. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibwQbgGE61XZrK6xRjM
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibwQbgGE61XZrK6xRjM
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 50d3373db0ad62dc1c0955ba81b17b346570079c && git checkout 50d3373db0ad62dc1c0955ba81b17b346570079c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 776c1d875b2d140b4c2419103a7ad8681c1d2610 7fc7033de3b8c45e516ba45ede12ee9928ebac7d && git checkout -B drift-repro 776c1d875b2d140b4c2419103a7ad8681c1d2610 && git merge --no-ff 7fc7033de3b8c45e516ba45ede12ee9928ebac7d
node scripts/docs-audit/affected-docs.mjs --json 776c1d875b2d140b4c2419103a7ad8681c1d2610
|
|
triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: ① Derived judgments
② Rulings cited
③ Narrowing / widening
④ Behaviour / flags
Implemented-by: |
|
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc 维护者速读
席位意见:契约复审 PASS,按裁定 A 原样实现。可以直接落地。 席位核对ACCEPT · seat
|
Requested by Tesla · project thread
Fixes #27767
Fixes #27778
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill or docs page teaches writing these columns; they are the storage service's own bookkeeping
Ruling-ref: 6545931776 (letter A)
维护者速读
sys_file上记录"文件归哪条记录、哪个字段"的五列(ref_object/ref_id/ref_field/released_ref_object/released_ref_id)改由文件引用生命周期独占:对象上设为readonly,并把 fix(service-storage): sys_file's storage-owned columns (key, mime_type, size, status, etag) are not writable outside the storage service #27765 的建档侧守卫扩到这五列;另按 service-storage: after #27399,sys_file.key/statusarereadonlyand so no longer engine-required on insert; state or restore the invariant, and trim an unused export #27778 在对象头部写明key/status的存在由存储写入方保证,并收掉一个无人引用的导出。packages/services/service-storage/src/sys-file-storage-owned-columns.ts(守卫列表)、src/objects/system-file.object.ts(五列readonly与新常量)、src/sys-file-storage-owned-columns.integration.test.ts(新钉)。pnpm --filter @objectstack/service-storage exec vitest run src/sys-file-storage-owned-columns.integration.test.ts,预期 24/24 通过。What changes
system-file.object.ts: a newSYS_FILE_REFERENCE_COLUMNSlist besideSYS_FILE_STORAGE_OWNED_COLUMNS, andreadonly: trueon the five file-reference fields. The engine's static readonly strip now takes a non-system caller's value for them off an update, by id and by predicate, and lands the rest of the write.sys-file-storage-owned-columns.ts: the fix(service-storage): sys_file's storage-owned columns (key, mime_type, size, status, etag) are not writable outside the storage service #27765 create-side guard now drops the storage-owned columns and the file-reference columns from a non-system create (one list,LIFECYCLE_OWNED_COLUMNS, built from the two). The row is still written, still startspending, and the write is not refused; onewarnnames the dropped columns. No new refusal code, no spec key, no governed path, no wiring change.sys_file.key/statusarereadonlyand so no longer engine-required on insert; state or restore the invariant, and trim an unused export #27778 item 1:system-file.object.tsnow states thatkeyandstatusstay declaredrequired, that the engine checks areadonlyfield for shape only on insert, and that their presence is the storage writers' invariant (presign/chunked startpendingwith akey, complete commits, the field copy writes a freshkeyandcommitted). The declaration stays as it is (triage 6511387772).sys_file.key/statusarereadonlyand so no longer engine-required on insert; state or restore the invariant, and trim an unused export #27778 item 2: the helper the guard calls is no longer exported (nothing imported it); it is renameddropLifecycleOwnedColumnssince it now covers both lists..changeset/27767-sys-file-reference-columns.md(patch,@objectstack/service-storage).Tests
New pins in
sys-file-storage-owned-columns.integration.test.ts(the #27765 rig: real ObjectQL over sqlite,StorageServicePluginon the local adapter, data writes as a signed-in caller with no system context):readonly;sys_file.key/statusarereadonlyand so no longer engine-required on insert; state or restore the invariant, and trim an unused export #27778: every storage writer setskeyandstatus(presign, complete, field copy).Pins on origin/main 5517005 (premise): 9 red / 15 green
Measured with only the list exported (no
readonly, no guard change): pins 1–5 red (9 cases), pins 6–8 and every pre-existing case green. Pins 1–5 green after the change. The mechanism of the reading is kept off GitHub under the security dispatch rule.Ablations (one per pin, via scripts/ablation-replace.mjs; each restored to blob == HEAD, git diff HEAD empty)
readonlyremoved fromref_id⇒ 3 red: pin 1, pin 2 (ref_id), pin 3.--replacementwas refused by the tool before running; re-run with--delete.)keywritten null ⇒ 1 red: pin 8.Acceptance notes
content/docs/permissions/system-context.mdxrow 47g describes the guard's system early return in terms of the five storage-owned columns. It stays true (a system insert still keeps them) but does not name the reference columns; left untouched, outside this card's file surface.storage-service-plugin.ts's wiring comment names the guard by the storage-owned columns only; no wiring change, left as it is.sys_filecolumn ("name,acl,scope, ownership") is written through the data door as before. If "ownership" is read as the reference columns, this PR's changeset, in the same release, supersedes it. It is left unedited:check-empty-changesettreats a rewrite of another PR's pending note as a release decision for a person, so the seat decides whether that one word changes.Verification
Head
7fc7033d(2d5586fcplus the revert of a changeset edit; no source change between them).pnpm --filter @objectstack/service-storage exec vitest run --maxWorkers=2 src/sys-file-storage-owned-columns.integration.test.ts⇒ 24/24.pnpm --filter @objectstack/service-storage exec vitest run --maxWorkers=2⇒ Test Files 71/71, Tests 1326/1326;pnpm --filter @objectstack/service-storage typecheck⇒ exit 0 (dts build of the closure).pnpm exec eslint --no-inline-configon the three touched source files ⇒ exit 0.7fc7033d:pnpm --filter @objectstack/dogfood testunder the verify lock ⇒ VERDICT command-exit 0, Test Files 361 passed / 3 skipped (364), Tests 2917 passed / 28 skipped (2945).Derived gates: 62 derived, 62 run, 0 NOT-MEASURED (dispatch-gates --ran reconciled, exit 0)
Every
dispatch-gates --commandsline run with stdin closed, exit codes recorded before any pipe. 58 exit 0 on the first pass. Four were re-run after their prerequisite and then exit 0:check-plugin-teardown-shape --self-test(exit 3 until the pinned control commit was fetched),check:dual-build-cjs-loadsandcheck:i18n(exit 3 until a full dts build), andcheck-empty-changeset(exit 1 on an edit of the pending #27399 note, which was reverted; see Acceptance notes). The derived list is identical for both heads.Generated by Claude Code