Skip to content

Commit 3e0f7e4

Browse files
hotlongclaude
andauthored
docs(skills): 拒收类用例的最低断言集 = code + status,不是「它抛了」(#6144) (#6233)
三份 os-dev / pm-dispatch 文本的验证纪律章节同步落条款,动机写死 #6142 实测的两种 失明机制(裸 Error 恒绿 / 从不抛的 transport 红得不指向缺陷)。 - .claude/agents/os-dev.md:反向验证块之后新增 "Rejection-class cases assert the envelope, not the throw"(dev 侧机制条款,英文,随该文件语种)。 - .claude/skills/pm-dispatch/SKILL.md:step 5 新增派发令标准条款(带适用判据 + 原话引文),step 7 新增复核侧对账条目 —— 按该文件既有的「派发条款 + 复核条目」 配对惯例(pin 翻转那条即此形)。 - skills/objectstack-pm-dispatch/SKILL.md:step 7 复核判据 + 内嵌 dev 模板的独立 段落(两处,与该文件既有验证纪律项的分布一致);按 #5451 route B 泛化,不带本仓 issue 号、不出现 role 一词。 ⛔ 未触碰三轴决策框架:check:skill-frame-sync 改前改后均绿(4 copies / 3 axes / binding sentence present in all 4)。 ⛔ 未改动任何测试套件、驱动代码或其他 skill —— 存量拒收套件的回填是另一张单 (#6144 分诊的范围闸)。 Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 01faeb1 commit 3e0f7e4

3 files changed

Lines changed: 74 additions & 0 deletions

File tree

.claude/agents/os-dev.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,32 @@ all real:
206206
forcing the template; #4984 is the family origin — fixtures spelling
207207
rejected aliases kept the tests green while the rule was dead).
208208

