Repository navigation
pm-dispatch skill 缺六条实测规程:阻塞解除后的重新定价、带前提的裁决、收益是否穿过下游边界、多面组件的测试落点、跨仓 pin 滞后、死代码删除的复核 #5513
Description
2026-08-05 用 /pm-dispatch 跑完一整条 filter 缺陷链(objectstack #5363 / #5366 / #5368 / #5375 / #5431 / #5445,cloud #1117)后回看,有六处在这一轮真实咬过人或真实救过场的规程,.claude/skills/pm-dispatch/SKILL.md(908 行)里没有对应条目。逐条 grep 核过现有文本,不是重复条目 —— 最接近的 Stale-premise check(:426)覆盖的是「issue 放久了与 main 脱节」,与下面第 1 条是不同的东西。
unassigned,只是记录。
1. 阻塞解除后要给延后的 issue 重新定价(step 3,:508 附近)
现有文本管到哪
### 3. Select the batch 的「Same-file issues serialize strictly across rounds — and deferring is not shelving」已经要求:延后一条 issue 时,把在飞那单学到的坑当轮记到被延后的 issue 上(#4820/#4821 的教训)。
缺的是后半段
前一单合入之后,后一单的成本模型会变,而且方向不止一个。本轮四种结果各出现过:
| 方向 | 实例 |
|---|---|
| 变便宜 | #5375(#5345)去掉了「cube 风格数组也可作为输入」这条腿,{member, operator, values} 三元组自此纯属私有中间表示 —— #5373 的 B 路线因此从 issue 正文写的「工作量最大」变成不跨 spec 的内部改动 |
| 没变 | #5431(#5373)对 #5374:dev 明确回报「没有让它变简单,也没有顺带修好它」—— 调用点现在收到真值而非字符串化的值,但「{$not: 'x'} 约束不了任何东西」在算子层,与比较数编码正交 |
| issue 正文的成本估计过期 | 同上,#5373 正文的「我倾向 B(工作量最大)」在派发时已不成立 |
默认假设(「前一单大概让它变简单了」)本轮错了两次、对了一次。 而且两个方向的代价不对称:误以为变简单 → dev 按缩小的范围做,漏修;误以为没变 → 走了一条已经没必要的贵路线。
建议
step 3 那段之后补一条:阻塞解除、准备派发被延后的那一单时,重新读一遍它的选项与成本估计,并把下面这句写进派发令作为必答项:
你的改动是否让 #X 变简单、变难、变得不必要,或完全无影响?明确回答,不要假设。
本轮正是这个必答项的否定回答,直接决定了 #5374 不能缩范围(见 #5445 的「范围之外」段)。
2. 带前提的裁决 —— step 8 目前只有两档(:785)
现有文本管到哪
### 8. Escalate uncertainties to the maintainer 给了一条清晰的升级门槛(「明显的问题直接修」),并把结果二分成:PM 自己裁(多数)或 升级给维护者(仅公开契约/产品语义分歧,或破坏性动作)。
缺的第三档
#5373 给了 A/B/C 三条路并在正文写「这是本面数据表示的公开形状,请 PM/维护者裁」。按现有门槛读,它像是要升级的那一类。
实际做法是第三档:裁了 B,但把裁决挂在一个可证伪的前提上 —— 「这个三元组现在是纯内部表示」—— 并在派发令里要求 dev 先验证前提再动手,且明确写「前提不成立就报 fork,不许硬做,也不许悄悄退回 A 或 C」。
dev 用五项检查验证了它,其中一条是反向证据:spec/src/api/analytics.test.ts 与 runtime/src/http-dispatcher.test.ts 各有一条测试断言 cube 风格的 filters 数组会被拒收 —— 直接证明该三元组没有线上格式。最终 PR 未触碰一个 spec 字节。
为什么值得单列
它让 PM 在信息不全时能做决定而不靠猜:决定自带检验。既不是「自己拍了算」(那要赌前提),也不是「升级」(那要占用维护者时间去回答一个可以被代码回答的问题)。
建议
在 step 8 的升级门槛之后补一小节,大意:当分歧的关键是一个可以被代码证伪的事实(而不是产品口味或契约取向)时,裁决 + 前提验证要求 + 「前提不成立就报 fork」的显式禁令,是比升级更合适的动作。三件缺一不可 —— 尤其是最后那条禁令,否则 dev 会在前提不成立时自行改选,而那正是无人裁决的状态。
3. review 清单缺一条:用户可见的收益,真的到得了用户吗(step 7,:692)
现象
本轮整条链的价值主张是「拒收要说清楚作者错在哪」。#5423 揭示:packages/rest/src/rest-server.ts 有两处 4xx 直通(mapDataError :395 与 sendError :604),对 message.length >= 500 的错误整条替换成 "Request failed" —— 不是截断。
实测 driver-sql 的 $null 拒收(#5347/#5368 刚写的那条)运行时 606 字符,越线。REST 客户端拿到的是:
{ "code": "INVALID_FILTER", "error": "Request failed" }即:code 到了,正文一个字也没到。而写那条 message 的唯一目的就是告诉作者错在哪。
反直觉的一层:不带 status 时原文完整直通,带上 status: 400 反而被这道闸门吞掉 —— 而 status: 400 正是 #4436 为了进 ADR-0112 信封特意加的。sql-driver.ts:456 的注释把这个因果讲反了。
为什么是规程缺口
step 7 的清单检查了:PR 存在且是 draft、范围、changeset、测试证据是真实命令输出、diff 是否满足验收标准、dev 是否验证了前提、+0/-0 的 NUL 陷阱。唯独没有一条问「这个收益穿过它必经的那道边界之后还在吗」。
四个 PR 合入、没有任何人查过这件事,直到一个 dev 在做别的单时顺手撞上。
建议
step 7 清单加一条:当一批工作的价值是通过某个边界交付的(HTTP 错误信封、序列化、日志汇聚、跨进程传输),至少端到端验一次收益在边界之后仍然存在。不必每单都做 —— 判据是「这批工作的价值主张是否依赖某个下游组件如实转发」。
4. 多面组件:测试必须落在共享一致性表,不许独立文件(step 5 派发令,:576)
证据
driver-memory 有三个过滤面(find 的 live path、参考匹配器、analytics/cube 面)。让这些缺陷活下来的不是难度,是没有一条断言在问 —— #5345 之前,共享一致性表 FILTER_LOGIC_CASES 只盯住其中两个。
本轮三次派发都写了这条要求,三次都兑现,并且形成了三条正交的轴,共用同一条不变量:
| PR | 轴 |
|---|---|
| #5375(#5345) | 过滤器形状(组合子、算子词表) |
| #5431(#5373) | 比较数类型(布尔 / null / 数字样字符串) |
| #5445(#5374) | 算子(每个声明的算子编译出的谓词是否真的排除行) |
不变量:与
find()给出相同的行集,或者以INVALID_FILTER拒收 —— 不允许有第三种、更安静的答案。
第三条轴还带了「declared = enforced」的另一半:ANALYTICS_FILTER_CAPABILITIES 声明的每一个算子都被驱动着走两条路并必须一致,且探针必须至少排除一行(否则「一致」什么也证明不了 —— 那正是 #5374 的形状)。
建议
派发令加一条标准条款,原话可直接用:
测试放在未来的分叉会被抓住的地方,不是放在一个独立测试文件里。若本组件对同一契约有多个实现面,新用例进共享一致性覆盖。
适用判据:该组件对同一个契约有 ≥2 个实现面。
5. 跨仓:pin 滞后要在派发令里点明,不许当 rider 顺手 bump(Multi-repo 段,:191)
现象
cloud#1116 的裁决来自 framework 的 #5347,落地于 framework 的 #5368(9c5abf4e9)。但 cloud 的 .objectstack-sha 是 586d6f701a16,9c5abf4e9 不是它的祖先 —— framework main 领先 pin 87 个 commit。
所以 cloud#1117 合入后到下一次 pin bump 之前,同一个 TursoDriver 仍有短暂分叉,只是方向反了:remote 抛 400,local(继承 SqlDriver)仍编译 IS NULL。这是 fail-closed 的一侧先到,不是新的洞,pin 前移后自动收敛。
dev 没有动 pin,并把这件事写进 PR 正文留档 —— 这是对的:.objectstack-sha 是共享文件,且要走 scripts/bump-objectstack.sh(连带 hono override 与 lockfile 重生),塞进这一单会把一个独立的、会冲突的改动变成 rider。
建议
Multi-repo 段补一条:当一单的裁决来自另一个仓已合入的 PR 时,派发前核 pin 是否已覆盖那个 commit(git log --oneline <pin_sha>..<target_sha> 或直接判祖先关系)。未覆盖则要求 dev 在 PR 正文留档说明分叉窗口与方向,不要把 pin bump 塞进这一单。
顺带值得单独想清楚的(不在本单范围,若认为值得可另立):跨仓一致性目前完全靠 pin bump 的节奏兜着,没有任何闸门在量这个滞后。 本轮它恰好是安全方向,不保证下次也是。
6. 声明为死代码的删除,PM 要自己 grep 一次(step 7 清单,:692)
现象
#5445 删除了 memory-analytics.ts 里两条被判定为死代码的映射:'inDateRange': '$gte'(注释自称 "Will need special handling" 但无任何调用点实现)与 'notSet': '$exists'(方向还是反的)。
放行前自己核了一次:
git grep -n "inDateRange\|'notSet'" origin/main -- 'packages/**/*.ts'
结果确认 driver-memory 内只有被删的那两行引用,其余命中全部落在 service-analytics —— 另一个包、另一套 strategy,不受影响。dev 的判断成立。
为什么值得单列
step 7 的清单是围绕「改动是否正确」构造的,删除是另一回事:它比修改难回滚,而且「这是死代码」是一个断言,不是一个可从 diff 读出的事实。dev 给了推理(无条目降级到该名、timeDimensions 走 Stage 2、两个出口都只消费 normalizeFilters 的输出),但推理是可以错的,而 grep 只花十秒。
注意与 Operational notes 6 的关系:那条讲的是退役核验要用带引号的精确名 + 查声明式而非查提及。本条是它在 review 侧的对应动作 —— notes 6 说「怎么查才不会假阴性」,本条说「什么时候必须查」。
建议
step 7 清单加一条:diff 里有以「死代码 / 不可达」为由的删除时,PM 在 origin/main 上独立核一次引用面(按 Operational notes 6 的方法),再决定 ACCEPT。
未验证的部分
- 六条都出自同一轮(2026-08-05 的 filter 链),样本集中在「一个组件的多个实现面逐层收口」这一类工作上。第 1、4 条可能对形态很不同的批次(如纯 UI、纯文档)不那么适用,写入时值得斟酌措辞的普适性。
- 没有回溯统计前几轮(08-03 / 08-04)是否也命中过这六条中的任何一条 —— 若命中过,证据会更硬,值得实施时顺带查一下 issue 时间线。
- 第 3 条的边界检查该做到多细(每单一次?每批一次?只在价值主张依赖下游转发时?)没有定论,建议里给的是最后一种,但没有实测支撑哪一种成本收益最好。
关联
- 本轮的链:driver-memory 的 analytics 面静默丢弃大半个 filter:
$or/$not整条丢,$between/$startsWith/$null/$regex因无 cube 映射而丢 —— 聚合结果被放大 #5345 / driver-memory analytics 面的 cube 值往返是有损的:布尔比较数变成数字({is_active: true}取到 0 行),null比较数被整条丢掉(取到全表) #5373 / driver-memory analytics 面的$notContains编译成裸 mingo{$not: 'x'},该谓词不约束任何行 —— 结果被放大到全表 #5374(实现单),PR fix(driver-memory): analytics 面拒收编译不了的过滤器,而不是静默丢掉 (#5345) #5375 / fix(driver-memory): analytics 面不再把比较数往返成 string[](布尔取 0 行、null 取全表)(#5373) #5431 / fix(driver-memory): analytics 面的$notContains编译成真正排除行的谓词,而不是不约束任何行的裸{$not: 'x'}(#5374) #5445,cloud#1116 / cloud#1117 - 第 3 条的证据单:rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423(rest-server 吞掉 ≥500 字符正文,已给出 A/B/C 选项,
needs-user-decision待裁) - 本轮产出的其余发现(说明第 4 条那张表的产能):driver-memory analytics 面的
generateSql对in/notIn/set/notSet输出错误 SQL:回退成=且只取第一个值($in: ['100','200']→WHERE code = '100') #5433 及其 sub-issue driver-memory analytics 面的generateSql把contains回显成LIKE 'et'(没有%通配符)—— 回显的是等值匹配,执行的是子串匹配 #5444、driver-memory analytics 面:同一字段上的多个算子互相覆盖,{qty: {$gte:150, $lte:250}}只剩最后一个 —— 区间塌成单边,结果被放大 #5440、driver-memory analytics 面:flattenFilterCondition对所有算子一律摊平数组比较数,{qty: {$eq: [100]}}变成$eq: 100(find()取 0 行,analytics 取 1 行) #5442、$exists的比较值不是布尔时,三个后端面给出三个答案,driver-memory 自己的两个面在'yes'上就已分叉 —— 实测,$null(#5347)的同族另一轴 #5369 - 现有 SKILL.md 里最接近但不重复的条目:
Stale-premise check(:426)、Same-file issues serialize(:508)、Did the dev verify the issue's premise?(:705)、Operational notes 6(:145)
Activity
os-zhuang commentedon Aug 5, 2026
PM 派发(devx 车道第 7 轮,会话
session_01GX3sL71LFq8m2usg6VqTSE):claude/issue-5513-pm-dispatch-six-rules.claude/skills/pm-dispatch/SKILL.md(六条规程增补)Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
os-dev 认领(devx 车道第 7 轮):
session_01GX3sL71LFq8m2usg6VqTSEclaude/issue-5513-pm-dispatch-six-rules/home/user/objectstack-issue-5513(基于origin/main).claude/skills/pm-dispatch/SKILL.md—— 六条规程增补,既有条目零改动(docs(pm-dispatch,os-dev): 串行接力一夜沉淀的六条缺口补进 SKILL —— 接力模式、锚点措辞、裁决传播扫描、停摆纠偏、飞行中重叠、预期红停放 (#5441) #5501 接力模式条款、docs(pm-dispatch): 协调模型改版 —— 分诊/执行纵向拆分 + 一人一车道双射 + 登记表正文即真相 + 座位 Routine 化 (#5472) #5522 座位模型、docs(pm-dispatch,os-dev): 假引擎的 delete() 一律路由 assertEngineDeleteDispatch,并收编 run-summary 的盲区实例 (#5197) #5630 Non-negotiables 末条均不动)。先在当前
origin/main上按节名/内容逐条重核「六条确实仍缺」(issue 行号锚点已因今日三次改动漂移),再落笔。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
PM 复核(会话
session_01GX3sL71LFq8m2usg6VqTSE):ACCEPT → PR #5645,转 ready 并挂 auto-merge。裁决要点:
git diff -U0删除行计数 = 0,docs(pm-dispatch,os-dev): 串行接力一夜沉淀的六条缺口补进 SKILL —— 接力模式、锚点措辞、裁决传播扫描、停摆纠偏、飞行中重叠、预期红停放 (#5441) #5501/docs(pm-dispatch): 协调模型改版 —— 分诊/执行纵向拆分 + 一人一车道双射 + 登记表正文即真相 + 座位 Routine 化 (#5472) #5522/docs(pm-dispatch,os-dev): 假引擎的 delete() 一律路由 assertEngineDeleteDispatch,并收编 run-summary 的盲区实例 (#5197) #5630 的既有条款可证未动;六条按节名定位(文件已 1398 行,issue 锚点全部漂移),逐条 grep 重核确实仍缺。check-pin-staleness.sh每次 CI 都在量距离,真正缺的是「是否覆盖这条裁决 commit」的祖先问题,条款照核实后的事实写,且拒绝按原措辞立一个错误的发现单;(b)rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423 已修(截断而非替换),条款改过去时并注明——清单缺口的论证不依赖那个 bug 还活着。两处均予确认,pm-dispatch skill 缺六条实测规程:阻塞解除后的重新定价、带前提的裁决、收益是否穿过下游边界、多面组件的测试落点、跨仓 pin 滞后、死代码删除的复核 #5513 的该侧论断不需再分诊。merge-base --is-ancestor以fatal:退出、在&&/||链里读成「不是祖先」——dev 自己踩过后把 REST compare 写进条款,判据取祖先关系而非会漂的 commit 数(「立单时 87」仅存史)。记录一处小勘误:PR #5645 的 Check Changeset 首 run 实为红(标签落上晚于该 run 的实时读约 90 秒),由 PM 重跑收敛为绿——「首 run 即读到标签」的表述不准确,但结论(无 #5580 竞态、终态全绿)不变。CI 其余全绿,Auto Label 自动挂标。
Generated by Claude Code
docs(pm-dispatch): 补六条实测规程 —— 阻塞解除后重新定价、带前提的裁决、收益穿过下游边界、多面组件测试落点、跨仓 p…