Skip to content

Commit 0c12b83

Browse files
committed
docs(pm-dispatch): 过滤/谓词语义裁决派发必须枚举编译面清单并逐面申报 (#6410)
#5930 裁决的流程半边:一个 `FilterCondition` 语义由 5 个互相独立的编译器/求值器 承载,每条语义裁决成本 ×5,而漏面反复复发 —— #5146 漏面 2 变成 #5903、#5326 / #5335 各花一圈补齐面 3/4、PR #5962 的 inventory 漏掉 HAVING 面变成 #5905。 在 step 5 派发提示词指引区加一条常设条款:凡过滤/谓词语义裁决,派发令必须枚举 完整编译面清单,dev 的 PR 必须逐面申报「已改 / 本就合规 / 明确不在范围(带理由)」; 清单本身由 PR 维护,与域表同一纪律。清单为逐面实测(file:line @ main 48f98b0), 非照抄卡片 —— 路径已搬家(驱动进 packages/drivers/、服务进 packages/services/)。 发布件按 #5451 route B 加泛化版:只写机制(「一个语义由 N 个独立实现承载时,派发 必须枚举全部承载面并要求逐面申报」),不带本仓 issue 号与文件名。 三轴决策框架未触碰:4 份拷贝逐字节相同(diff/cmp/md5 三重取证)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
1 parent 48f98b0 commit 0c12b83

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,56 @@ prompt:
13831383

13841384
一句话:**一个在缺信封的实现上无法转红的拒收用例,读起来是覆盖,实际不是。**
13851385

1386+
**过滤 / 谓词语义裁决:派发令枚举完整的编译面清单,PR 逐面申报 —— 派发令的标准
1387+
条款(#5930 裁决的流程半边)。** 适用判据:本单会**改变一条过滤 / 谓词语义**(算子
1388+
的 NULL 处理、组合子恒等、比较数形状、算子词表……)。满足时派发令**把下面那张表逐面
1389+
抄进去**,并带这一句(原话):
1390+
1391+
> 本单改的这条语义由**多个互相独立的编译器 / 求值器**各自实现。派发令列出的**每一
1392+
> 面**都必须在你的 PR 正文里有一个结论:**已改** / **本就合规**(给出证据)/
1393+
> **明确不在范围**(给出理由)。⛔ 不许静默略过 —— 评审把「没提到的面」一律读作
1394+
> 「漏掉的面」,不读作「不需要改」。
1395+
1396+
**这条防的不是「做错」,是「做对了一部分然后以为做完了」。** 一个 `FilterCondition`
1397+
语义由 **5 个互相独立的实现**承载(下表)⇒ 每条语义裁决的成本 ×5,而漏面**反复
1398+
复发**,三次都留在代码注释里:
1399+
1400+
- **#5146 → #5903**:裁决只落到面 1,面 2 是**不继承面 1 的独立编译器**,于是同一个
1401+
驱动的两种连接模式对同一条过滤给出两种答案。现场记录在
1402+
`driver-turso/src/remote-transport.ts:1731`:「LOCAL mode inherits that fix
1403+
(`TursoDriver extends SqlDriver`), this independent compiler inherited none of
1404+
it」。
1405+
- **#5326 / #5335**:面 3 与面 4 各**又花一圈**才对齐,记录在
1406+
`spec/src/data/filter.zod.ts:370`。
1407+
- **#5905**:#5298 的裁决由 PR #5962 落到 driver-sql / formula / service-analytics
1408+
与 conformance 表,**唯独漏了 HAVING 面** —— `objectql/src/having-filter.ts:37` 的
1409+
原话是「was not in that PR's inventory, which left this file as the lone
1410+
holdout」。**「inventory」这个词本身就是本条款的缺席证明**:那次派发确实有一份清
1411+
单,只是它不完整,而没有任何机制要求它完整。
1412+
1413+
三次都不是难度问题,是**没有一份清单在问「还有几面」**。
1414+
1415+
编译面清单(逐面实测 @ `main` `48f98b0`,2026-08-07):
1416+
1417+
| # | 面 | 落点(file:line) | 备注 |
1418+
| --- | --- | --- | --- |
1419+
| 1 | `driver-sql` | `packages/drivers/driver-sql/src/sql-driver.ts:7083`(`applyFilterCondition`) | `driver-sqlite-wasm`(`sqlite-wasm-driver.ts:67`)与 **local 模式**的 `driver-turso`(`turso-driver.ts:174`)都 `extends SqlDriver`,**靠继承共用这一面**,不单独算面 |
1420+
| 2 | turso RemoteTransport | `packages/drivers/driver-turso/src/remote-transport.ts:1526`(`private buildWhereSQL`) | **独立编译器,不继承面 1** —— 一个驱动的两面,由连接模式选中哪面 |
1421+
| 3 | service-analytics read-scope-sql | `packages/services/service-analytics/src/read-scope-sql.ts:259`(`compileScopedFilterToSql`) | RLS 读侧 |
1422+
| 4 | service-analytics filter-normalizer | `packages/services/service-analytics/src/strategies/filter-normalizer.ts:1235`(`lowerAnalyticsWhere`) | analytics / cube 侧 |
1423+
| 5 | `formula` | `packages/formula/src/matches-filter.ts:73`(`matchesFilterCondition`) | RLS 写侧 `check` 与公式求值;JS 两值语义的基准面 |
1424+
| 半面 | objectql `having-filter` | `packages/objectql/src/having-filter.ts:92` / `:98`(`applyHaving` / `matchesHaving`) | 聚合**后**过滤。算半面是因为词表是子集,**但申报义务不打折** —— 它是**唯一没有 conformance 表覆盖的面**(`FILTER_LOGIC_CASES` 不驱动 HAVING 路径),所以漏了它连门禁都不会红 |
1425+
| 冻结 | `driver-memory` / `driver-mongodb` | — | #5499 冻结投入:**pin-annotate,不翻转**。冻结面仍要申报,结论是「不在范围 + #5499」。现场注释见 `read-scope-sql.ts:176`、`having-filter.ts:41` |
1426+
1427+
**这张表本身由 PR 维护 —— 与域表同一纪律。** 增删一面(新驱动、新求值器、某面被合并
1428+
或退役、冻结状态变化)的那个 PR 顺手改这里,不留给下一次裁决重新数。清单**会**过期是
1429+
必然的,清单**没有维护者**才是缺陷。
1430+
1431+
⚠️ 派发前复核一遍再抄,⛔ 不要凭这张表的记忆填派发令:本仓的包路径搬过家(驱动进
1432+
`packages/drivers/`、服务进 `packages/services/`),行号更是每天在动。一条够用的复核
1433+
串:`grep -rn 'matchesFilterCondition\|buildWhereSQL\|compileScopedFilterToSql'
1434+
packages --include=*.ts | grep -v node_modules`。
1435+
13861436
**Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an
13871437
honest "the premise is dead" cheap to return.** Step 1's stale-premise check
13881438
is the PM's sample; the dev's verification is the real thing, so the prompt

‎skills/objectstack-pm-dispatch/SKILL.md‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,43 @@ Non-negotiables for this dispatch:
414414
Return ONLY the JSON report defined in the operating procedure.
415415
```
416416

417+
#### One semantics, N independent implementations — enumerate them in the prompt
418+
419+
**Applicability:** the issue changes a *semantic rule* — how an operator, a
420+
predicate, a comparison, or an absent/empty value is interpreted — and that rule
421+
is implemented **more than once**, by compilers or evaluators that share no code.
422+
Query filters, expression languages, permission predicates and serialization
423+
formats all tend to grow this shape as a project adds backends.
424+
425+
When it applies, the dispatch prompt carries an **explicit inventory of every
426+
implementing surface**, and requires the agent to give a verdict **for each one**
427+
in its PR body: **changed** / **already conformant** (with evidence) /
428+
**explicitly out of scope** (with a reason). A surface the PR never mentions is
429+
reviewed as one that was *missed*, not as one that needed no change.
430+
431+
Why this is worth a standing clause instead of case-by-case judgment: the failure
432+
it prevents is not "implemented it wrong", it is **"implemented part of it and
433+
believed the work was finished"**. That failure is invisible at review time — the
434+
diff is correct and the tests are green, while the untouched surfaces keep
435+
answering the old way until a user hits the divergence. The cost scales with the
436+
count: a semantics carried by N implementations makes every ruling an N-part
437+
task, and the parts that get skipped are exactly the ones nobody wrote down.
438+
439+
Two disciplines keep the inventory trustworthy:
440+
441+
- **The inventory is maintained by PR.** Whichever change adds, retires or merges
442+
an implementing surface updates the list in that same PR. An inventory going
443+
stale is inevitable; an inventory with **no owner** is the defect.
444+
- **Re-verify before pasting.** Paths move and surfaces get added between
445+
rulings, so re-derive the list from the code at dispatch time rather than
446+
copying the previous prompt. An inventory that was right last month and is
447+
pasted unchecked reintroduces the very miss it exists to prevent.
448+
449+
Surfaces that are **deliberately frozen** (deprecated backends, formats kept only
450+
for compatibility) stay in the inventory. Their verdict is "out of scope —
451+
frozen", recorded rather than silently absent: a reader cannot otherwise tell a
452+
frozen surface from a forgotten one.
453+
417454
#### Dispatch backends
418455

419456
**`mode:subagent` (default).** Sub-agents inside the PM's own session. Reports

0 commit comments

Comments
 (0)