Skip to content

[平台侧观察] UnknownFilterTokenError 覆盖不到含非 word 字符的类占位符串:{TODAY()} 不报错,原样下发按字面串比较(匹配全部行) #786

Description

@yinlianghui

发现于 #782 / PR #785 的实施过程中(文件面只有 src/views/task.view.ts 的注释),按「越界即停」单独记录。基线 origin/main = fb48ab5,平台锁 17.0.0-rc.2。

现象

UnknownFilterTokenError 存在的意义写在它自己的错误文案里:把无法解析的占位符变成 400 + 可修的提示,而不是「原样下发给数据引擎、按字面串比较、匹配不到任何东西(或者更糟,按字典序匹配到全部)」。

但它的覆盖面止于占位符文法本身。@objectstack/spec/data:

CONTEXT_TOKEN_WRAPPED_RE = DATE_MACRO_WRAPPED_RE = /^\$?\{([a-zA-Z0-9_]+)\}$/

classifyFilterToken(value):
  const m = value.match(CONTEXT_TOKEN_WRAPPED_RE);
  if (!m) return null;              // ← 这里
  ...
  return { kind: 'unknown', token, suggestion };

内层字符类只有 [a-zA-Z0-9_]。任何含非 word 字符的类占位符串——{TODAY()}、{current-user-id}、{30 days ago}、{user.id}——都在第一行 return null,于是 hasFilterToken() 判为「这棵 filter 里没有令牌」,resolveFilterTokens() 原样返回,字符串照原样到达 driver。

诊断只对拼错但仍是 word 字符的拼法生效({TODAY}、{this_quarter_start}、{14_day_agos})。拼得「更不像」的反而更安静。

实测(pinned 17.0.0-rc.2,InMemoryDriver + ObjectQL.find,一次性探针,未入库)

四行 crm_task,due_date 分别在 30 天前 / 1 天前 / 今天 / 7 天后:

classifyFilterToken:
  {today}      => {"kind":"date-macro","token":"today"}
  {TODAY}      => {"kind":"unknown","token":"TODAY"}
  {TODAY()}    => null

ObjectQL 读路径:
  due_date <  '{today}'      =>  2 行(恰好两行逾期,令牌解析生效)
  due_date <  '{TODAY}'      =>  THROWS UnknownFilterTokenError / FILTER_TOKEN_UNKNOWN
  due_date <  '{TODAY()}'    =>  4 行 —— 全部,含下周才到期的那行
  due_date =  '{TODAY()}'    =>  0 行

第三行就是 #744 记录的那个字典序倒置:'2026-…' 排在 '{'(0x7B)之前,所以 < 比较对每一行都为真。这正是 UnknownFilterTokenError 本来要消灭的失败模式,而它在这条拼法上没有触发。 而且两种错法的观感相反:= 得到空列表(像「今天没有数据」),< 得到全表(像「过滤没写对但至少有数据」),都不像报错。

为什么打 finding 而不是 bug

用户今天碰不到,本仓库也碰不到。 hotcrm 自己的作者期守卫用的正则更宽:test/metadata-references.test.ts 的 badTokensIn() 扫 /\{([^{}]+)\}/g,{TODAY()} 会被它逮住并在测试里报 unresolvable token。所以任何视图 / 页面组件的 filter 都进不了主干。#782 里的 {TODAY()} 从头到尾只活在注释里(PR #785 已订正)。

记录它的理由是:仓库守卫比平台诊断严,这个次序反了。守卫哪天被收窄、或者某条 filter 走了守卫没遍历到的路径(page 组件的 filter 直到 #784 才纳入遍历,就是一次实例),漏下来的东西平台不会拦。

可能的修法(供上游判断,本仓库不做 workaround)

平台侧二选一,都不在 hotcrm 内解决:

  1. 放宽 classifyFilterToken 的识别文法(识别 /^\$?\{([^{}]*)\}$/,再按 word 字符判 known / unknown),让「长得像占位符」的一律走诊断,{TODAY()} 得到 Did you mean "{today}"?;
  2. 或者维持文法不变,但在 resolveFilterTokens() 的字符串分支上补一条「看起来像占位符却没被识别」的检查。

倾向 1:判定「是不是占位符」与判定「是不是合法占位符」应当是两步,现在被同一个正则合成了一步,导致「不合法」被误判成「不是」。

Refs #782、PR #785、#744 / PR #773

Activity

  1. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    CollaboratorAuthor

    📎 PM(修复线):已镜像至 objectstack-ai/objectstack#5586(带 classify 表与 4 行字面量比较实测、两种修法建议)。本单维持 finding 持有——hotcrm 作者期守卫(/\{([^{}]+)\}/)比平台诊断严,本仓今天碰不到,不阻塞任何在途工作;上游若采纳「宽进严出」修法,本单随镜像关闭即可一并关闭。


    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

No one assigned

    Labels

    findingupstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstream

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions