Skip to content

fix(formula,service-analytics): 类型盲后端把 Date 与线上文本比出错——storage-form 轴指过去当场测出两个真缺陷 (#4191) - #4223

Merged
os-zhuang merged 2 commits into
mainfrom
claude/storage-form-time-locking-xkcdgc
Jul 30, 2026
Merged

fix(formula,service-analytics): 类型盲后端把 Date 与线上文本比出错——storage-form 轴指过去当场测出两个真缺陷 (#4191)#4223
os-zhuang merged 2 commits into
mainfrom
claude/storage-form-time-locking-xkcdgc

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

#4191 storage-form 轴的第二轮(首轮 #4205 已合,封了 SQL 系四格 + cloud#938 的 turso 四格)。本轮按优先级往下走,在两个类型盲后端上把轴测成了真缺陷

1. driver-memory:D-E3 的 schema-到达收敛,此前零覆盖

D-E3 承诺该驱动在 schema 到达时收敛表里已有的行initialData 夹具与持久化恢复,两者都发生在 schema 声明之前)。此前每条 sweep 都经 create() 播种——唯一不可能产出 pre-convention 形态的路径,所以这个承诺从未被断言过。

新增两条 describe:经 initialData 塞入原始 pre-#4047 形态(native → JS Datewire → zone-naive 文本;time 孪生 → epoch 日 Date 与全量时间戳),让 syncSchema 跑追溯 pass,再跑共享表;各带一条收敛后取值的前提断言,杜绝空转绿。结论:D-E3 的承诺成立,现在有锁了。

2. formula 的 RLS 写侧 check:10/16 发散,方向是拒写

post-image 是调用方原始写入载荷plugin-security{ ...opCtx.data },早于任何驱动 formatInput),所以 SDK 侧 create({ signed_on: new Date() }) 直接把 Date 交给 matchesFilterCondition;反向配对同样成立——CEL 的 today() 按 D1 lower 成 Date,撞上持有规范文本的记录。

JS 关系运算符排不了这个序:< 一族按 hint number 强制转换,Date 变 epoch、字符串变 NaN每个比较恒 false。实测 16 个共享用例中 10 个丢光全部 Date 值行。fail-closed 把它变成拒写——#4047 的写侧孪生,正是 D-D2 为裸日期上界记录过的那个失败方向。

3. analytics preview evaluator:同样 10/16,且双向

同一配对(mongo 的 datetime canon 就是 BSON Date,D-E2),但 compare 回退到 String(value)'Mon Jul 27 2026 …' 字典序排在每个 '2026-…' comparand 之后,于是窗口既丢行又多收行$gte 那格多返回了 b_prev/h_leap)。草稿图表因此显示了发布后会变的数字——恰恰是 preview 存在的意义(连续性)被打破。

修法

utcInstantMsspec/data/calendar-day.ts,与 nextUtcCalendarDay 并列(core 再导出)——一处定义供两个后端用,即 D-D2 对同一问题采用的纪律,而不是各留一份私有副本。它只接受无歧义拼写(Date / epoch ms / 裸 YYYY-MM-DD / 带或不带显式时区的 ISO 时间戳,后者按 D-B2 视为 UTC),因此答案不依赖进程时区——这一点对跑在 America/New_York 下的 Temporal Conformance job 是硬要求。

