Skip to content

chore(deps): 升级 @objectstack/* 17.0.0-rc.2 → 17.0.0-rc.3(零后果修复),并实测四条在案上游镜像 - #910

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-901-rc3-upgrade
Aug 6, 2026
Merged

chore(deps): 升级 @objectstack/* 17.0.0-rc.2 → 17.0.0-rc.3(零后果修复),并实测四条在案上游镜像#910
yinlianghui merged 1 commit into
mainfrom
claude/issue-901-rc3-upgrade

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #901

把 hotcrm 的平台基线从 17.0.0-rc.2 提到 npm rc dist-tag 当前指向的 17.0.0-rc.3升级本身没有触发任何后果修复——六道门全绿,源码 / 元数据 / 测试 / 文档一行未改。下面把「为什么没有」写实,而不是把空表当成没跑。


1. 版本对照

npm view @objectstack/spec dist-tags 复测(2026-08-06):

{ "latest": "16.1.0", "rc": "17.0.0-rc.3" }

versions 尾部到 17.0.0-rc.3 为止,无 rc.4。

位置 FROM TO
package.json dependencies 的 11 个 @objectstack/* 17.0.0-rc.2 17.0.0-rc.3
package.json devDependencies@objectstack/formula 17.0.0-rc.2 17.0.0-rc.3
objectstack.manifest.json specVersion ^17.0.0-rc.2 ^17.0.0-rc.3
objectstack.manifest.json engines.protocol ^17.0.0-rc.2 ^17.0.0-rc.3
pnpm-lock.yaml 刷新 刷新
package-lock.json 刷新(npm install --package-lock-only) 刷新

package-lock.json 是在案的第二把锁——.stackblitzrcstartCommandnpm install,且上一次 rc.1→rc.2 升级(5a11631 / #663)同批刷过它,本单沿用同一处置。它里面残留的 41 处 rc.2 全部是 better-auth@1.7.0-rc.2 及其 adapter,与 @objectstack/* 无关,属正确保留;两把锁里 @objectstack/*17.0.0-rc.2 已归零(grep -n "objectstack.*17\.0\.0-rc\.2" 双锁均无命中)。

安装后逐包核实:spec / runtime / metadata / objectql / cli / service-automation / service-analytics / account / formula / driver-memory / driver-sql / driver-sqlite-wasm 均为 17.0.0-rc.3pnpm install --frozen-lockfile 退出码 0(CI 安装步等价复现)。

rc.2 → rc.3 这个窗口里平台到底改了什么

node_modules/@objectstack/* 全部 50+ 个包的 CHANGELOG.md 机械抽取 ## 17.0.0-rc.3 段落、剔除 Updated dependencies 与版本提升行后,只剩一条实质变更:

  • @objectstack/specBulkActionParamSchemaoptions[] 条目改为 .passthrough()(上游 #4001)label / value 以外的键(coloricondisabledvisibleWhen)过去在 parse 时被静默删掉,现在保留。changelog 自述 "Behaviour change, loosening only"——过去能 parse 的现在都能 parse,无迁移项。

其余每一个包的 rc.3 段落都只有 Updated dependencies + 版本号列表。这是本单「零后果修复」的根因,不是没查。

对本仓的实际影响:无。 hotcrm 只有两处带 options 的 bulk-action param,都在 src/views/account.view.ts(update_tier / transfer_owner),选项逐条只写了 labelvalue——过去没有任何东西被剥掉,所以这次放宽是今天的 no-op;变化只是从此可以给 bulk-action 选项配色/配图标并且它能活到渲染层。


2. 逐红项修复表

空表,且是真的空。 六道门在删掉 dist/ 后的真实 rc.3 上依次跑完,全部退出码 0:

退出码 关键行
pnpm validate 0 ✓ Validation passed (1459ms) — 17 Objects / 344 Fields / 24 Flows
pnpm typecheck 0 tsc --noEmit,无输出
pnpm build 0 ✓ Build complete,Artifact: dist/objectstack.json (1921.4 KB)
pnpm test -- --maxWorkers=2 0 Test Files 66 passed (66) / Tests 1587 passed, 1 skipped (1588)
pnpm lint 0 13 warning(s), 14 suggestion(s)
pnpm hygiene 0 ✓ source hygiene clean

「钉子红」也是零。 本仓有大量把 rc.2 特定行为钉死的测试(object-validation-predicates 的 fail-closed、flow-scheduled 的 #4336 双形态、parent-derived-reach 的权限矩阵、view-predicate-dialect 的 49 个 CEL envelope 计数……),rc.3 一条都没有动到它们所钉的行为,所以没有钉子需要更新到 rc.3 实况。

作者期告警在条数与措辞上逐字不变(4 条 approval-approver + 1 条 crm_campaign_member field-group-shadowed),即升级没有新增也没有消除任何告警。

pnpm install 打出的 better-sqlite3 peer 警告(better-auth 1.7.0-rc.2^12.0.0,@objectstack/cli 带 13.0.2)是随平台包一起进来的既有 peer 冲突,rc.2 上同形,不因本单产生。


3. 在案上游镜像行为测量

探针一次性写、跑完即删,未入库。四条镜像分别对应本仓 issue #779 / #788 / #786 / #651

三档结论:已修复 0 条,仍复现 4 条,需专项复测 8 条。

3.1 objectstack#5574(镜像 #779)— multi: true 批量更新 ctx.previous 恒空 — 仍复现

按镜像探针复刻:真 crm_knowledge_article schema + 真 knowledge_article.hook + sys_fetch_previous_update 内建复刻(priority 5 / beforeUpdate / object *)+ priority 299 观测 hook。

[PROBE] single-row update  => [{"hasInputId":true,"previousDefined":true,"previousStatus":"published", ...}]
[PROBE] MULTI update       => [{"hasInputId":false,"previousDefined":false,"input":"{\"category\":\"troubleshooting\"}"}]
[PROBE] MULTI null-stamp   => [{"hasInputId":false,"previousDefined":false,"input":"{\"last_reviewed_at\":null}"}]

引擎源码侧同样逐字未变(@objectstack/objectql@17.0.0-rc.3 dist/index.mjs):

  • 内建 sys_fetch_previous_update 的首行仍是 if (hookCtx.input?.id && !hookCtx.previous)(:8640 起);
  • beforeUpdatehookContext 构造里没有 previous(:6453 起),hookContext.previous 唯一一次赋值在 driver 写之后、供 afterUpdate 用(:6556)。

补充实测(镜像未记,本单新增,措辞照实): 镜像第 3 条后果「批量写能把已发布文章的 last_reviewed_at 写成 null」在 rc.3 上只在 system 上下文下复现。同一探针三种上下文:

调用上下文 落库 last_reviewed_at
非 system(createContext({ userId: 'u1' })) 保持原值(未被写成 null)
完全不传 context 保持原值(未被写成 null)
system(isSystem: true) A / B 双双落成 null

根因在 multi 分支的 if (!opCtx.context?.isSystem) { … stripReadonlyFields(…) }(:6522-6525):非 system 调用方的 last_reviewed_at(readonly: true)先被 readonly 剥离,null 根本到不了 driver。rc.3 的 objectql 没有任何变更(其 rc.3 段落只有 Updated dependencies),所以这不是 rc.2→rc.3 的行为差,而是镜像探针与本探针在上下文构造上的差别——本单据实记录,不改写镜像的结论。需要留意的是:镜像所论证的现实路径(批量导入 / seed / record-change flow)本仓恰恰是 system 上下文运行的(见 .changeset/record-change-flows-run-as-system.md),所以影响判断依然成立。

镜像的核心主张(ctx.previous 在批量路径恒空,15 个读 previous 的 hook 在该路径空转)无条件复现。

3.2 objectstack#5591(镜像 #788)— readonly 的两个相邻事实 — 仍复现(两半都在)

ObjectQL.create + InMemoryDriver + 真 schema + 真 hook,非 system 上下文 createContext({ userId: 'u1' }):

第 1 点 · insert 路径不剥离 readonly:

[PROBE] insert(non-system) published_at => "2024-03-01T00:00:00.000Z"

原样落库,无 read-only 警告。源码侧一致:stripReadonlyFields( 在整个 objectql dist 里只有两个调用点(:6489 单行、:6524 multi),都在 update 分支,insert 分支没有。

第 2 点 · update 路径的剥离连坐删掉 hook 自己写的戳(按镜像的 draft→published 整记录回传探针):

payload 带 published_at? false (undefined) => status=published  'published_at' in row=true  value="2026-08-06T..."  ← 正常
payload 带 published_at? true  (null)      => status=published  'published_at' in row=false value=null            ← 复现
payload 带 published_at? true  ("2024-03-01T00:00:00.000Z")
                                           => status=published  'published_at' in row=false value=null            ← 复现

也就是:调用方回传了 published_at 这一个键,hook 在同一次写里打的戳就被连坐删掉,落成一篇 status 为 published 而 published_at 不存在的文章。suppliedKeys 仍是在 hook 运行之前从调用方 payload 快照的(:6438 const suppliedKeys = new Set(Object.keys(opCtx.data ?? {})),:6465 才 triggerHooks("beforeUpdate"),:6489 才 strip),机制与镜像记录的逐字一致。

(镜像在 rc.2 上记为 published_at = null,本探针读回是「该键不存在」——driver-memory 只存写过的列,同一事实的两种读法。)

3.3 objectstack#5586(镜像 #786)— UnknownFilterTokenError 对含非 word 字符的类占位符漏诊 — 仍复现

classifyFilterToken 探针:

[PROBE] classifyFilterToken "{today}"           => {"kind":"date-macro","token":"today"}
[PROBE] classifyFilterToken "{TODAY}"           => {"kind":"unknown","token":"TODAY"}
[PROBE] classifyFilterToken "{TODAY()}"         => null
[PROBE] classifyFilterToken "{current-user-id}" => null
[PROBE] classifyFilterToken "{30 days ago}"     => null
[PROBE] classifyFilterToken "{user.id}"         => null

读路径探针(四行 crm_task,due_date 分别在 30 天前 / 1 天前 / 今天 / 7 天后):

[PROBE] find due_date  <  '{today}'    => 2 row(s) ["t-30","t-1"]
[PROBE] find due_date  <  '{TODAY}'    => THROWS UnknownFilterTokenError / FILTER_TOKEN_UNKNOWN
[PROBE] find due_date  <  '{TODAY()}'  => 4 row(s) ["t-30","t-1","t0","t+7"]   ← 全表,含下周才到期那行
[PROBE] find due_date  =  '{TODAY()}'  => 0 row(s) []

与镜像在 rc.2 上的记录逐行相同。源码侧亦然:@objectstack/spec@17.0.0-rc.3 dist/data/index.js:909 仍是
var DATE_MACRO_WRAPPED_RE = /^\$?\{([a-zA-Z0-9_]+)\}$/;,:993 仍是 CONTEXT_TOKEN_WRAPPED_RE = DATE_MACRO_WRAPPED_RE,classifyFilterToken(:1022)第二行仍是 if (!m) return null;

3.4 objectstack#4697(镜像 #651)— FlowVariableSchema 是否新增 defaultValue — 仍复现(未新增)

读 spec 源码判定,node_modules/@objectstack/spec/dist/flow.zod-koL_4VyN.d.ts:41:

declare const FlowVariableSchema: z.ZodObject< {
    name: z.ZodString;
    type: z.ZodString;
    isInput: z.ZodDefault< z.ZodBoolean >;
    isOutput: z.ZodDefault< z.ZodBoolean >;
}, z.core.$strict >;

运行时确认仍是 strict、仍然四个键:

[PROBE] FlowVariableSchema keys => ["name","type","isInput","isOutput"]
[PROBE] safeParse({name,type,defaultValue}) success => false
[PROBE]   issues => [{"code":"unrecognized_keys","keys":["defaultValue"],"path":[]}]

lead_conversioninit_defaults 那个 assignment 节点的 workaround 仍然是唯一可写法,暂不能撤。

3.5 需专项复测(本单不展开)

以下八条没有廉价探针,按派发口径只标注、不强行展开:

镜像 主题 档位
objectstack#5491–5495 权限矩阵五条 需专项复测
objectstack#5386 controlled_by_parent 可达范围 需专项复测
objectstack#5149 UI 侧 需专项复测
objectstack#5019 UI 侧 需专项复测

4. 文档版本引用清单

全仓(排除 node_modules / dist / .git)grep rc.2 得 68 个文件。逐处判定后:

同批更新为 rc.3(纯版本号引用,4 处 / 2 文件)

  • package.json — 12 个 @objectstack/* 依赖版本
  • objectstack.manifest.jsonspecVersionengines.protocol
  • 两把锁文件随之刷新

不动(语义性引用,66 个文件)

它们全部是「某行为在哪个版本上被实测/开始生效」的历史记录,不是本仓依赖什么的声明。改成 rc.3 等于伪造一次没做过的复测。分布:

文件数 形态举例
src/**(objects / views / flows / profiles / actions / sharing / dashboards) 30 MEASURED on 17.0.0-rc.2 and pinned by test/parent-derived-reach.test.ts;从 17.0.0-rc.2 起该 abort 拒绝写入
test/** 18 Measured on 17.0.0-rc.2, on this exact harness: 后接实测输出块
.changeset/*.md(含 upgrade-objectstack-17-rc2.md) 15 待发布 changeset 里的实测叙述与上次升级的历史条目
content/docs/administration/sharing-and-security{,.zh-Hans,.zh-Hant}.mdx 3 Measured against the shipped stack (platform 17.0.0-rc.2)
CHANGELOG.md 1 上次升级的历史条目
AGENTS.md 1 ≤ 17.0.0-rc.1 … / ≥ 17.0.0-rc.2 … 的行为阈值表(在 rc.3 上依然成立)

README.md / docs/** / e2e/** / scripts/** / .github/**rc.2 命中。


5. rc.3 使已合并文档断言失效的条目

逐条核对结果:零条。 rc.3 唯一的实质变更是 BulkActionParamSchema.options[] 的 passthrough 放宽,而:

因此本单没有需要另立单的失效断言。


验证输出

### validate  EXIT=0   ✓ Validation passed (1459ms)
### typecheck EXIT=0   tsc --noEmit(无输出)
### build     EXIT=0   ✓ Build complete — Artifact: dist/objectstack.json (1921.4 KB)
### lint      EXIT=0   13 warning(s), 14 suggestion(s)
### hygiene   EXIT=0   ✓ source hygiene clean
### test      EXIT=0   Test Files 66 passed (66) / Tests 1587 passed, 1 skipped (1588)

全部在 rm -rf dist 之后、pnpm install 刷新出的真实 rc.3 上跑(#5148 防 stale artifact),并在共享验证锁 /tmp/os-heavy-verify.lock 内串行、NODE_OPTIONS=--max-old-space-size=4096 限堆。未起任何 dev server。


Generated by Claude Code

Bumps every @objectstack/* dependency to the current npm `rc` dist-tag
(17.0.0-rc.3, confirmed by `npm view @objectstack/spec dist-tags`), with
`specVersion` and `engines.protocol` in objectstack.manifest.json following,
and both lockfiles refreshed.

Nothing needed migrating. rc.3 carries exactly one substantive platform
change and it is a loosening: BulkActionParamSchema's options[] entry became
.passthrough() (upstream #4001), so keys beyond label/value on a bulk-action
param option survive parsing instead of being stripped. Every other
@objectstack/* package in this release republishes with an `Updated
dependencies` changelog entry only.

HotCRM's two bulk-action params with options (src/views/account.view.ts,
update_tier / transfer_owner) spell their options with exactly label and
value, so nothing was being stripped here and the loosening is a no-op today.

Verified against the installed rc.3 with dist/ deleted first (upstream #5148):
validate, typecheck, build, test (1587 passed, 1 skipped, 66 files), lint and
hygiene all exit 0, with the pre-existing author-time warnings unchanged in
count and wording.

Version strings elsewhere are deliberately untouched: every remaining
17.0.0-rc.2 in src/, test/, content/ and .changeset/ records when a behaviour
was measured, not what the app depends on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 6, 2026 6:02am

Request Review

@github-actions github-actions Bot added dependencies Dependency bumps and lockfile changes metadata Declarative metadata — schema, security posture, UI surfaces labels Aug 6, 2026
@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 07:43
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 7df2978 Aug 6, 2026
10 checks passed
This was referenced Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency bumps and lockfile changes metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(deps): 升级 @objectstack/* 17.0.0-rc.2 → 17.0.0-rc.3(维护者指令),并测量在案上游镜像的行为变化

2 participants