Repository navigation
docs(agents): 把唯一的 ObjectQL 范例改成本仓真实的读路径 (#855) - #862
Conversation
…ad path (#855) `broker.find('opportunity', { filters: … })` named a surface that does not exist here, a predicate key that fails silently in process, and an object name that violates the `crm_` prefix rule four lines above it. - `broker`: zero occurrences in `src/`. The surface is `ctx.api` — 43 call sites in `*.hook.ts` (cast as `HookApi`, then `api.object(...)`) and 17 in action script bodies (`ctx.api.object(...)`). - `filters`: the deprecated plural alias of the `filter` HTTP query-param (a JSON string), not a key of the in-process query object. In process it is dropped without error: `findOne` returns the object's first row, `count` counts the whole object — the exact account `.changeset/hook-query-where-not-filter.md` records. - `'opportunity'`: the object is `crm_opportunity`. The predicate value moves from the AST-array form to the object form every first-party call site uses; the array form is legal at the platform level but is not assignable to `HookQuery['where']`.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
The new predicate sentence said "the predicate key is `where`, and only `where`" without naming the surface. Flow node `config` is a different, schema-unvalidated bag (`config: z.record(z.string(), z.unknown())`) and all 24 `*.flow.ts` query/update nodes spell their predicate `filter:`. An unscoped rule in a binding instruction file invites the next agent to "fix" those into `where:` — the same class of harm this issue is about.
追加提交
|
| 命令 | 退出码 |
|---|---|
pnpm validate |
0(17 Objects 344 Fields · 5 条基线 warning 不变) |
pnpm typecheck |
0 |
pnpm build |
0(dist/objectstack.json (1921.3 KB)) |
pnpm lint |
0(13 warning(s), 14 suggestion(s),基线既有) |
pnpm hygiene |
0(✓ no raw control bytes in first-party files) |
pnpm test -- --maxWorkers=2 |
0(Test Files 66 passed (66) · Tests 1587 passed | 1 skipped (1588)) |
控制字节自扫两文件仍为零命中(grep -naP exit 1)。
一处如实更正
上文第 5 节的验证表是我在本地跑的,退出码属实。但我另外用 curl 轮询 GitHub check-runs 想确认 CI,那条路在本 session 里返回 HTTP 403(GitHub access is not enabled for this session),我的轮询脚本把错误体解析成「0 个未完成」因而报了一次「ALL CHECKS COMPLETE」—— 那是假阳性,不是 CI 结论。GitHub API 在本 session 只能经 MCP 走。以 PR 页面上的真实 check 结果为准;本条追加提交后的 CI 尚在运行。
Generated by Claude Code
The clause said "all 24 flows do", which was reasoning from the validate banner's flow count rather than from the files. Measured: `filter:` appears 44 times across 17 of the 21 `*.flow.ts` files, and `where:` appears in none of them. Not every flow has a query node, so "all 24" was wrong.
更正:上一条评论里的 flow 侧计数是错的(
|
… the AST form Two corrections to my own text, both found by measuring rather than reasoning: - The AST-array predicate is not unused here. `src/pages/lead_detail.page.ts:217` spells a page component's `filter:` as `[['status', '!=', 'completed']]` — the ObjectUI/FilterBuilder form. What is true is narrower: no `ctx.api` call site uses it. The changeset said "zero first-party call sites", which swept in a surface the rule does not govern. - The clause named only flows as the other surface. It now names both flows and page component configs, so the scope of the `where`-only rule is legible from the rule itself. Also: the prefix rule sits five lines above the example in the original file, not four.
再更正:AST 数组形并非本仓零使用(
|
| 面 | 谓词键 | 值的形状 | 实测 |
|---|---|---|---|
ctx.api(hook / action script body) |
where |
对象形 | 60 处调用,其中 28 处带谓词;数组形 0 |
*.flow.ts 节点 config |
filter |
对象形 | 44 处 / 21 个 flow 文件中的 17 个;where 0 |
| page 组件 config | filter |
AST 数组形 | src/pages/lead_detail.page.ts:217 |
AGENTS.md 里那句收尾因此从「flow 是另一个面」扩成「其它面不受本条约束,各有各的拼法」,并把 flow 与 page 两处都点名 + 给出行号。一条只提 flow 的例外说明,仍然会让下一个 agent 觉得 page 那处是漏网的。
顺带把 changeset 里「stated four lines above it」改成 five(原文件 rule 在 59 行、范例在 64 行)。
这已经是我第三次修自己的措辞,说明一下为什么没有一次到位
这个 PR 的主张是「指令文件里的断言必须实测」,而我前两版的收尾句恰好是推的不是测的:第一版没写作用域(会让人去改 24 个 flow),第二版的「all 24 flows」是从 validate banner 的流程数推的,第三版才把三个面都 grep 干净。每一次都是同一个毛病 —— 在讲「不要凭印象写指令」的段落里凭印象写指令。留档在此,比悄悄改掉有用。
70058243 重跑全量,六项退出码均 0(Test Files 66 passed (66) · Tests 1587 passed \| 1 skipped (1588)),两文件控制字节自扫零命中。
Generated by Claude Code
Fixes #855
1. 前提复核(先于编辑)
基线取最新
origin/main=eb4a7e11(#855 正文记的是9d2c787a,其后落了 #857 / #859 / #861 三个 merge)。- Format:+ 反引号broker.find('opportunity', { filters: [['amount', '>', 50000]] })crm_强制前缀规则行号前提成立,三处失真全部复现。
2. 三处失真的实测证据
2.1
broker—— 本仓零命中探针没坏:同一条 grep 打在真实存在的调用面上是 60 命中。本仓真实读路径分两个上下文,同一个
ctx.apisurface:*.hook.ts(TypeScript,编译期受检)const api = ctx.api as HookApi ...后api.object('crm_x')ctx.api.object('crm_x')src/actions/)≥2 处真实文件行(issue 点名的两处,行号在当前 main 上复核):
再补两处,证明
ctx.api.前缀形也是真实的、且 hook 侧的api就是ctx.api:2.2
filters—— HTTP query-param 的已弃用别名,不是进程内 query 的键出处按裁定在
node_modules里复核(@objectstack/spec17.0.0-rc.2,src/api/protocol.zod.ts,行号与 issue 记的 326-327 一致):两点:值是
z.string()(JSON 字符串),且它挂在HttpFindQueryParamsSchema上 —— HTTP 层。范例给的却是进程内调用。连单数filter都不是进程内的键,filters更不是。进程内的真相由本仓自己的
src/objects/_hook-api.ts写死(HookQuery只有where/fields/top),其注释复述了这笔账:find会把filter归一成where(碰巧对),findOne把 query 直接摊进 AST({...query, limit: 1})从不做别名 → 返回该对象第一行;count只读query.where→ 数全表。都不报错、不返回null。本仓
.changeset/hook-query-where-not-filter.md记的就是这笔账:17 处 hook 调用曾写成filter:,代价包括 line-item 定价取错产品、报价接受 / 赢单-合同激活的「是否已在目标状态」判在无关记录上、case 升级把跟进任务派给第一个账户的 owner、删除守卫数全表因而拦下无引用的删除、campaign ROI 把全表计数记成归因。类型面反证(一次性
tsc --noEmit --ignoreConfig --strict,跑完即删,探针文件不在工作树内):第 6 行是把范例的 AST 数组当
where的值;第 10 行是范例的filters:键。对照组(对象形where: { amount: { $gt: 50000 } })无报错。2.3
'opportunity'—— 违反同文件第 59 行第 59 行原文:「All HotCRM business object names MUST use the
crm_prefix and the prefix MUST be written explicitly in source」,且明确 hookobject:/ actionobjectName:等全用带前缀名、No automatic prefix injection by the runtime。pnpm validate报 17 Objects,实测全为crm_*;真实名是crm_opportunity。范例与规则相距 5 行却自相矛盾。3. 新范例
三处逐一对上:
broker→ctx.api;filters→where;opportunity→crm_opportunity。谓词的「值」也换了形状,这一点 issue 未提,说明如下:原范例的
[['amount', '>', 50000]]是 filter AST 数组形,它在平台层合法(spec/src/data/filter.zod.ts:537,592有[field, operator, value]的 lowering,'>'→$gt见AST_OPERATOR_MAP:405),所以我没有把它列为「第四处失真」。但它 (a) 不可赋给HookQuery['where'](见上 TS2322),(b) 本仓 60 处一等公民调用零使用,全部用对象形($in/$nin/$lt/$lte/$gte实测在用)。范例的职责是给 agent 抄,所以取实际在用的形状。$gt是ComparisonOperatorSchema的正式成员(filter.zod.ts:65,z.union([z.number(), z.date(), FieldReferenceSchema]),50000是 number),语义与原范例的amount > 50000等价。范例下方补了两条直接说明句(
ctx.api是唯一 surface、hook 侧 cast 一次;谓词键只有where及其静默失败方式 + 指向本仓那份 changeset)。只动了第 2 条这四行所在的块,条目 1、3 与其余全文一字未改。4. 改动面
只有两个文件,15 增 2 删:
AGENTS.md:第 61-64 行的 ObjectQL 条目。.changeset/agents-md-objectql-example.md:'hotcrm': patch,站内路径一律反引号(docs(guides): import-and-export 按实测改写——导入向导在列表视图而非 Setup → Data,Salesforce 迁移标注未落地 (#763) #797:link-check 会扫.changeset)。src/**、content/**、content/docs/releases/、@objectstack/*依赖版本均未触碰。#856(action 后缀 3:1)已单独立单,本 PR 不动。5. 验证(全量,共享锁 +
NODE_OPTIONS=--max-old-space-size=4096)AGENTS.md不在多数闸门的扫描面内,全量跑是为了证明基线没被我破坏:pnpm validate✓ Validation passed (1654ms)·17 Objects 344 Fields·24 Flows—— 5 条 author-time warning 为基线既有(4 条 approval 空审批人 + 1 条 crm_campaign_member group),与本 PR 无关pnpm typechecktsc --noEmit无输出pnpm build✓ Build complete (1561ms)·dist/objectstack.json (1921.3 KB)pnpm test -- --maxWorkers=2Test Files 66 passed (66)·Tests 1587 passed | 1 skipped (1588)pnpm lint13 warning(s), 14 suggestion(s)—— 基线既有pnpm hygiene✓ no raw control bytes in first-party files·✓ source hygiene cleantest 输出里 source-hygiene 元测试的
✗ source hygiene: scanned director(y|ies) missing: .changeset是该元测试的预期 stderr(它断言「扫不到东西的检查比没有检查更糟」),套件仍全绿。控制字节自扫(超出
check-source-hygiene的盲区):6. 顺带发现
无新增。通读时留意到的两处均已有单:#856(
*.action.ts单复数 3:1,finding)、以及本 PR 已修的 #855 自身。未发现其它需另立的过期指令。Generated by Claude Code