Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .claude/agents/os-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,32 @@ all real:
forcing the template; #4984 is the family origin — fixtures spelling
rejected aliases kept the tests green while the rule was dead).

**Rejection-class cases assert the envelope, not the throw.** For any case whose
point is that bad input is *refused*, the minimum assertion set is the error's
**`code` AND `status`** (the ADR-0112 envelope). `expect(...).toThrow()` /
`rejects.toThrow()` on its own is not a rejection test: it carries one bit where
the defect has two, and PR #6142 (#6050) measured both ways it goes blind —
opposite directions, same hole:

- **A bare `Error` ⇒ permanently green.** Deleting the new refusal gate turned
22 of `driver-sql`'s 28 cases red, and *most* of those reds were the driver
throwing knex's bare `Undefined binding(s)` — an `Error` whose `code` and
`status` are both `undefined`. The unfixed driver already throws; only the
envelope is missing. A throw-only assertion therefore stays **green on the
very driver the issue targets**.
- **A transport that never throws ⇒ red, but pointing away from the defect.**
The same deletion turned 20 of `driver-turso`'s 29 remote cases red, and all
20 failed by *answering* — that transport never throws. A throw-only
assertion reports "the promise resolved", which names the absence of a throw
and never the absence of an envelope, so it cannot separate "refused with the
wrong envelope" from "did not refuse at all" — and those are exactly the two
defects.

Where the wording is itself contract (#5240, one condition ⇒ one wording),
assert the message's first sentence **on top of** `code`+`status`, never instead
of them. A rejection test that cannot go red on a missing envelope reads as
coverage and is not.

**Key-vs-value reachability criterion.** Match a fixture guard's assertion to
what the rule guards. Guarding that a **key** is a real authoring surface →
assert the schema reports no `unrecognized_keys` on the fixture. Guarding a
Expand Down
28 changes: 28 additions & 0 deletions .claude/skills/pm-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,28 @@ prompt:
实现面逐层收口」这类工作上,没有第二个实现面的活(纯 UI、文档、单面脚本)这条无处可
绑,该省掉而不是改写它。

**拒收类用例的最低断言集是 `code` + `status`,不是「它抛了」—— 派发令的标准条款。**
适用判据:本单会**新增或改写拒收 / 错误类用例**(验收点里出现「应当被拒收」的活)。
满足时派发令带这一句(原话):

> 拒收类用例最低断言**错误的 `code` 与 `status`**(ADR-0112 信封)。
> `expect(...).toThrow()` / `rejects.toThrow()` 单独使用**不构成**拒收测试;措辞本身
> 是契约时(#5240「一个条件一种措辞」),首句断言**加在** `code`+`status` **之上**,
> 而不是代替它。本条约束你**新写或改写**的用例 —— 顺手回填存量套件不在本单范围内。

出处是 #6142(#6050)的反向验证实测。两种失明机制方向相反,同一个洞:

- **裸 `Error` ⇒ 恒绿。** 删掉拒收闸后 `driver-sql` 28 例红 22,**多数红在抛出 knex 的
裸 `Undefined binding(s)`** —— 一个 `code` / `status` 均为 `undefined` 的 Error。未修的
驱动本来就抛,缺的只是信封:只断言「它抛了」的用例,**在本单所针对的那个驱动上保持
绿色**。
- **从不抛的 transport ⇒ 红,但红得不指向缺陷。** 同一次删闸,`driver-turso` remote
29 例红 20,**20 个全部**红在「本该拒收却编译出了 SQL」—— 该 transport 从不抛。只断言
抛出的用例在这里报的是「promise 没有 reject」,说的是**没抛**而不是**没信封**,分不开
「拒收了但信封错」与「根本没拒收」—— 而这正是这一族的两个缺陷。

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

**Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an
honest "the premise is dead" cheap to return.** Step 1's stale-premise check
is the PM's sample; the dev's verification is the real thing, so the prompt
Expand Down Expand Up @@ -1478,6 +1500,12 @@ against the report's own claims:
genuinely invalid shapes are still there (step 5's two lines). #5365 slipped
through exactly this review layer and was caught by CI instead: CI does catch
it, at the price of one extra lap.
- **拒收类用例的绿,是不是「它抛了」的绿?** 判据:本单验收点含「应当被拒收」。抽查
diff 里的拒收用例有没有断言 `code` 与 `status`(ADR-0112 信封)—— 只写 `toThrow()` /
`rejects.toThrow()` 的用例,在**未修实现本来就抛裸 Error** 的那一族上恒绿(#6142
实测:`driver-sql` 删闸后 22 红中多数是裸 knex Error,`code` / `status` 均
`undefined`),于是「28 例全绿」这种报告读起来是覆盖、实际证不了拒收。缺断言判
REWORK 补齐,而不是接受绿色输出。本条是 step 5 那条标准条款在复核侧的对账。
- **Did the dev verify the issue's premise?** The report's
`premise_still_valid` field makes the answer explicit — a `false` there
reopens triage rather than failing review. A report that falsifies the
Expand Down
20 changes: 20 additions & 0 deletions skills/objectstack-pm-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ against the report's own claims**:
present.
- Test evidence in the report shows the **actual commands and passing output**,
not a bare "tests pass".
- Rejection-class tests in the diff — those whose point is that bad input is
**refused** — assert the error's identity (its `code` and `status`, or
whatever fields the project's error envelope declares), not merely that
something was thrown. A throw-only assertion is green on any producer that
already throws a bare error, which is what an unfixed producer usually does,
so it reads as coverage while being unable to fail on the defect it names.
- The diff plausibly satisfies the issue's acceptance criteria.

Verdict per issue:
Expand Down Expand Up @@ -586,6 +592,20 @@ Definition of done, in order:
written in the language the repository's PRs use.
- Tear down anything you started (dev servers, temporary processes) by PID.

Rejection-class tests assert the envelope, not the throw. For any test whose
point is that bad input is REFUSED, the minimum assertion set is the error's
identity — its `code` and its `status`, or whatever fields your project's error
envelope declares. "It threw" alone (`expect(...).toThrow()`,
`rejects.toThrow()`) is not a rejection test, and it goes blind in two opposite
directions. An unfixed producer usually throws ALREADY — a bare error carrying
neither field — so the assertion stays GREEN on the very defect the test names.
And a producer that answers instead of throwing fails it with "nothing was
thrown", naming the absence of a throw rather than the absence of an envelope,
so it cannot separate "refused with the wrong envelope" from "did not refuse at
all". Assert the message's wording on top of the envelope fields only where the
wording is itself contract — never instead of them. A rejection test that
cannot go red on a missing envelope reads as coverage and is not.

When to STOP instead of coding. If the issue underspecifies a decision that
shapes a public contract — a schema, API shape, naming, metadata semantics —
or two readings of the issue lead to different architectures: make no guess,
Expand Down
Loading