fix(cli): 把本地存储根写成设置服务读的那个 env 名 —— OS_STORAGE_ROOT → OS_STORAGE_LOCAL_ROOT(方案 B) - #5601
Conversation
CLI 与设置服务对同一个值用了两个拼写。CLI 自造了 `OS_STORAGE_ROOT`;
设置服务从它自己拥有的命名空间派生 env 名 ——
`envKeyOf('storage','local_root')` = `OS_STORAGE_LOCAL_ROOT` —— 而全仓
没有任何地方设置过它。于是两条通道从未相遇:`os serve` 按运维给的根构造
了本地 adapter,`StorageServicePlugin` 在 `kernel:ready` 从 settings 重新
解析,只看到 manifest 的 schema 默认值,就把 adapter 换成了
`./.objectstack/data/uploads`。
所以 `OS_STORAGE_ROOT` 只对一个值生效 —— 恰好等于该默认值的那个,这正是
普通 `pnpm dev` 从没暴露它的原因。其余任何值都是构造完就被丢弃:生产
`/srv/uploads` 被忽略、运维按 backup-restore.mdx 备份到空目录;`dev --fresh`
承诺 tempdir 独占本次运行的全部状态,上传实际落在项目 cwd 且退出后不清理;
每次干净启动都响一条数据丢失级 swap 警告 —— 那条警告是**准确的**,swap
真的发生了,本 commit 不动它,它随 swap 消失而不再响。
修在生产者侧,不在消费者侧加容忍读:`dev.ts` 发布 `OS_STORAGE_LOCAL_ROOT`,
`serve.ts` 经单一通道 `resolveStorageLocalRootEnv` 解析根,并与 `os migrate`
的 storage 引导共用,使 CLI 落字节的位置与 server 完全一致。
`OS_STORAGE_ROOT` 经 `readEnvWithDeprecation('OS_STORAGE_LOCAL_ROOT',
'OS_STORAGE_ROOT')` 保留一个 release,每进程 warn 一次,随后移除。旧名供值
时同时回写到新名 —— 设置服务只查 `OS_STORAGE_LOCAL_ROOT`,没有这一步,旧名
部署会原样保留本单要修的缺陷。
不动 `packages/services/service-storage`:swap 谓词是对的(#4096 已修正),
消费缝归 #5536。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
…rage-root-env-channel
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
CI 红说明(PM,cli 车道):ESLint job 的失败项是 解堵热修已派发(#5138 的原 dev,按门禁自身处方把两处 engine double 的 Generated by Claude Code |
Fixes #4968
按 16:41Z 维护者裁决的方案 B 落地:CLI 改写设置服务自己的 env key
OS_STORAGE_LOCAL_ROOT,旧名OS_STORAGE_ROOT以readEnvWithDeprecation形态保留一个 release(改名弃用,非长期双读),读到旧名时打弃用 warn。不动
packages/services/service-storage/**(hasAny/applySettings消费缝归 #5536)、不动packages/services/service-settings/**语义、不动 releases 目录、不动packages/runtime/**与packages/rest/**。前提复核:成立(先证后改)
在修改前的最新
origin/main上重跑复现,16:15Z 核对评论的证据链逐条成立:同一次
--fresh启动(构造侧🧪 Fresh OS_HOME: /tmp/objectstack-dev-L6v8I6),GET /api/settings/storage:并且补了一条核对评论没有的直接证据 —— 真实上传一个文件,看字节落在哪:
--fresh的隔离承诺确实被破坏:字节落在项目 cwd,不在 tempdir,且进程退出后留在工作树里。根因与修法
设置服务从它自己拥有的命名空间派生 env 名 ——
envKeyOf('storage','local_root')=OS_STORAGE_LOCAL_ROOT(settings-service.types.ts:215),而 CLI 自造了OS_STORAGE_ROOT。两条通道从未相遇:os serve按运维给的根构造 adapter,StorageServicePlugin在kernel:ready从 settings 重新解析,只看到 manifest 的 schema 默认值,就把 adapter 换掉。所以
OS_STORAGE_ROOT只对恰好等于该默认值的那一个值生效 —— 这正是普通pnpm dev从没暴露它的原因。修在生产者侧,不在消费者侧加容忍读:
dev.ts发布OS_STORAGE_LOCAL_ROOT;serve.ts新增单一通道resolveStorageLocalRootEnv(),与os migrate的 storage 引导(data-migration-plugins.ts)共用,使 CLI 落字节的位置与 server 完全一致。⚠ 一处裁决未点名、但方案 B 验收项依赖的细节:旧名必须回写
裁决文本只说"以
readEnvWithDeprecation('OS_STORAGE_LOCAL_ROOT','OS_STORAGE_ROOT')形态保留"。但光"读到"不够:设置服务只查OS_STORAGE_LOCAL_ROOT,所以旧名供值时必须同时把值 stamp 到新名上,否则旧名部署会 100% 原样保留本单要修的缺陷(adapter 建在/srv/uploads,settings 仍停在 schema 默认值,kernel:ready照swap)。这一步是验收项"旧名走弃用通道 →
source:'env'、不 swap"能成立的前提,不是额外发挥;方向仍是方案 B(契约优先、单一拼写),没有引入第二条语义。设置服务的this.env持有process.env的活引用且按需读(settings-service.ts:127/:507),所以 stamp 与插件构造顺序无关。验收:三种启动形态真机核(维护者点名项)
local_root./.objectstack/data/uploadsdefault--fresh/tmp/objectstack-dev-jrW4lp/uploadsenvOS_STORAGE_LOCAL_ROOT=/srv/uploads/srv/uploadsenvOS_STORAGE_ROOT=/srv/uploads/srv/uploadsenv--fresh修后:同样的上传探针,字节回到 tempdir、项目 cwd 干净:
旧名形态的弃用 warn(原文):
showcase 启动诊断从 1 条 warning 变为 0 条 —— 那条 swap 警告本身是准确的(#4096 已把谓词改对),本 PR 一个字没动它;它不再响是因为 swap 不再发生。
反向验证(方向先判后跑)
判断 1 —— 删掉 stamp(只保留裁决文本字面写的弃用读): 预判 2 红 3 绿。跑批与预判一致:
这正是 stamp 是承重件、不是装饰的证据:只做弃用读会留 3/5 绿。
判断 2 —— 把
dev.ts改回写OS_STORAGE_ROOT: 预判单测保持绿,因为 serve 子进程仍会经弃用通道桥接,缺陷不会复现,变化只是每次--fresh多响一条运维根本没设过的弃用 warn。核实结论:全仓没有任何测试触及dev.ts的localEnv/freshStorageRoot(grep 仅命中本 PR 的测试文件),该处只有真机--fresh形态能钉住,单测钉不住 —— 如实报告,不假装有覆盖。测试
新增 5 例(
serve-storage-capability.test.ts),覆盖裁决点名的四种组合 + 端到端:新名生效且静默、旧名生效 + 弃用 warn + 回写、旧名端到端喂到 capability arg、两名同设新名赢且不 warn、都不设时不 stamp 且回落默认。顺手改了一个名不副实的既有用例名:
honours OS_STORAGE_ROOT…其实从不读 env(它传参),名字挂着一个它不碰的变量,正是 env 通道长期没被审视的缝 —— 改为honours an explicit root…。已 merge 当前
origin/main(81087877e)后重跑,同样全绿。消费半径巡检
resolveStorageLocalRootEnv的调用方:serve.ts能力槽位、data-migration-plugins.ts(os migrate files-to-references)—— 两处都已切到同一通道。全仓 grepOS_STORAGE_ROOT,剩余命中只有两处刻意不动的:content/docs/releases/v17.mdx:1911"OS_STORAGE_ROOTactually takes effect" —— 按裁决,releases 目录不进代码 PR,发布流程校正;.changeset/storage-adapter-swap-verdict.md(StorageServicePlugin warns "storage adapter swapped (LocalStorageAdapter → LocalStorageAdapter) … files may be unreachable" on every clean boot #4096 的待发布 changeset)同样写着 "OS_STORAGE_ROOTtakes effect" —— 属他单的发布输入,本 PR 不改,此处提请发布流程一并校正,否则同一版发布说明会先断言它生效、再由本 PR 的 changeset 更正。文档
environment-variables.mdx:OS_STORAGE_LOCAL_ROOT为正名并说明它就是 Setup → Settings → File Storage → Root directory 的同一个值;旧名单列一行标注弃用,并写清"改名前任何非默认值都被静默丢弃"这一交接事实。backup-restore.mdx:改用新名,并加一条 warn Callout —— 提醒运维核对备份目录里是否真有文件,旧版本上按配置路径备份会拷到空目录;给出用 Setup 页面对账的办法。界外发现
查重(含 closed)后立单 #5594(
finding,未进pm:queue,unassigned):dev --fresh注释声称 tempdir "owns ALL persistent state",但 app 自声明的 cwd 相对 datasource 路径(showcase 的showcase_external.db)仍写进项目树并在退出后留存。本 PR 修好了其中的 storage 那一半,余下的是声明口径问题,属观察类,交 PM 分诊。Generated by Claude Code