刻意收窄:提升只在「一侧是 Date 且两侧都能读成 instant」时触发,所以此前能工作的比较逐字节不变(string-vs-string 保持 ISO 字典序,number 保持数值),安全姿态不会比操作数本身所能证成的更宽。裸墙钟返回 null——Field.time 不是 instant(#2004),类型盲面必须原样放过而不是替它编一个日历日。

覆盖与验证

两个消费者各加一条 native-writer sweep(外加 formula 的镜像配对断言)。spec 模块文档新增「What the axis means on a TYPE-BLIND backend」一节,写明为何 date 列与整张 Field.time 表按既有 scope rule($gt-on-datetime 那条)留在轴外。

套件 结果
formula 全量 315 passed
service-analytics 全量 476 passed
driver-memory 全量 260 passed
plugin-security 全量(RLS check 真实调用方) 677 passed
core 全量 437 passed
spec check:docs / check:api-surface 绿(api-surface diff 仅 utcInstantMs 一行,已 regen 提交)

service-analyticstsc --noEmit 有 5 行报错,stash 后逐字相同——改动前既有,不在本 PR 触及的文件里。

driver-mongodb:本轮不动,已立项 #4220

#4191 点名最尖的一格。本环境跑不了真实 MongoDB(出站代理对 fastdl.mongodb.org 返回 403 policy denial,mongodb-memory-server 误报成「版本-平台组合不存在」;本机无 docker 守护进程),而该驱动既无读修复也无 backfillsupports.migrations: false,已逐一 grep 确认无 $expr/$convert/repair 路径)——D-E3 有意不做 boot-step backfill,但它指向的「until migrated」那个迁移并不存在。

因此遗留 sweep 必然红、修复必须盲写安全相关的驱动改动。按「不做未验证的架构改动、不为未交付能力做假覆盖」立项 #4220,写明预期机理、两条候选修法($expr 读修复 vs 显式迁移命令,倾向二者并存以与 SQLite 对称)与验收规则。

关联

#4191(本体)、#4205(首轮)、cloud#938、#4220(mongo)、#4203check:generated 台账,main 上即红);ADR-0053 D-A3 / D-D2 / D-E2 / D-E3。

🤖 Generated with Claude Code

https://claude.ai/code/session_01SWHvwFgUU2U8M9nbV1g1iQ


Generated by Claude Code

claude added 2 commits July 30, 2026 16:13
…rage-form axis (#4191)

D-E3 promises this driver converges the rows ALREADY in a table when the
schema arrives — the population initialData fixtures and persistence
restores create, both landing before any schema is declared. Nothing
asserted it: every existing sweep seeds through create(), which is the
one path that cannot produce a pre-convention form.

Two describes seed the raw pre-#4047 forms via initialData (native → a
JS Date, wire → zone-naive text; the time twin → an epoch-day Date and a
full timestamp), let syncSchema run its retroactive pass, then run the
shared tables. Each carries a premise assertion on the converged values
so the sweep cannot pass vacuously.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SWHvwFgUU2U8M9nbV1g1iQ
…ext on the type-blind filter backends (#4191)

Pointing the storage-form axis at the two backends that have no storage
found the same defect in both, and neither needed a legacy fixture to
reach it — the cross-type pairing arrives from real callers:

- the RLS `check` post-image is the caller's RAW write payload
  (`{ ...opCtx.data }`, before any driver formatInput), so an SDK write
  of `new Date()` lands in matchesFilterCondition as a Date against
  wire-text comparands; the mirror pairing arrives too, since a CEL
  `today()` lowers to a Date;
- a preview row from a mongo-backed dataset arrives as a BSON Date
  (D-E2), against the same comparands.

JS relational operators cannot order that pair: they coerce with hint
number, so the Date becomes its epoch and the string becomes NaN. 10 of
the 16 shared cases diverged on each backend. Fail-closed made formula's
half a DENIED write — the write-side twin of #4047, the direction D-D2
recorded for the bare-day upper bound. The preview's half was worse:
falling back to String(value) puts 'Mon Jul 27 2026 …' after every
'2026-…' comparand, so windows both lost rows and gained them, and a
drafted chart's numbers changed at publish.

utcInstantMs joins nextUtcCalendarDay in spec/data/calendar-day.ts (core
re-exports it) — one definition for both, the D-D2 discipline. It accepts
only unambiguous spellings, so the answer never depends on the process
zone, and returns null for a bare wall clock, which denotes no instant:
the Field.time table and the date column stay out of the sweep by the
same scope rule this matrix already applies to $gt-on-datetime.

Deliberately narrow: the lift runs only when one operand is a Date and
both read as instants, so every comparison that worked before is
byte-identical and the security posture never loosens beyond what the
operands justify.

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

vercel Bot commented Jul 30, 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)
objectstack Ignored Ignored Jul 30, 2026 4:28pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling and removed size/m labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/core, @objectstack/formula, @objectstack/service-analytics, @objectstack/spec.

115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/data-api.mdx (via @objectstack/service-analytics)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/core, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/formula, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/formula, @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/core)
  • content/docs/permissions/authorization.mdx (via packages/core, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/core, @objectstack/formula, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/formula, @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/core, @objectstack/service-analytics, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/core, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v15.mdx (via @objectstack/core, @objectstack/formula)
  • content/docs/releases/v16.mdx (via @objectstack/formula, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/service-analytics, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation protocol:data tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants