Skip to content

[17.0-rc2验收] REST create/update 回包的 record 缺所有 formula 字段(GET/LIST 有)—— applyFormulaPlan 只挂在 find/findOne 上,写路径回包不做公式水合 #5504

Closed

Description

@yinlianghui

现象

POST /data/:object 与 PATCH /data/:object/:id 回包里的 record 不含任何 formula 字段(键整个缺失),而同一记录随后的 GET / LIST 都有值 —— read-your-write 不一致。

复现步骤

POST /api/v1/data/crm_account {"name":"PatchEcho"}
# → 201,record.display_title === undefined(键缺失)
GET /api/v1/data/crm_account/<id>
# → display_title = "ACC-000016 - PatchEcho"  ✅
PATCH /api/v1/data/crm_account/<id> {"segment":"growth"}
# → 200,record.display_title 仍缺失

多公式对象更明显 —— crm_forecast 的 POST 回包 expected_amount / attainment_pct / coverage_ratio 全部缺失,GET/LIST 均正常求值(30000 / 10 / 0.556)。两次独立运行、多个对象(crm_account、crm_forecast、crm_opportunity_line_item)均复现。

期望 vs 实际

  • 期望:写操作回包的 record 与紧接着的 GET 等价(回包自称是 record,消费方会直接拿来渲染);
  • 实际:回包是入库文档本身,formula(虚拟列)一个都没有,客户端必须额外再 GET 一次。

落点分析

packages/objectql/src/engine.ts:applyFormulaPlan 只有两个调用点 ——

  • L4651(find 结果)
  • L4783(findOne 结果)

insert / update 的返回路径不经过 formula plan,REST 层(packages/rest/src/rest-server.ts POST/PATCH handler → protocol.createData/updateData)也原样透传。

影响

  • 对 nameField 指向 formula 的对象(HotCRM 里 account/product/case/campaign/quote/knowledge_article/forecast/lead/contact 全是),create 后直接用回包渲染记录标题会得到空;
  • 集成方在写后需要多一次往返才能拿到派生值,且这种缺失是静默的(键不存在,不是 null),容易被当成「字段没配」误排查。

体验/一致性问题,p2 量级。

环境

hotcrm@0899b4f + @objectstack 17.0.0-rc.2(REST 验收,admin token,file:SQLite)

查重:搜过 formula create response、write response record formula、formula fields missing/undefined/hydrate,两仓 open+closed 无既有单(#1979 是把 context 穿进 applyFormulaPlan 的读路径工作项,#1530/#3306 是求值本身的缺陷,均不覆盖「写回包不水合」)。

Activity

  1. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    Contributor

    分诊结论

    分类:pm:queue(明确缺陷,已定位到具体文件/行号)
    领域标签:domain:engine(packages/objectql)

    核对结果:在 origin/main(28ad90e)上核实,packages/objectql/src/engine.ts 中 applyFormulaPlan 确实仅有两个调用点——L4651(find 结果后处理)与 L4783(findOne 结果后处理)。insert()(L4830 起)与 update()(L5085 起)两个写路径函数体内均未出现任何 applyFormulaPlan / formula 水合逻辑,返回的 result 是入库文档本身。REST 层 packages/rest/src/rest-server.ts 的 createData/updateData 调用点(L4732、L4870、L6304 等)对返回值也未做二次加工,原样透传给客户端。issue 描述与当前代码现状完全吻合,premise 未被近期任何 PR 修复,问题依然存在。

    查重结果:搜索 objectstack-ai 全组织(objectstack/objectui/cloud)open issues 与 open/closed PR,关键词 applyFormulaPlan、formula write hydrate、formula create response missing 等,未发现任何影子在办/在审条目。#3306、#1928 是求值本身的既有缺陷(公式算出 null),#1988 是读路径的 context 穿透,均不覆盖本 issue「写路径回包不做公式水合」这一新问题面,不构成重复。

    freeze 排查:本 issue 落点是 packages/objectql/src/engine.ts 的写路径通用逻辑,与 driver-memory / driver-mongodb 包族无关,不适用 #5499 冻结,正常排队。

    「本评论来自分诊座位 Routine(#5474 试点),不构成认领」


    Generated by Claude Code

  2. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    Contributor

    排程记录(engine-core 车道 PM,会话 session_01V7WetGmnfoXNn8cLieKKmx,第 1 轮):本单与 #5503 同落 packages/objectql/src/engine.ts 写路径(insert/update),按同文件串行纪律推迟到下一轮,⛔ 本轮不派。已知陷阱预记:#5503 在飞,会改写路径的 readonly/autonumber 剥离逻辑;本单开工时必须基于 #5503 合并后的 main 核对 insert/update 返回路径行号(正文引用的 L4651/L4783/L4830/L5085 届时可能漂移),水合点选择需与 find/findOne 的 applyFormulaPlan 语义一致(含 context 穿透现状,见 #1979)。


    Generated by Claude Code

  3. self-assigned this
    on Aug 5, 2026
  4. os-zhuang commented on Aug 5, 2026

    @os-zhuang
    Contributor

    认领:PM 循环第 2 轮主批(engine-core 车道)
    会话:session_01V7WetGmnfoXNn8cLieKKmx
    分支:claude/issue-5504-write-response-formula-hydration
    Worktree:objectstack-issue-5504
    域:domain:engine-core
    文件面:packages/objectql/src/engine.ts(insert/update 返回路径)、objectql 测试、必要时 packages/rest 的回包断言测试(仅测试,生产码预计只动 objectql)、.changeset/*.md。
    前提已对合并后 main 复核:applyFormulaPlan(engine.ts:540)仍只有 find(:4668)/findOne(:4800)两个调用点,写路径回包不水合。同日变更:#5610(strictReadonlyWrites)与 #5627(autonumber 剥离)刚动过写路径 —— 水合点须在剥离/拒绝之后。


    Generated by Claude Code

  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    Contributor

    复核通过,ACCEPT(engine-core 车道 PM,第 2 轮主批):PR #5698,CI 绿后转 ready 入队。

    交付:insert/update 各一个共享水合点(hydrateWriteFormulas),复用读路径同一 plan 构建器与求值、同一 context 穿透,零额外往返;覆盖面由落点保证(单条/批量/insertMany 族),谓词 multi update 刻意不接(计数契约,已用测试钉住「不是漏掉」);两侧顺序钉死(剥离/拒绝之后、afterInsert/afterUpdate 之前)。REST 零生产码改动,用真实引擎端到端测试证明「这一层不用改」。

    特别记一笔本单反向验证的假绿排查:首轮多出 3 个假绿,根因是测试 harness 的内存 driver 返回活引用(违反 driver-memory 自述的「shallow copies, never live references」契约),读路径就地水合污染了 store —— 修 harness 后反向验证才可信(15 红/3 绿,3 绿均为刻意对照组)。这是「反向验证不但要跑、还要审它自己」的好样本。

    跨包两处注释校准(plugin-audit / trigger-record-change,均注释级)属「语义翻转随行扫描」纪律的正当范围 —— 本 PR 把「写结果不带 formula 字段」这个事实反转了,留着旧陈述就是留谎。顺带发现 #5699(nowSnapshot 休眠形参,default 与 formula 的 now() 双瞬时)已核实立单,归发现分诊轮定级(倾向退休形参或收窄 docstring)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions