Repository navigation
fix(storage)!: a file its record lets go keeps that record's read rule - #26144
Conversation
ADR-0104 D3: one file, one owning record, one access rule. Until a writer who may read it claims it again, or it is collected, a file its record let go is read by that record's rule. sys_file gains two nullable bookkeeping fields naming that record; release writes them, a claim clears them, and the door's verdict (shared with the claim path) reads them. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018e5yJf1ptbMURVgWb3LrF6
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018e5yJf1ptbMURVgWb3LrF6
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 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 29ad22f967de28b93b04e77c9021ecee66f96b66 && git checkout 29ad22f967de28b93b04e77c9021ecee66f96b66
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1ea2363f6389a4cbd812843d670692283f52b37 7cc88889e2294032fbb467107499c8f04d9a77da && git checkout -B drift-repro d1ea2363f6389a4cbd812843d670692283f52b37 && git merge --no-ff 7cc88889e2294032fbb467107499c8f04d9a77da
node scripts/docs-audit/affected-docs.mjs --json d1ea2363f6389a4cbd812843d670692283f52b37
|
Contract reviewServed-tier: Inputs: card #26046 (body, triage, both claims, the os-dev-report), PR #26144 (body, file list, the net diff of ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. This PR is Tier H (one ADR-0104 D3 addendum line): it carries an APPROVED review from
Generated by Claude Code |
Requested by Leehom · project thread
Fixes #26046
Clause-②: no (narrowing)
Metadata-protocol: none
What this changes
ADR-0104 D3 promises one file, one owning record and one access rule. This makes the rule hold after the owning record lets the file go. Until a writer who may read that record claims the file again, or the platform collects it, the file is read by that record's rule.
file-reference-lifecycle.ts).sys_filegains two nullable bookkeeping fields,released_ref_objectandreleased_ref_id, naming the record that last owned the file. Every release writes them, whether or not it tombstones. A claim clears them, because the claiming record's rule replaces the old one.judgeFileReadinstorage-service-plugin.ts). Such a file is judged by that record's read access, throughreadableFieldOwners, the function the field-owned arm already asks (so afileAccessDelegatedecides here too). The download door and the claim path (files: an authorization gap in the file-reference lifecycle, within one organization (detail withheld) #25692) share this verdict, so both follow it. A record that no longer exists is read by nobody, so only the uploader reads its released files.public_read, attachments-scope files, files: an authorization gap in the file-reference lifecycle, within one organization (detail withheld) #25692's write rule and fix(storage)!: an anonymous write carries no file, at every anonymous door #26036's anonymous-write rule are untouched. The reap guard still reads only the ownership columns, so the new fields never hold a file back from collection. files: a record write that names a file its writer may not read keeps a dangling id (the quiet answer of #25951); refuse the field instead, so no slot holds a file it does not own #26043's path is not touched.The measurements and the exact path are with the server seat, not here.
Declarations
Clause-②: no (narrowing), measured on this diff: a file read that the owning record's rule refuses is now refused after the record lets the file go. Nothing refused becomes accepted. The claim wroteClause-②: noand named this arm as expected.Metadata-protocol: none: nothing a metadata author writes changes. The two fields are platform-managed bookkeeping on a platform object.patchchangeset on@objectstack/service-storage,fix(storage)!:, with the BREAKING section and the ADR-0087 dispositionnot-required (no-migration-prescription).Tests
HEAD
7cc88889e.file-reference-release-read-rule.integration.test.ts: one realObjectQL+ sqlite engine withStorageServicePluginbooted as shipped, asked through the download door and the claim path, for every way a file leaves its slot, under both collection settings. On base0a6948210it is red (13 failed, 8 passed); on this branch 21/21 pass.scripts/ablation-replace.mjs(anchor 1 to 0, blob24540c7fe6b3to12afcb53c051) turns the same 13 red; restored, blob equals HEAD andgit diff HEADis empty.pnpm --filter @objectstack/service-storage test: 66 files, 1218 tests passed.typecheck(includingcheck:test-typecheck) passed. eslint onpackages/services/service-storagewith--no-inline-config: clean.pnpm --filter @objectstack/dogfood testunder the verify lock, VERDICT command-exit 0, Test Files 296 passed | 3 skipped (299), Tests 2523 passed | 18 skipped (2541).Gates
Local subset at HEAD
7cc88889e:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 80 families from this diff; all 80 ran with exit 0 (each code captured before any pipe), and--ranreconciles 80 run, 0 NOT MEASURED. Before the full build,check:i18nandcheck:dual-build-cjs-loadsanswered exit 3 (prerequisite not met) andcheck-plugin-teardown-shape --self-testexit 3 (shallow clone); afterpnpm buildand fetching the self-test's control commit all three are 0, andcheck:i18ndrove the translation-bundle commit.node scripts/check-platform-checklist.mjs --changed-from origin/main: OK, 4 anchors judged, 0 lost. Every other family is CI's; this PR's check-runs are the record.Acceptance notes
authorizeDownload'sgatedtest reads the ownership columns only). That decides how long an already-issued URL lives, not who gets one.#26043 remains open; its path is not touched.维护者速读(草稿)
改了什么:记录放手一个文件后,这个文件继续按原记录的读权限判定,直到有能读原记录的人重新认领它,或它被平台回收。
sys_file加了两个平台自管的空值字段记住"原记录";ADR-0104 的 2026-10-26 补记里改了一行。为什么改:ADR-0104 承诺"一个文件、一个所属记录、一条访问规则"。实测发现这条规则在记录放手文件之后不成立(细节只在席位线程)。合同、证件、HR 附件这类私密记录里的文件,替换或删除后也要保持原来的访问范围。
风险与代价(含回滚):这是收窄:以前能读到的这类文件,现在不能读原记录的成员读不到了(上传者本人始终能读)。修复前已有的数据有一处遗留,详情在席位线程。回滚即 revert 本 PR;两个新列留在库里无害。
席位意见:
你要做的:审阅 ADR 那一行与修复,批准后合并(Tier H,保持 draft 等你)。
Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_018e5yJf1ptbMURVgWb3LrF6