209+
**Rejection-class cases assert the envelope, not the throw.** For any case whose
210+
point is that bad input is *refused*, the minimum assertion set is the error's
211+
**`code` AND `status`** (the ADR-0112 envelope). `expect(...).toThrow()` /
212+
`rejects.toThrow()` on its own is not a rejection test: it carries one bit where
213+
the defect has two, and PR #6142 (#6050) measured both ways it goes blind —
214+
opposite directions, same hole:
215+
216+
- **A bare `Error` ⇒ permanently green.** Deleting the new refusal gate turned
217+
22 of `driver-sql`'s 28 cases red, and *most* of those reds were the driver
218+
throwing knex's bare `Undefined binding(s)` — an `Error` whose `code` and
219+
`status` are both `undefined`. The unfixed driver already throws; only the
220+
envelope is missing. A throw-only assertion therefore stays **green on the
221+
very driver the issue targets**.
222+
- **A transport that never throws ⇒ red, but pointing away from the defect.**
223+
The same deletion turned 20 of `driver-turso`'s 29 remote cases red, and all
224+
20 failed by *answering* — that transport never throws. A throw-only
225+
assertion reports "the promise resolved", which names the absence of a throw
226+
and never the absence of an envelope, so it cannot separate "refused with the
227+
wrong envelope" from "did not refuse at all" — and those are exactly the two
228+
defects.
229+
230+
Where the wording is itself contract (#5240, one condition ⇒ one wording),
231+
assert the message's first sentence **on top of** `code`+`status`, never instead
232+
of them. A rejection test that cannot go red on a missing envelope reads as
233+
coverage and is not.
234+
209235
**Key-vs-value reachability criterion.** Match a fixture guard's assertion to
210236
what the rule guards. Guarding that a **key** is a real authoring surface →
211237
assert the schema reports no `unrecognized_keys` on the fixture. Guarding a

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,28 @@ prompt:
11951195
实现面逐层收口」这类工作上,没有第二个实现面的活(纯 UI、文档、单面脚本)这条无处可
11961196
绑,该省掉而不是改写它。
11971197

1198+
**拒收类用例的最低断言集是 `code` + `status`,不是「它抛了」—— 派发令的标准条款。**
1199+
适用判据:本单会**新增或改写拒收 / 错误类用例**(验收点里出现「应当被拒收」的活)。
1200+
满足时派发令带这一句(原话):
1201+
1202+
> 拒收类用例最低断言**错误的 `code``status`**(ADR-0112 信封)。
1203+
> `expect(...).toThrow()` / `rejects.toThrow()` 单独使用**不构成**拒收测试;措辞本身
1204+
> 是契约时(#5240「一个条件一种措辞」),首句断言**加在** `code`+`status` **之上**,
1205+
> 而不是代替它。本条约束你**新写或改写**的用例 —— 顺手回填存量套件不在本单范围内。
1206+
1207+
出处是 #6142(#6050)的反向验证实测。两种失明机制方向相反,同一个洞:
1208+
1209+
- **`Error` ⇒ 恒绿。** 删掉拒收闸后 `driver-sql` 28 例红 22,**多数红在抛出 knex 的
1210+
`Undefined binding(s)`** —— 一个 `code` / `status` 均为 `undefined` 的 Error。未修的
1211+
驱动本来就抛,缺的只是信封:只断言「它抛了」的用例,**在本单所针对的那个驱动上保持
1212+
绿色**
1213+
- **从不抛的 transport ⇒ 红,但红得不指向缺陷。** 同一次删闸,`driver-turso` remote
1214+
29 例红 20,**20 个全部**红在「本该拒收却编译出了 SQL」—— 该 transport 从不抛。只断言
1215+
抛出的用例在这里报的是「promise 没有 reject」,说的是**没抛**而不是**没信封**,分不开
1216+
「拒收了但信封错」与「根本没拒收」—— 而这正是这一族的两个缺陷。
1217+
1218+
一句话:**一个在缺信封的实现上无法转红的拒收用例,读起来是覆盖,实际不是。**
1219+
11981220
**Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an
11991221
honest "the premise is dead" cheap to return.** Step 1's stale-premise check
12001222
is the PM's sample; the dev's verification is the real thing, so the prompt
@@ -1478,6 +1500,12 @@ against the report's own claims:
14781500
genuinely invalid shapes are still there (step 5's two lines). #5365 slipped
14791501
through exactly this review layer and was caught by CI instead: CI does catch
14801502
it, at the price of one extra lap.
1503+
- **拒收类用例的绿,是不是「它抛了」的绿?** 判据:本单验收点含「应当被拒收」。抽查
1504+
diff 里的拒收用例有没有断言 `code``status`(ADR-0112 信封)—— 只写 `toThrow()` /
1505+
`rejects.toThrow()` 的用例,在**未修实现本来就抛裸 Error** 的那一族上恒绿(#6142
1506+
实测:`driver-sql` 删闸后 22 红中多数是裸 knex Error,`code` / `status`
1507+
`undefined`),于是「28 例全绿」这种报告读起来是覆盖、实际证不了拒收。缺断言判
1508+
REWORK 补齐,而不是接受绿色输出。本条是 step 5 那条标准条款在复核侧的对账。
14811509
- **Did the dev verify the issue's premise?** The report's
14821510
`premise_still_valid` field makes the answer explicit — a `false` there
14831511
reopens triage rather than failing review. A report that falsifies the

skills/objectstack-pm-dispatch/SKILL.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,12 @@ against the report's own claims**:
390390
present.
391391
- Test evidence in the report shows the **actual commands and passing output**,
392392
not a bare "tests pass".
393+
- Rejection-class tests in the diff — those whose point is that bad input is
394+
**refused** — assert the error's identity (its `code` and `status`, or
395+
whatever fields the project's error envelope declares), not merely that
396+
something was thrown. A throw-only assertion is green on any producer that
397+
already throws a bare error, which is what an unfixed producer usually does,
398+
so it reads as coverage while being unable to fail on the defect it names.
393399
- The diff plausibly satisfies the issue's acceptance criteria.
394400

395401
Verdict per issue:
@@ -586,6 +592,20 @@ Definition of done, in order:
586592
written in the language the repository's PRs use.
587593
- Tear down anything you started (dev servers, temporary processes) by PID.
588594
595+
Rejection-class tests assert the envelope, not the throw. For any test whose
596+
point is that bad input is REFUSED, the minimum assertion set is the error's
597+
identity — its `code` and its `status`, or whatever fields your project's error
598+
envelope declares. "It threw" alone (`expect(...).toThrow()`,
599+
`rejects.toThrow()`) is not a rejection test, and it goes blind in two opposite
600+
directions. An unfixed producer usually throws ALREADY — a bare error carrying
601+
neither field — so the assertion stays GREEN on the very defect the test names.
602+
And a producer that answers instead of throwing fails it with "nothing was
603+
thrown", naming the absence of a throw rather than the absence of an envelope,
604+
so it cannot separate "refused with the wrong envelope" from "did not refuse at
605+
all". Assert the message's wording on top of the envelope fields only where the
606+
wording is itself contract — never instead of them. A rejection test that
607+
cannot go red on a missing envelope reads as coverage and is not.
608+
589609
When to STOP instead of coding. If the issue underspecifies a decision that
590610
shapes a public contract — a schema, API shape, naming, metadata semantics —
591611
or two readings of the issue lead to different architectures: make no guess,

0 commit comments

Comments
 (0)