Repository navigation
Commit 29c7157
pm-dispatch: a declared narrowing is the fourth contract-review face, at CONTRACT_REVIEW_TIER (#23750)
Fixes #23692
Clause-②: no
Implements ruling record 6203713500 (letter B, maintainer 「B (推荐)」):
ruling C (6053791774) stands as a face of its own under ruling B′
(6159870311). A card declared `Clause-②: no` that narrows a published
accept set owes one isolated review at `CONTRACT_REVIEW_TIER`, exactly.
## What changed
**`scripts/pm/record-recognisers.mjs`**
- `REVIEW_FACES` gains `narrowing`, mapped to
`CONTRACT_REVIEW_TIER_NAME`. The decision order is `schema`, `rules`,
`narrowing`, `declaration`. `REVIEW_TIER_NAMES` is unchanged: still two
names.
- `reviewFace(paths, repo, { declaredOn })` takes one optional, named
third input: the texts the `Clause-②:` line is written on. Those are
each committed changeset's text, plus the PR body where the caller holds
it. The texts are read with `readClause2Line`, imported from
`clause2-line.mjs`; there is no second parser.
- `servedTierStands` is unchanged. Because the face now answers
`CONTRACT_REVIEW_TIER_NAME` on a narrowing PR, it accepts a
`CONTRACT_REVIEW_TIER` record there and refuses `TIER_DEFAULT` (ruling
item 1).
- In `--template` and `--brief-template`, `--face narrowing` is listed
from the face table. The narrowing brief sets the scope floor at every
path of the diff outside the prose faces, with no lane remainder.
`--paths` over a list the face cannot be read from is refused: exit 1,
with the usage, the reason, and a pointer to `--face`.
- Self-test: a new battery `letter B: a declared NARROWING is a face of
its own …` (17 cases, floor 17; the roster floor goes from 11 to 12).
The closed-set case now expects four faces.
**`.claude/skills/pm-dispatch/references/contract-review.md`**: net +1
line (47 → 48; the ceiling stays at 60).
- 契约三面 → 契约四面, adding 收窄申报. 三面皆不碰 → 四面皆不碰.
- The tier bullet (档随面定) now covers the narrowing face: the spelling at
line start in the PR body and the changeset, `CONTRACT_REVIEW_TIER`, and
a scope floor of the whole diff outside the prose faces.
- The reading line (面读 …) now reads the file list and the declaration
line, and still never a seat's own statement. In-place edit, 113 B.
`execution-duties.md` is not touched (ruling item 2). Neither are the
frozen `dispatch-gates.mjs`, `dispatch-gates.data.mjs` and
`dispatch-gates.self-test.mjs`, or `check-governed-queue-guard.mjs`.
## Design answer: how the face learns the declaration (dispatch §2 (b))
- **Path faces decide first and never read `declaredOn`.** Results for
`schema` and `rules` are byte-identical to the two-argument call. A case
pins this by `JSON.stringify` equality with a narrowing line, with a
malformed line, and with no line.
- **The paths decide nothing and a declaration is handed.** A
`(narrowing)` arm on any carrier gives the narrowing face. Otherwise, a
`malformed` line on any carrier gives UNREADABLE: `readClause2Line`
collapses near-miss arms such as `(narrowed)` into `malformed` precisely
so they cannot read as no narrowing. Otherwise the result is the
declaration face.
- **The paths decide nothing and no declaration is handed.** If the file
list carries a changeset (a `.changeset` markdown file other than the
README), the result is UNREADABLE: `face: null`, `tierName: null`, plus
a `reason`. `servedTierStands` stands nothing on that, so the reading is
never the cheaper tier. This is the queue guard's own rule for a pair
with no path list, applied one level down. If the list carries no
changeset, the declaration face stands, byte-identical to before. A
narrowing of a published accept set moves a published package and
therefore ships a changeset, so a committed tree with no changeset
carries no narrowing declaration.
- **Why the no-declaration fallback is not always UNREADABLE
(measured).** The queue guard calls `reviewFace(pair.paths, pair.repo)`
with no declaration. Its self-test pins `recordVerdict` standing at
`TIER_DEFAULT` on a declaration-only pair whose paths include no
changeset (case
`the-SAME-reading-STANDS-a-TIER_DEFAULT-record-on-a-declaration-only-pair`).
The required `Governed Surface Queue Guard` check runs that self-test
first. An unconditional UNREADABLE would turn that case red, and fixing
it would need an edit to `check-governed-queue-guard.mjs`, which is
outside this card's file surface. The changeset rule keeps that pin true
and still refuses the narrowing-only shape that met PR #23683. So the PM
reading in §2 (e) holds: no guard edit is owed. The guard self-test
passes 322 cases before and after.
Three choices for the reviewer to judge:
1. A `yes (narrowing)` line maps to the narrowing face. The arm decides,
the way one hit decides a mixed diff. The ruling's text names only the
`no (narrowing)` spelling.
2. The narrowing face's scope floor is the whole diff outside the prose
faces. The ruling does not address scope, and the letter-C `hits` floor
would be empty on this face.
3. The PR body counts as a carrier when a caller hands it, because the
ruling writes the spelling on both. The doc's reading line names the
file list and the declaration line.
## Verification at head `4c15f575b`
- Defect reproduced at `3b46855fb`: `--brief-template --paths` over a
list shaped like PR #23683's (one changeset,
`packages/metadata-protocol/src/protocol.ts`, one runtime test) printed
"You review at `TIER_DEFAULT`". At this head the same command exits 1
and names `--face narrowing`.
- `pnpm check:pm-record-recognisers` (`--self-test`): 184 cases passed
at the base and 201 pass at the head, exit 0.
- Ablation: `scripts/ablation-replace.mjs` deleted the `narrowing` row
of `REVIEW_FACES` (anchor ×1 → ×0, blob `e0e82d93bad8` →
`23a1eb6ce055`). The self-test then exited 1 with 8 of 201 cases red:
- the faces are the closed set of four, in decision order, and they map
to exactly two NAMES
- ⭐ a narrowing-only path list with `Clause-②: no (narrowing)` on its
changeset ⇒ the narrowing face, CONTRACT_REVIEW_TIER
- ⭐ …and a CONTRACT_REVIEW_TIER record STANDS on that pull request — the
ruling's first half
- ⭐ the line read off the PR BODY counts as well — the ruling writes the
one spelling on both carriers
- ⭐ `yes (narrowing)` is the narrowing face — a diff that widens and
narrows owes the narrowing review, as a mixed diff owes its hit
- ⭐ `--face narrowing`'s record carries CONTRACT_REVIEW_TIER and the
`Scope:` line, round-trips through the locator and STANDS on the
narrowing face
- ⭐ `--template --face narrowing` prints exactly that template and exits
0
- ⭐ the narrowing brief reviews at CONTRACT_REVIEW_TIER, hands `--face
narrowing`, and floors the WHOLE diff — no lane remainder
The restore leg proved blob == HEAD (`e0e82d93bad8`) with an empty `git
diff HEAD`. The restored run passes 201 cases, exit 0.
- `node scripts/pm/check-governed-queue-guard.mjs --self-test`: 322
cases pass, exit 0, before and after.
- `check-skill-line-ratchet` for `contract-review.md`: "is 47 lines
(ceiling 60; headroom 13)" before, "is 48 lines (ceiling 60; headroom
12)" after, exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `4c15f575b` derives 37 commands, the same
list as the dispatch. All 37 were run locally, each exit code captured
before any pipe. `--ran` reports: "37 derived famil(ies) accounted for —
37 run, 0 NOT-MEASURED (a DERIVED zero — all 37 recorded an exit code
and none of them is 3)".
- `check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET:
`@objectstack/formula` not built). After a rebuild of
`@objectstack/formula` and `@objectstack/lint` under the verify lock
(command-exit 0), it reran with exit 0.
- `pnpm check:pm-dispatch-gates` exited 0: fast tier, 1884 cases, 6 slow
sections deferred to CI.
- No control bytes in either file (scan exit 1 = no match). No
changeset: neither `scripts/pm/**` nor `.claude/**` publishes a package,
so this PR carries `skip-changeset`.
## Acceptance notes (observations; not edited, because the files are
frozen or outside this card's surface)
- `scripts/pm/dispatch-gates.mjs:12013` (comment: "the contract-review
reference's three faces") now names three of four faces. The file is
frozen.
- `scripts/pm/dispatch-gates.data.mjs:1062` ("the three contract faces …
a published schema, governed rules text, the `Clause-②: yes`
declaration") likewise. Frozen.
- `scripts/pm/dispatch-gates.self-test.mjs:9961` ("the three contract
faces") likewise. Frozen.
- `.claude/skills/pm-dispatch/references/lanes/ui.md:38` still says
契约三面. It is outside this card's file surface (carrier: none).
- `dispatch-gates.mjs:11794–11797` (the `--tier` docblock) and
`:11866–11873` remain true. The `--tier` note already says what ruling C
says.
- Records written at `TIER_DEFAULT` on narrowing-only PRs before this
lands, such as PR #23683's, are not re-opened (ruling item 3).
## 维护者速读(草稿)
**改了什么**:复核记录识别器新增第四个复核面「收窄」。PR 正文与 changeset 行首写 `Clause-②: no
(narrowing)` 的卡,其隔离复核档位定为 `CONTRACT_REVIEW_TIER`,且只认这一档。规则文
contract-review.md 同步把「三面」改为「四面」,净增一行。
**为什么改**:裁决 B 认定「收窄已发布接受集的卡欠一次契约复审档复核」是独立的一面。旧识别器没有这一面,收窄类 PR
只能按默认档复核,规则文与工具给出两个答案;本周已有四张收窄类 PR 遇到。
**风险与代价(含回滚)**:风险小,只动 PM 工具与一份内部规则文,不发布任何包。若调用方没有交来申报内容,而 PR 带
changeset,识别器答「不可读」,不再默认给便宜档;命令行 `--paths` 此时会拒绝并提示改用
`--face`。队列守卫的线上行为不变(它只看 governed PR,永远按路径定面)。回滚:revert 本 PR 即可,无数据迁移。
**席位意见**:
**你要做的**:无需动手。席位拿到达档契约复核 PASS 后按 Tier S 落地;如对上文三处设计取舍有不同意见,在卡上回一句即可。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VqGhCg9wdsBXPz54uAEWg7)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c6c84d3 commit 29c7157
2 files changed
Lines changed: 185 additions & 34 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
16 | | - | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | | - | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
0 commit comments