This is an automated email from the ASF dual-hosted git repository.

shuke987 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/doris-skills.git


The following commit(s) were added to refs/heads/main by this push:
     new 0476f3b  feat(doris-repo-review): flag regressions per finding and 
floor them at Major (#12)
0476f3b is described below

commit 0476f3b84adeee4df75bc7ed694980306f8c6ab8
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Sep 14 14:26:39 2026 +0800

    feat(doris-repo-review): flag regressions per finding and floor them at 
Major (#12)
    
    * feat(doris-repo-review): flag regressions per finding and floor them at 
Major
    
    The 2026-09-13 review of apache/doris#67900 found that the refactor had 
dropped
    the per-attempt reset of the Flight result-location flag, wrote it up 
correctly,
    and rated it Minor because the trigger was narrow and the fix was one line. 
The
    verdict stayed APPROVE, a PASS receipt was posted with the regression as its
    first note, and the team's "fix only Blocker/Major" rule dropped it; the 
next
    reviewer sent it back as a must-fix.
---
 README.md                                          |   2 +-
 skills/doris-repo-review/SKILL.md                  | 116 +++++++++++++++----
 .../doris-repo-review/references/doc-templates.md  | 102 ++++++++++++++++-
 .../references/pr-comment-format.md                |  19 +++-
 skills/doris-repo-review/references/prompts.md     |  89 +++++++++++++--
 .../doris-repo-review/scripts/post-pass-comment.sh |  25 +++++
 .../scripts/prepare-review-context.sh              |  16 ++-
 .../scripts/verify-review-docs.py                  | 123 ++++++++++++++++++++-
 verify/README.md                                   |   2 +-
 verify/repo-review/post-comment.sh                 |  44 +++++++-
 verify/repo-review/review-documents.sh             |  98 +++++++++++++++-
 11 files changed, 587 insertions(+), 49 deletions(-)

diff --git a/README.md b/README.md
index 4082c3c..094da27 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ skill from its `description`, so in practice you describe the 
problem and the ri
 
 | Skill | What it does | Use it when |
 |---|---|---|
-| [`doris-repo-review`](skills/doris-repo-review/) | Contributor-side PR 
review of a local `apache/doris` clone, running the same flow as the CI Code 
Review Runner: worktree alignment, risk scan with premise checks, subagent 
fan-out, shared ledger, mechanical coverage report, anchored EN + ZH review 
documents kept per reviewed head, dismissals carried forward from earlier 
reviews of the same PR, and — on a passing review — one machine-readable PASS 
comment on the PR | Reviewing an `apach [...]
+| [`doris-repo-review`](skills/doris-repo-review/) | Contributor-side PR 
review of a local `apache/doris` clone, running the same flow as the CI Code 
Review Runner: worktree alignment, risk scan with premise checks, subagent 
fan-out, shared ledger, mechanical coverage report, anchored EN + ZH review 
documents kept per reviewed head with a per-finding category and a regression 
flag judged against the merge base that floors an undeclared functional / data 
/ resource / performance change at [...]
 
 These skills read a Doris **source** checkout and never touch a cluster; the 
cluster-side skills
 never touch repository source. Nothing here builds Doris or runs tests. The 
one GitHub write in
diff --git a/skills/doris-repo-review/SKILL.md 
b/skills/doris-repo-review/SKILL.md
index 186b39a..6a3b148 100644
--- a/skills/doris-repo-review/SKILL.md
+++ b/skills/doris-repo-review/SKILL.md
@@ -47,10 +47,10 @@ $S/save-run-state.sh --ctx "$CTX" --verdict ... --findings 
b,m,mi,n  # step 11:
 | `scripts/align-to-pr.sh` | Resolve the PR, diagnose how the current 
directory relates to it, align it to the PR head |
 | `scripts/prepare-review-context.sh` | Produce the authoritative diff, 
new-side line ranges, required AGENTS.md list, existing comments, ledger 
skeleton |
 | `scripts/coverage-report.sh` | Mechanical check of which changed files no 
ledger file has mentioned yet |
-| `scripts/verify-review-docs.py` | Validate commit, anchors, EN/ZH agreement, 
verdict, findings, rounds, and convergence |
+| `scripts/verify-review-docs.py` | Validate commit, anchors, EN/ZH agreement, 
verdict, findings, per-finding category and regression flag with the 
category-aware `Major` floor, rounds, and convergence |
 | `scripts/record-review-runtime.sh` | Record the qualified reviewer model, 
effort, and exact commit |
 | `scripts/review-runtime-policy.sh` | The exact model and effort allowlist 
for a pipeline-equivalent review |
-| `scripts/post-pass-comment.sh` | Render and post the machine-readable PASS 
comment; refuses everything that is not a pass |
+| `scripts/post-pass-comment.sh` | Render and post the machine-readable PASS 
comment; refuses everything that is not a converged pass free of floored 
regressions, and refuses to leave any other regression undisclosed |
 | `scripts/save-run-state.sh` | Persist this run's merged ledger under the 
stable per-PR state directory, so the next review inherits its dismissals |
 | `references/prompts.md` | Subagent prompt templates (CI wording, carried 
over verbatim) |
 | `references/doc-templates.md` | Templates for both documents, anchor format, 
verdict rule |
@@ -171,7 +171,7 @@ Output (under `$CTX`):
 
 | File | Contents |
 |---|---|
-| `meta.env` | PR info, base/head sha, diff range, `DOCS_ROOT`, `ALIGN_MODE`, 
date, dirty-file count |
+| `meta.env` | PR info, `BASE_SHA` / `HEAD_SHA` / `MERGE_BASE`, `TARGET_AHEAD` 
(target-branch commits since the merge base), diff range, `DOCS_ROOT`, 
`ALIGN_MODE`, date, dirty-file count |
 | `pr.diff` / `pr_changed_files.txt` / `pr_changed_files_status.txt` | The 
authoritative diff and change list |
 | `changed_line_ranges.txt` / `.tsv` | **New-side (post-change) line ranges** 
- the source for anchors |
 | `pr_commits.txt` / `pr_diffstat.txt` | Commit list and change size |
@@ -184,7 +184,11 @@ Output (under `$CTX`):
 | `prior_runs/` | What earlier reviews of **this same PR** concluded - see 
below |
 
 If `BASE_SOURCE` is not `PR base sha (matches CI)`, the baseline differs from 
CI's and the
-documents must say so.
+documents must say so. Two different "before" commits come out of this step 
and they are not
+interchangeable: `BASE_SHA` is the target-branch tip the diff was cut against 
and the commit the
+PASS receipt binds to; `MERGE_BASE` is where the PR actually branched off, and 
it is the only
+correct answer to "what did this code do before the PR" (steps 4.1 and 6). 
When `TARGET_AHEAD`
+is non-zero the two differ, and the script says so.
 
 Record the qualified runtime selected in step 0 before reading source:
 
@@ -284,11 +288,13 @@ So each entry gets one more field:
   Premise result: <confirmed | FALSE - item dismissed | cannot be checked 
cheaply>
 ```
 
-Run every one of them **before** step 5. The check is nearly always a 
one-liner against the base:
+Run every one of them **before** step 5. The check is nearly always a 
one-liner against the merge
+base (`MERGE_BASE` in `meta.env` — not `BASE_SHA`, which is the target-branch 
tip and may hold
+commits the PR never saw; `TARGET_AHEAD` says how many):
 
 ```bash
-git show "$BASE_SHA:path/to/File.java" | grep -n 'thing I think is new'
-git show "$BASE_SHA:path/to/File.java" | sed -n '120,140p'
+git show "$MERGE_BASE:path/to/File.java" | grep -n 'thing I think is new'
+git show "$MERGE_BASE:path/to/File.java" | sed -n '120,140p'
 rg -n 'symbol' --files-with-matches            # "only one caller" claims
 ```
 
@@ -369,10 +375,41 @@ candidate into `$CTX/ledger/main-merged.md`:
   `pr_review_threads.md`.
 - **Assign a status**: `accepted` / `dismissed_with_evidence` / `duplicated`. 
A dismissal must come
   with concrete code evidence, which goes verbatim into the document's 
"Considered and Dismissed".
+- **Classify**: give every accepted candidate exactly one `Category` from the 
closed vocabulary
+  of `references/doc-templates.md` — `functional-bug`, `functional-loss`, 
`data-error`,
+  `resource-leak`, `performance`, `observability`, `test-coverage`, `wording`, 
`maintainability`
+  — naming its *consequence*; the domain (concurrency, lifecycle, 
compatibility, config, …) goes
+  in a parenthesis after it. The verifier rejects any other value.
+- **Flag regressions**: for every accepted candidate decide `Regression: yes | 
no` against the
+  **merge base** — does HEAD behave differently from `git show 
$MERGE_BASE:<path>` in a way the PR
+  body does not declare as intended? Not `$BASE_SHA`: when `TARGET_AHEAD` is 
non-zero the target
+  branch has commits the PR never saw, and a fix that landed there is absent 
at HEAD without the
+  PR having removed anything (a normal merge keeps it) — that is not a 
regression, and a semantic
+  merge concern, if there is one, is its own row. Record the flag and its 
evidence in
+  `main-merged.md`; it becomes the mandatory `Regression` line of the finding. 
Then apply the
+  severity floors of `references/doc-templates.md`: a regression in 
`functional-bug`,
+  `functional-loss`, `data-error`, `resource-leak`, or `performance` is **at 
least `Major`**, and
+  for a PR presented as behaviour-preserving every undeclared differing cell 
of the differential
+  table is such a regression. A regression in the other four categories is 
judged by its
+  consequence, but below `Major` it needs a written *Severity rationale* 
saying why the behaviour
+  change is acceptable. Severity is the consequence when it triggers, not the 
probability; "narrow
+  trigger", "cloud only", "one-line fix" and "no wrong result" are not 
discounts.
+  `verify-review-docs.py` rejects a floored regression rated `Minor`/`Nit` and 
a non-floored one
+  without a rationale, so decide it here, not at document time.
+- **Severity challenge before a downgrade**: when you intend to rate a 
candidate below what its
+  subagent proposed, or to dismiss a `functional-bug` / `functional-loss` / 
`data-error` /
+  `resource-leak` candidate, first write the
+  *Severity rationale* (consequence when it triggers, who notices, what stays 
leaked or wrong, why
+  that is not `Major`) into `main-merged.md`, then dispatch **one** 
severity-challenge subagent with
+  the prompt of `references/prompts.md` section E — its only job is to argue 
the higher severity
+  from primary sources. Finalize the severity only after it returns, and carry 
the rationale into the
+  finding. It is one short agent; skipping it is how apache/doris#67900 
shipped a `Minor` that the
+  next reviewer had to send back as a must-fix.
 - **Fill the risk items back in**: update `Status` and `Final conclusion` for 
every entry in
   `00-main-risk-scan.md`.
 
-When this step ends, **no candidate may be left without a status**.
+When this step ends, **no candidate may be left without a status, a category, 
a regression flag,
+or — where it was downgraded or is a non-floored regression below `Major` — a 
severity rationale**.
 
 ### 6a. Run the coverage report at the end of every round
 
@@ -472,9 +509,13 @@ already exists, you are re-running against an unchanged 
head: overwrite that one
 - The document header must state: the PR link and state, the **head sha 
actually reviewed**, the
   diff range, the review directory (`WORKDIR`), and the `branch check` / 
`commit check` results
   from step 1.
-- Every finding must carry: a severity, a `path:line` anchor, a verbatim 
snippet, and the four
-  parts **what is wrong / why it happens / when it bites / suggested fix**. 
Give a diff patch when
-  the fix is small and self-contained; use prose only for architectural 
problems.
+- Every finding must carry: a severity, a `Category` from the closed 
vocabulary, a
+  `Regression: yes | no` line (with the merge-base evidence), a `path:line` 
anchor, a verbatim
+  snippet, and the four parts **what is wrong / why it happens / when it bites 
/ suggested fix** —
+  plus a **severity rationale** whenever the severity is below what a subagent 
proposed, or a
+  regression in `observability` / `test-coverage` / `wording` / 
`maintainability` is rated below
+  `Major`. Give a diff patch when the fix is small and self-contained; use 
prose only for
+  architectural problems.
 - The "Critical Checkpoints" table needs a conclusion per row; mark a 
non-applicable one `n/a` with
   a half-line reason - **never delete the row**.
 - "Response to Review Focus" answers each of the user's focus points.
@@ -495,22 +536,31 @@ python3 $S/verify-review-docs.py --ctx "$CTX" \
     --doc review-docs/pr-<N>-review.zh.md
 ```
 
-It validates the reviewed head, anchors, EN/ZH finding order and severity, 
verdict, rounds, and
-convergence. Fix every error before continuing.
+It validates the reviewed head, anchors, EN/ZH finding order, severities, 
categories and
+regression flags, the category-aware regression floor, the rationale a 
non-floored regression
+below `Major` needs, verdict, rounds, and convergence. Fix every error before 
continuing — and if
+the error is "is a regression in category … but rated Minor", the fix is the 
severity (and with it
+the verdict), not the flag and not the category.
 
 ---
 
 ## 10. Post the PASS comment to the PR
 
 **Only when the verdict is APPROVE**, the review converged, and step 9 passed. 
A
-REQUEST_CHANGES or non-converged review posts nothing - say so in the closing 
report and stop.
+REQUEST_CHANGES or non-converged review posts nothing - say so in the closing 
report and stop. A
+regression in a floored category can only exist inside a REQUEST_CHANGES, so 
it never reaches the
+poster; a `Regression: yes` in `observability` / `test-coverage` / `wording` / 
`maintainability`
+rated `Minor` / `Nit` does reach it, and the receipt must disclose every one 
of them.
 
-Write optional notes and run the poster once:
+Write the notes and run the poster once:
 
 ```bash
-# At most 5 bullets, each anchored where it can be. Skip the file when there 
is nothing to say.
+# At most 5 bullets, each anchored where it can be. Every finding flagged 
`Regression: yes`
+# comes first, named with its ID and category; the poster refuses to post 
without notes when
+# such a finding exists. Skip the file only when there is nothing to say and 
no regression.
 cat > "$CTX/pr-comment-notes.md" <<'EOF'
-- `fe/fe-core/src/main/java/org/apache/doris/X.java:214` — <what the 
maintainer should know>
+- F-01 (observability, undeclared behaviour change) 
`fe/fe-core/src/main/java/org/apache/doris/X.java:214` — <what changed and why 
it is acceptable>
+- `fe/fe-core/src/main/java/org/apache/doris/Y.java:88` — <what the maintainer 
should know>
 EOF
 
 $S/post-pass-comment.sh --ctx "$CTX" \
@@ -518,8 +568,8 @@ $S/post-pass-comment.sh --ctx "$CTX" \
 ```
 
 The poster reads model, effort, and commit from `review-runtime.json`; it 
invokes
-`verify-review-docs.py` itself for verdict, findings, rounds, and convergence; 
then it rechecks the
-live PR head. A refusal is final. The normal flow posts immediately; 
`--dry-run` exists only for
+`verify-review-docs.py` itself for verdict, findings, regression counts, 
rounds, and convergence;
+then it rechecks the live PR head. A refusal is final. The normal flow posts 
immediately; `--dry-run` exists only for
 maintainer testing. Same account plus same commit updates the existing 
comment, while a new commit
 creates a new one. Keep the schema in `references/pr-comment-format.md` 
machine-generated.
 
@@ -530,16 +580,19 @@ creates a new one. Keep the schema in 
`references/pr-comment-format.md` machine-
 Tell the user:
 
 1. The paths of both documents, the verdict (REQUEST_CHANGES / APPROVE), the 
finding count per
-   severity, and whether the rounds converged in the sense of step 7 - "the 
verdict was settled at
-   round N" if they did, and which condition failed if they did not. A run 
that ended with only
-   Minor/Nit still arriving **converged**; do not report it as a failure.
+   severity, every finding flagged `Regression: yes` with its category (these 
are the behaviour
+   changes the PR body never declared; the author should declare or fix them), 
and whether the
+   rounds converged in the sense of step 7 - "the verdict was settled at round 
N" if they did, and
+   which condition failed if they did not. A run that ended with only 
Minor/Nit still arriving
+   **converged**; do not report it as a failure.
 1a. **What this run inherited**, when `prior_runs/` was not empty: which heads 
were reviewed
    before, how many of their dismissals were carried forward, and which of 
their accepted findings
    are fixed at this head. If no prior run existed, say that this is the first 
review of this PR.
 2. The `branch check` / `commit check` results - especially `ahead:N` 
(unpushed commits that were
    not reviewed).
 3. **What happened to the PASS comment**: the URL when one was posted or 
updated, or the reason
-   nothing was posted (REQUEST_CHANGES, non-convergence, unqualified runtime, 
or moved PR head).
+   nothing was posted (REQUEST_CHANGES, non-convergence, unqualified runtime, 
an undisclosed
+   regression, or moved PR head).
 4. **Where the current directory now stands**: with `ALIGN_MODE=switched` it 
is detached on the PR
    head, and `git checkout <PREV_REF>` restores it. **Do not switch back 
automatically** - the user
    may still want to read the code.
@@ -573,7 +626,7 @@ Tell the user:
 | Fetch existing inline threads (30 threads / 1200 chars) | Same jq |
 | Text after `/review` = review focus | Free text after the PR URL → `--focus` 
|
 | Single-file ledger with sections | A `ledger/` directory, one file per owner 
|
-| Main risk scan → 1-3 full-review subagents + risk-focused → merge → ≤3 
rounds | Same shape, three local additions: every risk item carries a premise 
check the main agent runs before dispatch (4.1); round 1 goes out in two waves 
so the ledger can deduplicate (5.1); a round converges on **severity plus 
coverage**, not on "no new candidates at all" (7) |
+| Main risk scan → 1-3 full-review subagents + risk-focused → merge → ≤3 
rounds | Same shape, four local additions: every risk item carries a premise 
check the main agent runs before dispatch (4.1); round 1 goes out in two waves 
so the ledger can deduplicate (5.1); every finding carries a category and a 
regression flag against the merge base, a regression in a functional / data / 
resource / performance category is floored at `Major`, and a downgrade needs a 
written rationale plus a sever [...]
 | CI reviews one push in isolation | Earlier runs of the same PR are loaded 
from a stable state directory and read as input (2.1); documents are named by 
head sha and never overwritten (9) |
 | `gh pr review` / Reviews API posting inline comments | **Two `review-docs/` 
documents (EN + ZH) with `path:line` anchors** |
 | CI's review verdict is visible on the PR itself | On a qualified converged 
pass, one automatic `doris-repo-review/v1` comment bound to the exact commit; 
otherwise nothing |
@@ -587,6 +640,21 @@ Tell the user:
   for as long as you keep looking. What has to stop moving is the *verdict* - 
see step 7. Two runs
   in a row reporting "did not converge" while the blocking findings were 
settled in round 1 is a
   broken criterion, not a deep PR.
+- **Discounting a regression because its trigger is narrow or its fix is one 
line.** Severity is
+  what happens when it triggers, not how often. A per-attempt reset that a 
refactor dropped is a
+  `functional-loss` regression whether it needs "Flight + replan + a failing 
retry" or not; rate it
+  `Major`, or the PASS receipt will sit on top of a known behaviour change and 
the fix threshold of
+  the team ("only Blocker/Major") will silently drop it — apache/doris#67900, 
2026-09-13.
+- **Filing a real behaviour change under a soft category to dodge the floor.** 
A metric that
+  stopped being emitted is `observability`; a reset that stopped happening is 
`functional-loss`
+  even when the only visible symptom today is a log line. The category names 
the consequence,
+  not the file the code lives in.
+- **Comparing HEAD with the target-branch tip instead of the merge base.** 
When `TARGET_AHEAD` is
+  non-zero, `git show $BASE_SHA:<path>` shows code the PR branch has never 
contained; a fix that
+  landed there after the PR branched off is "missing" at HEAD without the PR 
having removed
+  anything, and a normal merge keeps it. That is not a regression and must not 
become a `Major`.
+  Regression evidence is `git show $MERGE_BASE:<path>`; `BASE_SHA` is for the 
diff range and the
+  receipt.
 - **Dispatching a subagent on a premise you never checked.** The premise check 
of step 4.1 costs
   one command; skipping it costs a whole agent, and the agent comes back 
having proved you wrong
   rather than having reviewed anything.
diff --git a/skills/doris-repo-review/references/doc-templates.md 
b/skills/doris-repo-review/references/doc-templates.md
index a9ce34a..6a96e51 100644
--- a/skills/doris-repo-review/references/doc-templates.md
+++ b/skills/doris-repo-review/references/doc-templates.md
@@ -13,9 +13,9 @@ permanent, and what it destroys is the only record of what an 
earlier review alr
 and dismissed. Re-running against the *same* head overwrites that head's pair, 
and only that one.
 
 The ZH document is a real Chinese review, not a machine translation of the EN 
one: same facts,
-same anchors, same IDs, but idiomatic Chinese. Identifiers, file paths, log 
messages, config
-names, code snippets, and the severity words (`Blocker` / `Major` / `Minor` / 
`Nit`) stay in
-their original form in both documents.
+same anchors, same IDs, same severities, categories and regression flags, but 
idiomatic Chinese.
+Identifiers, file paths, log messages, config names, code snippets, and the 
severity words
+(`Blocker` / `Major` / `Minor` / `Nit`) stay in their original form in both 
documents.
 
 ## Anchor format (mandatory, both documents)
 
@@ -50,6 +50,88 @@ without a compat path, or a broken build/test contract. 
`Major` = real defect or
 guarantee that will bite in production or during upgrade. `Minor` = worth 
fixing, not urgent.
 `Nit` = style or wording.
 
+## Category, regression flag, and severity floors
+
+Every finding carries two more mandatory lines right after its severity — its 
**category** and
+whether it is a **regression**:
+
+```markdown
+- **Category**: functional-loss (lifecycle)        <!-- EN;  ZH: - 
**类别**:功能缺失(生命周期) -->
+- **Regression**: yes                              <!-- EN;  ZH: - **回归**:是 / 
否 -->
+```
+
+### Category
+
+The category names the *consequence* of the finding, from this closed 
vocabulary. The
+verifier rejects any other value, so an unrecognised category cannot slip past 
the floor. One
+class per finding; the domain that produced it (concurrency, lifecycle, 
compatibility, config,
+…) goes in an optional parenthetical after the class.
+
+| Category (EN / ZH) | What belongs here | Regression floor |
+|---|---|---|
+| `functional-bug` / `功能缺陷` | the code does the wrong thing: crash, exception, 
hang, deadlock, wrong branch, wrong control flow, a check that fires when it 
should not | at least `Major` |
+| `functional-loss` / `功能缺失` | something the base did, or the PR claims, no 
longer happens: a dropped reset, a removed compat path, a check that no longer 
runs, a sibling path left unchanged, a rolling-upgrade path that breaks | at 
least `Major` |
+| `data-error` / `数据错误` | wrong query results, lost, duplicated or corrupted 
data, an edit log or persisted state that does not replay | at least `Major` |
+| `resource-leak` / `资源泄露` | memory, file descriptors, threads, connections, 
locks, temp files, tablets or statements not released | at least `Major` |
+| `performance` / `性能降低` | slower, more memory / CPU / IO, a lost 
optimisation, an unbounded loop or allocation | at least `Major` |
+| `observability` / `可观测性` | logs, VLOG, metrics, profile fields, the 
diagnostic content of an error message | judged normally |
+| `test-coverage` / `测试覆盖` | missing, weakened, or non-negative tests; a gate 
a change would leave green | judged normally |
+| `wording` / `措辞` | comments, docs, messages, naming | judged normally |
+| `maintainability` / `可维护性` | duplication, dead code, structure, an 
unexplained special case | judged normally |
+
+Spelling is compared case-insensitively with spaces, hyphens and underscores 
removed, and the
+ZH forms `功能性bug` / `功能性缺失` / `资源泄漏` / `性能` are accepted as aliases. EN and ZH
+documents must agree on the category of every finding.
+
+### Regression
+
+`yes` means the behaviour at HEAD differs from the **merge base** in a way the 
PR body does not
+declare as intended — the cell of the differential table that the author did 
not ask for. `no`
+means the defect was already there (the PR merely exposes, documents, or moves 
it) or the change
+is the one the PR set out to make. The evidence is `git show 
<MERGE_BASE>:<path>`; name it in
+the finding.
+
+The comparison point is `MERGE_BASE` (from `meta.env`), **not** `BASE_SHA`. 
`BASE_SHA` is the
+tip of the target branch — the snapshot the diff was cut against and the 
commit the PASS receipt
+binds to — and it may carry commits the PR branch has never seen 
(`TARGET_AHEAD` in `meta.env`
+counts them). A fix that landed on the target branch after the PR branched off 
is absent at
+HEAD without the PR having removed anything, and a normal merge keeps it; 
comparing against
+`BASE_SHA` would report it as a "dropped" behaviour and a Major regression 
that does not exist.
+Whether the PR still merges cleanly *semantically* against a target branch 
that has moved on is
+a real question, but a different one — raise it, when it applies, as its own 
finding or
+dismissal row, never as `Regression: yes`.
+
+`verify-review-docs.py` rejects a finding without the line and rejects EN/ZH 
documents whose
+flags disagree.
+
+### Floors
+
+The flag and the category together drive a floor that the verifier enforces 
and that no
+amount of "but it is narrow" overrides:
+
+| Condition | Floor |
+|---|---|
+| `Regression: yes` in `functional-bug`, `functional-loss`, `data-error`, 
`resource-leak`, or `performance` | at least `Major` — the verifier rejects 
`Minor` / `Nit` |
+| `Regression: yes` in `observability`, `test-coverage`, `wording`, or 
`maintainability` | judged normally by consequence; below `Major` the finding 
**must** carry a **Severity rationale** paragraph saying why the behaviour 
change is acceptable (the verifier checks that it is there), and the PASS 
receipt must name it in its notes |
+| a PR presented as behaviour-preserving (`[refactor]`, `[chore]`, "Behavior 
changed: No", or any wording that claims equivalence): **every** differing cell 
of the D1 differential table that the PR body does not name as intended | 
`Regression: yes`, classified by its consequence like any other finding — a 
cell that differs in what the code *does* (as opposed to what it logs or says) 
is `functional-loss` / `functional-bug` / `data-error`, hence at least `Major`. 
The reviewer's job is to f [...]
+
+Severity is the consequence *when* the finding triggers — what is wrong, 
leaked, lost, or
+silently skipped — never the probability that it triggers. A narrow trigger 
(one protocol, cloud
+only, a retry that has to fail twice), a rare environment, or a one-line fix 
is not a discount. The
+review of apache/doris#67900 rated a per-attempt reset that the PR had dropped 
as `Minor` because
+the trigger was "Flight + replan + failing retry" and the fix was one line; 
the connector statement
+scope it leaked was raised again by the next reviewer as a must-fix. Under 
this table that finding
+is `functional-loss` (the reset the base performed no longer happens), 
`Regression: yes`, and
+therefore at least `Major` — the case this section exists to prevent.
+
+**Downgrading needs a written rationale.** When the main agent rates a 
candidate below what the
+subagent proposed, or dismisses a `functional-bug` / `functional-loss` / 
`data-error` /
+`resource-leak` candidate, the finding (or the "Considered and Dismissed" row) 
must carry a
+**Severity rationale** paragraph: the consequence analysis — what happens when 
it triggers, who
+notices, what is left leaked or wrong — and explicitly why that is not 
`Major`. "Narrow",
+"one-line fix", or "no wrong result" alone is not a rationale. SKILL.md step 6 
additionally
+dispatches a severity-challenge subagent before such a downgrade is final.
+
 ---
 
 ## English template
@@ -84,8 +166,9 @@ guarantee that will bite in production or during upgrade. 
`Minor` = worth fixing
 ### F-01 · <one-line title>
 
 - **Severity**: Blocker
+- **Category**: functional-bug (concurrency) <!-- one of functional-bug / 
functional-loss / data-error / resource-leak / performance / observability / 
test-coverage / wording / maintainability; the domain goes in the parenthesis. 
Mandatory. -->
+- **Regression**: yes <!-- yes = behaviour differs from the merge base and the 
PR body does not declare it intended; no = pre-existing or intended. Mandatory; 
yes in the first five categories is at least Major. -->
 - **Where**: `path/to/File.java:412-430`
-- **Category**: correctness / concurrency / lifecycle / compatibility / config 
/ performance / observability / test coverage
 
 ```java
 <verbatim snippet from the anchor>
@@ -97,6 +180,12 @@ guarantee that will bite in production or during upgrade. 
`Minor` = worth fixing
 
 **When it bites.** <a concrete trigger scenario — who calls what, in which 
order, with which data>
 
+**Severity rationale.** <required whenever the severity is below what a 
subagent proposed, a
+regression is rated at the floor rather than Blocker, or a regression in 
observability /
+test-coverage / wording / maintainability is rated below Major (say why the 
behaviour change is
+acceptable): the consequence when it triggers and why that is (not) worse. 
Omit for findings rated
+as proposed.>
+
 **Suggested fix.**
 
 ```diff
@@ -184,8 +273,9 @@ out. This is what makes the review auditable — do not 
silently drop a concern.
 ### F-01 · <一句话标题>
 
 - **等级**:Blocker
+- **类别**:功能缺陷(并发) <!-- 功能缺陷 / 功能缺失 / 数据错误 / 资源泄露 / 性能降低 / 可观测性 / 测试覆盖 / 措辞 / 
可维护性 之一;所属领域写在括号里。必填。 -->
+- **回归**:是 <!-- 是 = HEAD 的行为与 merge base 不同且 PR 描述没有声明这是有意的;否 = 
既有问题或有意改动。必填;前五类里的回归至少是 Major。 -->
 - **位置**:`path/to/File.java:412-430`
-- **类别**:正确性 / 并发 / 生命周期 / 兼容性 / 配置 / 性能 / 可观测性 / 测试覆盖
 
 ```java
 <锚点处原样摘录的代码>
@@ -197,6 +287,8 @@ out. This is what makes the review auditable — do not 
silently drop a concern.
 
 **什么时候会踩到。** <具体触发场景:谁在什么顺序上调用了什么、数据长什么样>
 
+**定级理由。** <当等级低于子 agent 提议、回归只定在 Major 下限而非 Blocker、或可观测性 / 测试覆盖 / 措辞 / 
可维护性类的回归定在 Major 以下(说明为什么这个行为变化可以接受)时必填:触发后的后果是什么、为什么(不)更严重。等级与提议一致时可省略。>
+
 **修改建议。**
 
 ```diff
diff --git a/skills/doris-repo-review/references/pr-comment-format.md 
b/skills/doris-repo-review/references/pr-comment-format.md
index edd51d2..c16d755 100644
--- a/skills/doris-repo-review/references/pr-comment-format.md
+++ b/skills/doris-repo-review/references/pr-comment-format.md
@@ -1,7 +1,10 @@
 # PASS comment format (`doris-repo-review/v1`)
 
-When a review passes, the skill posts **one comment** on the PR from the 
locally authenticated
-`gh` account. The comment is written by `scripts/post-pass-comment.sh`, never 
typed by hand: the
+When a review passes — a converged `APPROVE` with no Blocker/Major finding and 
no regression in
+a category that carries the Major floor (`functional-bug`, `functional-loss`, 
`data-error`,
+`resource-leak`, `performance`) — the skill posts **one comment** on the PR 
from the locally
+authenticated `gh` account. A regression in any other category can stay 
`Minor`/`Nit` and still
+pass, but the receipt then has to name it in the notes; the poster refuses to 
post without them. The comment is written by `scripts/post-pass-comment.sh`, 
never typed by hand: the
 agent supplies optional notes, while verified documents supply the structured 
result. Anything that reads the
 comment back — a script, a dashboard, another agent — depends on that layout 
being fixed.
 
@@ -55,7 +58,7 @@ receipt for the matching commit; it is not a human Apache 
approval.</sub>
 | `status` | `PASS` | the only value ever posted; `CHANGES_REQUESTED` is 
reserved, not used |
 | `pr` | `owner/repo#N` | `meta.env` |
 | `commit` | 40-hex | `HEAD_SHA` — the exact commit reviewed |
-| `base` | 40-hex | `BASE_SHA` — with `commit` this reproduces the reviewed 
diff |
+| `base` | 40-hex | `BASE_SHA` — with `commit` this reproduces the reviewed 
diff (`git diff base...commit`); the regression flags were judged against the 
merge base of the two |
 | `reviewed_at` | ISO-8601, minute precision, with offset | when the comment 
was rendered |
 | `reviewer` | GitHub login | `gh api user`, falling back to GraphQL `viewer` 
and `gh auth status` |
 | `model` | exact eligible model id | `review-runtime.json`, recorded before 
source review |
@@ -115,12 +118,16 @@ gh api repos/apache/doris/issues/66807/comments 
--paginate \
 The comment is not a substitute for the review documents; it is the receipt. A 
note earns its
 place only when a maintainer would act differently without it:
 
+- **every finding flagged `Regression: yes`** — an undeclared behaviour change 
in
+  `observability` / `test-coverage` / `wording` / `maintainability` that 
stayed below `Major` —
+  first, named by ID and category with its `path:line` anchor and why it is 
acceptable; the poster
+  refuses a receipt that would leave one undisclosed;
 - a residual risk that did not reach `Minor`, with a `path:line` anchor;
 - coverage the review could not reach (no build, no cluster, no test run);
 - a backport or upgrade consideration the PR itself does not state;
 
 A non-converged review records what remained open in the local documents and 
posts no receipt.
 
-Not this: restating what the PR does, listing every `Minor`/`Nit` (they live 
in the documents),
-praise, or anything that reads as an official Apache sign-off. `_None._` is a 
perfectly good
-notes section.
+Not this: restating what the PR does, listing every `Minor`/`Nit` (they live 
in the documents;
+only the ones flagged as regressions belong here), praise, or anything that 
reads as an official
+Apache sign-off. `_None._` is a perfectly good notes section when no finding 
is a regression.
diff --git a/skills/doris-repo-review/references/prompts.md 
b/skills/doris-repo-review/references/prompts.md
index d3ae4b8..18f9664 100644
--- a/skills/doris-repo-review/references/prompts.md
+++ b/skills/doris-repo-review/references/prompts.md
@@ -11,7 +11,7 @@ Substitute before use:
 |---|---|
 | `{CTX}` | absolute review context directory |
 | `{REPO_ROOT}` | absolute repo root |
-| `{BASE_SHA}` / `{HEAD_SHA}` | from `{CTX}/meta.env` |
+| `{BASE_SHA}` / `{HEAD_SHA}` / `{MERGE_BASE}` | from `{CTX}/meta.env` |
 | `{ROUND}` | 1-based round number |
 | `{AGENT_ID}` | short slug, e.g. `r1-fe-spi` |
 | `{FOCUS}` | this subagent's assigned coverage |
@@ -40,9 +40,14 @@ Authoritative PR context (do not obtain the diff or the 
changed-path list any ot
 - Earlier reviews of this PR     : {CTX}/prior_runs/   (may be absent - then 
this is the first)
 - Coverage checklist             : {CTX}/coverage_checklist.tsv
 - Diff range                     : {BASE_SHA}...{HEAD_SHA} (three-dot, from 
the merge base)
+- Merge base                     : {MERGE_BASE}
 
 These were generated with `git diff {BASE_SHA}...{HEAD_SHA}` in this worktree. 
The base SHA
-identifies the target-branch snapshot and is not necessarily the diff's left 
endpoint.
+identifies the target-branch snapshot and is not necessarily the diff's left 
endpoint; the
+diff's left endpoint is the merge base. Whenever you need "what did this file 
look like before
+the PR", read `git show {MERGE_BASE}:<path>` - never `git show 
{BASE_SHA}:<path>`: the target
+branch may carry commits the PR branch has not seen, and their absence at HEAD 
is not something
+the PR did.
 
 Before reading any file whose exact path is not already confirmed by 
pr_changed_files.txt,
 pr.diff, or a previous successful command output, you MUST first run `rg 
--files` to confirm
@@ -65,6 +70,17 @@ Ledger rules:
   globally unique: prefix them with your agent id, e.g. `{AGENT_ID}-01`.
 - If a candidate overlaps one that already exists in the ledger, record it in 
your own file
   with a duplicate note naming the existing candidate ID instead of restating 
it.
+- Every candidate carries a `Category:` - exactly one of functional-bug, 
functional-loss,
+  data-error, resource-leak, performance, observability, test-coverage, 
wording,
+  maintainability - naming the consequence, with the domain (concurrency, 
lifecycle,
+  compatibility, config, ...) in a parenthesis after it.
+- Every candidate carries `Regression: yes | no` — does HEAD behave 
differently from the merge
+  base (`git show {MERGE_BASE}:<path>`) in a way the PR body does not declare 
as intended? Cite
+  the merge-base lines. Propose the severity from the consequence when it 
triggers, never from
+  how narrow the trigger is: a regression in functional-bug, functional-loss, 
data-error,
+  resource-leak, or performance is at least Major, and for a PR presented as
+  behaviour-preserving every undeclared differing cell of your differential 
table is such a
+  regression.
 
 Line-number rule (this run has no GitHub inline comments, so anchors are the 
only pointer):
 - Every candidate MUST carry `Path:` plus `Line:` using NEW-SIDE (post-change) 
line numbers,
@@ -176,12 +192,15 @@ A subagent told only "review your slice" invents a 
method, and the method it inv
 techniques below are the ones that actually produced findings. Name the one 
you want, and say what
 it should be applied to.
 
-**D1. Differential against the base.** *Do not read the diff. Reconstruct both 
sides.* Extract the
-pre-change files with `git show {BASE_SHA}:<path>`, build the old and the new 
behaviour tables
-yourself - one row per (input, condition) the code distinguishes - and list 
every cell that
-differs. Then classify each differing cell as intended (name the commit that 
says so) or as a
-regression. This is what catches a defect whose changed line is *correct*: the 
line is right, the
-comment explaining it is right, and the consequence two modules away is wrong.
+**D1. Differential against the merge base.** *Do not read the diff. 
Reconstruct both sides.*
+Extract the pre-change files with `git show {MERGE_BASE}:<path>` (the merge 
base, not the
+target-branch tip: a commit the target branch gained after the PR branched off 
is not a cell the
+PR changed), build the old and the new behaviour tables yourself - one row per 
(input, condition)
+the code distinguishes - and list every cell that differs. Then classify each 
differing cell as
+intended (name the commit that says so) or as a regression, and name the 
consequence of each
+regression cell (functional-loss, functional-bug, data-error, ...). This is 
what catches a defect
+whose changed line is *correct*: the line is right, the comment explaining it 
is right, and the
+consequence two modules away is wrong.
 
 **D2. What did this switch turn on?** When a change fixes something that was 
silently not working,
 the path it revives has never been exercised. Ask: what else is on that path, 
what has never run,
@@ -216,3 +235,57 @@ find the two or three most load-bearing "not a bug" 
conclusions in the ledger an
 from primary sources. Then ask which *kinds* of check were never run at all - 
persistence and
 replay, upgrade *and downgrade*, CI gates, licence obligations, the thrift 
surface - and run the
 ones that apply.
+
+---
+
+## E. Severity-challenge subagent (one per intended downgrade, SKILL.md step 6)
+
+Append to the shared preamble. Dispatch it **before** finalizing a severity 
that is lower than the
+subagent proposed, or before dismissing a functional-bug / functional-loss / 
data-error /
+resource-leak candidate. It is
+deliberately one-sided: its job is to make the strongest case for the higher 
severity so the main
+agent's rationale has been argued against by someone before it is written into 
the documents.
+
+```
+You are a SEVERITY-CHALLENGE subagent. You are not reviewing a slice and you 
are not looking for
+new findings. One candidate is about to be rated lower than proposed, or 
dismissed, and your only
+job is to argue the higher severity from primary sources - then say honestly 
whether the argument
+holds.
+
+Candidate ID          : {CANDIDATE_ID}
+Claim                 : {CLAIM}
+Anchor(s)             : {ANCHORS}
+Proposed by subagent  : {PROPOSED_SEVERITY}
+Main agent intends    : {INTENDED_SEVERITY_OR_DISMISSAL}
+Main agent's rationale: {RATIONALE}
+Category              : {CATEGORY}
+Regression flag       : {REGRESSION_FLAG} (evidence: {REGRESSION_EVIDENCE})
+
+Do this, in order:
+1. Re-derive the consequence from the code, not from the ledger: when the 
trigger happens, what is
+   wrong, leaked, lost, or silently skipped? Who notices, and when? What is 
the blast radius (one
+   session, one query, the FE, persisted state)? Cite `path:line`.
+2. Check the regression flag yourself with `git show {MERGE_BASE}:<path>`: did 
the merge base
+   behave differently? If HEAD differs and the PR body does not declare it, 
the flag is `yes`. Then
+   check the category: is the consequence really observability / test-coverage 
/ wording /
+   maintainability, or is something the base *did* no longer happening 
(functional-loss), or
+   wrong (functional-bug / data-error), leaked (resource-leak), or slower 
(performance)? A `yes` in
+   one of those five is at least Major - say so even if the main agent's 
rationale never mentions
+   it. A difference that exists only because the target branch moved on after 
the PR branched off
+   is not a regression; say that too.
+3. Attack the rationale: is it about probability ("narrow", "cloud only", 
"needs two failures") or
+   about cost ("one-line fix") rather than about consequence? Those are not 
severity arguments.
+   Is "no wrong result" true for every consumer of the leaked or skipped state?
+4. Name the strongest counter-argument to your own case and say whether it 
survives.
+
+Write to {CTX}/ledger/sub-{ROUND}-challenge-{CANDIDATE_ID}.md and return 
exactly:
+- the severity you would assign, and the category and regression flag you 
verified,
+- the one-paragraph consequence analysis with citations,
+- UPHOLD_DOWNGRADE when the main agent's rationale survives your best case, 
otherwise
+  RAISE_SEVERITY with the floor that applies.
+```
+
+The main agent records the outcome in `main-merged.md` under the candidate 
(`Severity challenge:
+UPHOLD_DOWNGRADE | RAISE_SEVERITY, <one line>`), and the finding's **Severity 
rationale** paragraph
+must survive that challenge, not merely precede it.
+
diff --git a/skills/doris-repo-review/scripts/post-pass-comment.sh 
b/skills/doris-repo-review/scripts/post-pass-comment.sh
index 0e769e7..60a3224 100755
--- a/skills/doris-repo-review/scripts/post-pass-comment.sh
+++ b/skills/doris-repo-review/scripts/post-pass-comment.sh
@@ -10,6 +10,9 @@
 #
 # Runtime fields come from review-runtime.json. Review fields come directly 
from
 # verify-review-docs.py. The agent supplies notes, never receipt fields or 
format.
+# Refuses on anything that is not a converged APPROVE with zero Blocker/Major 
findings and
+# zero regressions in a category that carries the Major floor; a regression in 
any other
+# category (rated Minor/Nit) is allowed only when the notes disclose it.
 #
 # Rendered body: <ctx>/pr-comment.md   Posted URL: <ctx>/pr-comment.url
 set -euo pipefail
@@ -82,6 +85,14 @@ F_BLOCKER="$(jq -er '.findings.blocker | numbers' 
<<<"$RESULT_JSON")"
 F_MAJOR="$(jq -er '.findings.major | numbers' <<<"$RESULT_JSON")"
 F_MINOR="$(jq -er '.findings.minor | numbers' <<<"$RESULT_JSON")"
 F_NIT="$(jq -er '.findings.nit | numbers' <<<"$RESULT_JSON")"
+F_REGRESSIONS="$(jq -er '.regressions | numbers' <<<"$RESULT_JSON")" || {
+    echo "ERROR: the verifier did not report a regression count; update 
verify-review-docs.py." >&2
+    exit 2
+}
+F_FLOORED_REGRESSIONS="$(jq -er '.floored_regressions | numbers' 
<<<"$RESULT_JSON")" || {
+    echo "ERROR: the verifier did not report a floored-regression count; 
update verify-review-docs.py." >&2
+    exit 2
+}
 
 [ "$RESULT_COMMIT" = "$NORMALIZED_HEAD_SHA" ] || { echo "ERROR: review 
documents target another commit." >&2; exit 2; }
 [ "$VERDICT" = "APPROVE" ] || { echo "ERROR: review verdict is $VERDICT. 
Nothing was posted." >&2; exit 1; }
@@ -90,6 +101,13 @@ F_NIT="$(jq -er '.findings.nit | numbers' 
<<<"$RESULT_JSON")"
     echo "ERROR: Blocker or Major findings cannot produce a PASS comment." >&2
     exit 1
 }
+# The verifier already floors a functional / data / resource / performance 
regression at Major,
+# so this only fires when the two scripts disagree; a PASS receipt must never 
sit on top of an
+# undeclared behaviour change in one of those categories.
+[ "$F_FLOORED_REGRESSIONS" -eq 0 ] || {
+    echo "ERROR: $F_FLOORED_REGRESSIONS finding(s) are regressions in a 
category that is at least Major; a PASS receipt cannot be issued. Nothing was 
posted." >&2
+    exit 1
+}
 
 # ------------------------------------------------------------------------- 
notes
 NOTES_BODY="_None._"
@@ -116,6 +134,12 @@ if [ -n "$NOTES_FILE" ]; then
         NOTES_BODY="$(cat "$NOTES_FILE")"
     fi
 fi
+# A regression outside the floored categories stays Minor/Nit, so the verdict 
is still APPROVE -
+# but the receipt must not be silent about a behaviour change the PR body 
never declared.
+if [ "$F_REGRESSIONS" -gt 0 ] && [ "$NOTE_COUNT" -eq 0 ]; then
+    echo "ERROR: $F_REGRESSIONS finding(s) are undeclared behaviour changes 
(Regression: yes); the receipt must disclose each of them - list them as 
--notes-file bullets. Nothing was posted." >&2
+    exit 1
+fi
 # ------------------------------------------------------------------ live PR 
state
 PR_TSV="$(gh api "repos/${UPSTREAM_REPO}/pulls/${PR_NUMBER}" --jq '[.head.sha, 
.state] | @tsv')" || {
     echo "ERROR: cannot read ${UPSTREAM_REPO}#${PR_NUMBER}." >&2; exit 1; }
@@ -226,6 +250,7 @@ echo "reviewer  : ${REVIEWER}"
 echo "model     : ${MODEL} (effort ${EFFORT})"
 echo "findings  : blocker=${F_BLOCKER} major=${F_MAJOR} minor=${F_MINOR} 
nit=${F_NIT}"
 echo "notes     : ${NOTE_COUNT}"
+[ "$F_REGRESSIONS" -eq 0 ] || echo "disclosed : ${F_REGRESSIONS} undeclared 
behaviour change(s) in non-blocking categories, named in the notes"
 if [ "$ACTION" = "update" ]; then
     echo "action    : UPDATE the existing comment ${EXISTING_ID} (same commit)"
 elif [ -n "$LAST_ID" ]; then
diff --git a/skills/doris-repo-review/scripts/prepare-review-context.sh 
b/skills/doris-repo-review/scripts/prepare-review-context.sh
index b75ff36..c0101a4 100755
--- a/skills/doris-repo-review/scripts/prepare-review-context.sh
+++ b/skills/doris-repo-review/scripts/prepare-review-context.sh
@@ -154,6 +154,10 @@ fi
 
 MERGE_BASE="$(git merge-base "$BASE_SHA" "$HEAD_SHA")"
 DIFF_RANGE="${BASE_SHA}...${HEAD_SHA}"
+# Target-branch commits the PR branch has not seen. When this is non-zero, 
HEAD differs from
+# BASE_SHA in places the PR never touched; "what did the PR change" questions 
- the regression
+# flag above all - must be answered against MERGE_BASE, not BASE_SHA.
+TARGET_AHEAD="$(git rev-list --count "${MERGE_BASE}..${BASE_SHA}")"
 
 # ------------------------------------------------------------------ worktree 
state
 git status --porcelain > "$CTX/worktree_status.txt"
@@ -350,7 +354,9 @@ Candidate format:
   Status:
   Path:
   Line:          <new-side line or start-end, must match 
changed_line_ranges.txt or be justified>
-  Severity:      Blocker | Major | Minor | Nit
+  Severity:      Blocker | Major | Minor | Nit   <from the consequence when it 
triggers, not its probability>
+  Category:      functional-bug | functional-loss | data-error | resource-leak 
| performance | observability | test-coverage | wording | maintainability   
<one class, optionally "(domain note)">
+  Regression:    yes | no   <does HEAD differ from `git show 
MERGE_BASE:<path>` in a way the PR body does not declare? cite merge-base 
lines; yes in the first five categories is at least Major>
   Claim:
   Evidence:      <call chain / concrete trigger scenario / file:line citations>
   Duplicate relationship:
@@ -387,6 +393,10 @@ Owned by the main agent.
   Source IDs:
   Status:                                  <accepted | dismissed_with_evidence 
| duplicated>
   Severity:
+  Category:                                <functional-bug | functional-loss | 
data-error | resource-leak | performance | observability | test-coverage | 
wording | maintainability>
+  Regression:                              <yes | no, with the merge-base 
evidence (`git show MERGE_BASE:<path>`); yes in a 
functional/data/resource/performance category is at least Major>
+  Severity rationale:                      <required when rated below the 
subagent's proposal, and for a regression in any other category rated below 
Major: consequence when it triggers, why not Major>
+  Severity challenge:                      <UPHOLD_DOWNGRADE | RAISE_SEVERITY 
| n/a - from the section-E subagent>
   Path:
   Line:
   Claim:
@@ -447,6 +457,7 @@ fi
     echo "BASE_SOURCE=$BASE_SOURCE"
     echo "HEAD_SHA=$HEAD_SHA"
     echo "MERGE_BASE=$MERGE_BASE"
+    echo "TARGET_AHEAD=$TARGET_AHEAD"
     echo "DIFF_RANGE=$DIFF_RANGE"
     echo "DIRTY_FILES=$DIRTY_COUNT"
     echo "STATE_DIR=${STATE_DIR:-}"
@@ -461,6 +472,9 @@ echo "changed files : $(grep -c . 
"$CTX/pr_changed_files.txt" || true)"
 echo "commits       : $(grep -c . "$CTX/pr_commits.txt" || true)"
 echo "diff lines    : $(wc -l < "$CTX/pr.diff" | tr -d ' ')"
 echo "diffstat      : $(tail -n 1 "$CTX/pr_diffstat.txt")"
+if [ "${TARGET_AHEAD:-0}" -gt 0 ]; then
+    echo "target ahead  : $TARGET_AHEAD commit(s) on $BASE_REF after the merge 
base - judge regressions against MERGE_BASE, not BASE_SHA"
+fi
 echo
 if [ "${PRIOR_RUNS:-0}" -gt 0 ]; then
     echo "prior runs of this PR (read them, see SKILL.md 2.1): $PRIOR_RUNS"
diff --git a/skills/doris-repo-review/scripts/verify-review-docs.py 
b/skills/doris-repo-review/scripts/verify-review-docs.py
index bcebccf..037c411 100755
--- a/skills/doris-repo-review/scripts/verify-review-docs.py
+++ b/skills/doris-repo-review/scripts/verify-review-docs.py
@@ -1,5 +1,10 @@
 #!/usr/bin/env python3
-"""Validate both doris-repo-review documents and emit their agreed result."""
+"""Validate both doris-repo-review documents and emit their agreed result.
+
+Besides head, anchors, verdict, rounds and convergence, every finding must 
name its category
+and state whether it is a regression against the merge base. A regression in a 
category whose
+consequence is functional, data, resource, or performance (see CATEGORIES) is 
at least Major; a
+regression anywhere else may stay Minor/Nit only with a written severity 
rationale."""
 
 from __future__ import annotations
 
@@ -17,6 +22,47 @@ SEVERITY_RE = re.compile(
     r"^-\s+\*\*(?:Severity|等级)\*\*\s*[::]\s*(Blocker|Major|Minor|Nit)\s*$",
     re.IGNORECASE,
 )
+# `- **Regression**: yes` / `- **回归**:是`, optionally followed by one 
parenthetical note or
+# the template's HTML comment.
+REGRESSION_RE = re.compile(
+    r"^-\s+\*\*(?:Regression|回归)\*\*\s*[::]\s*(yes|no|是|否)\s*"
+    r"(?:[((][^()()]*[))]|<!--.*?-->)?\s*$",
+    re.IGNORECASE,
+)
+REGRESSION_YES = ("yes", "是")
+# `- **Category**: functional-bug (concurrency: lock order)` / `- 
**类别**:功能缺陷(并发)`.
+# The class comes first; one parenthetical domain note or the template's HTML 
comment may follow.
+CATEGORY_RE = re.compile(
+    r"^-\s+\*\*(?:Category|类别)\*\*\s*[::]\s*(.+?)\s*"
+    r"(?:[((][^()()]*[))]|<!--.*?-->)?\s*$",
+    re.IGNORECASE,
+)
+# Canonical category -> (regression floor applies?, accepted spellings). 
Spellings are compared
+# case-insensitively with spaces, hyphens and underscores removed, so 
`functional bug`,
+# `functional_bug` and `功能性 bug` all resolve. Anything else is rejected: an 
unknown category
+# would silently escape the floor.
+CATEGORIES: dict[str, tuple[bool, tuple[str, ...]]] = {
+    "functional-bug": (True, ("functional-bug", "功能缺陷", "功能性bug", "功能bug")),
+    "functional-loss": (True, ("functional-loss", "功能缺失", "功能性缺失")),
+    "data-error": (True, ("data-error", "数据错误")),
+    "resource-leak": (True, ("resource-leak", "资源泄露", "资源泄漏")),
+    "performance": (True, ("performance", "性能降低", "性能")),
+    "observability": (False, ("observability", "可观测性")),
+    "test-coverage": (False, ("test-coverage", "测试覆盖")),
+    "wording": (False, ("wording", "措辞")),
+    "maintainability": (False, ("maintainability", "可维护性")),
+}
+CATEGORY_ALIASES: dict[str, str] = {
+    re.sub(r"[\s_-]", "", alias).casefold(): canonical
+    for canonical, (_, aliases) in CATEGORIES.items()
+    for alias in aliases
+}
+FLOORED_CATEGORIES = tuple(name for name, (floored, _) in CATEGORIES.items() 
if floored)
+# A regression - behaviour that differs from the merge base without the PR 
declaring it
+# intended - in a floored category is at least Major, whatever the width of 
its trigger or the
+# size of its fix. In any other category it may stay below Major only with a 
written rationale.
+REGRESSION_SEVERITY_FLOOR = ("Blocker", "Major")
+RATIONALE_RE = re.compile(r"^\*\*(?:Severity rationale|定级理由)\s*[.。::]?\s*\*\*")
 HEAD_RE = re.compile(r"^\|\s*PR head\s*\|\s*`([0-9a-fA-F]{40})`")
 VERDICT_RE = 
re.compile(r"^\|\s*(?:Verdict|结论)\s*\|\s*\*\*(APPROVE|REQUEST_CHANGES)\*\*\s*\|")
 ROUNDS_RE = re.compile(r"^\|\s*(?:Rounds|轮次)\s*\|(.*?)\|\s*$")
@@ -63,6 +109,9 @@ class Document:
     converged: bool | None = None
     finding_ids: list[str] = field(default_factory=list)
     severities: dict[str, str] = field(default_factory=dict)
+    regressions: dict[str, bool] = field(default_factory=dict)
+    categories: dict[str, str] = field(default_factory=dict)
+    rationales: set[str] = field(default_factory=set)
     anchors: list[Anchor] = field(default_factory=list)
     finding_anchors: dict[str, list[Anchor]] = field(default_factory=lambda: 
defaultdict(list))
 
@@ -149,6 +198,31 @@ def parse_document(path: Path, errors: list[str]) -> 
Document:
             else:
                 document.severities[current_finding] = match.group(1).title()
 
+        if match := REGRESSION_RE.match(line):
+            if current_finding is None:
+                errors.append(f"{path}:{lineno}: regression flag is not under 
a finding")
+            elif current_finding in document.regressions:
+                errors.append(f"{path}:{lineno}: duplicate regression flag for 
{current_finding}")
+            else:
+                document.regressions[current_finding] = match.group(1).lower() 
in REGRESSION_YES
+
+        if match := CATEGORY_RE.match(line):
+            category = canonical_category(match.group(1))
+            if current_finding is None:
+                errors.append(f"{path}:{lineno}: category is not under a 
finding")
+            elif category is None:
+                errors.append(
+                    f"{path}:{lineno}: unknown category 
{match.group(1).strip()!r} for "
+                    f"{current_finding}; use one of {', '.join(CATEGORIES)}"
+                )
+            elif current_finding in document.categories:
+                errors.append(f"{path}:{lineno}: duplicate category for 
{current_finding}")
+            else:
+                document.categories[current_finding] = category
+
+        if current_finding is not None and RATIONALE_RE.match(line):
+            document.rationales.add(current_finding)
+
         for match in ANCHOR_RE.finditer(line):
             anchor = Anchor(
                 path=match.group(1),
@@ -174,6 +248,30 @@ def parse_document(path: Path, errors: list[str]) -> 
Document:
     for finding in document.finding_ids:
         if finding not in document.severities:
             errors.append(f"{path}: finding {finding} has no severity")
+        if finding not in document.categories:
+            errors.append(
+                f"{path}: finding {finding} has no Category/类别 line (one of 
{', '.join(CATEGORIES)})"
+            )
+        if finding not in document.regressions:
+            errors.append(
+                f"{path}: finding {finding} has no Regression/回归 line (yes/no 
against the merge base)"
+            )
+        elif document.regressions[finding] and finding in document.categories:
+            severity = document.severities.get(finding)
+            category = document.categories[finding]
+            if severity in (None, *REGRESSION_SEVERITY_FLOOR):
+                pass
+            elif category in FLOORED_CATEGORIES:
+                errors.append(
+                    f"{path}: finding {finding} is a regression in category 
{category} but rated "
+                    f"{severity}; a {category} regression against the merge 
base is at least Major"
+                )
+            elif finding not in document.rationales:
+                errors.append(
+                    f"{path}: finding {finding} is a regression in category 
{category} rated "
+                    f"{severity}; add a **Severity rationale** paragraph 
saying why the behaviour "
+                    "change is acceptable at that severity"
+                )
         if not document.finding_anchors.get(finding):
             errors.append(f"{path}: finding {finding} has no `path:line` 
anchor")
 
@@ -191,6 +289,23 @@ def severity_counts(document: Document) -> dict[str, int]:
     }
 
 
+def canonical_category(value: str) -> str | None:
+    return CATEGORY_ALIASES.get(re.sub(r"[\s_-]", "", value).casefold())
+
+
+def regression_count(document: Document) -> int:
+    return sum(1 for value in document.regressions.values() if value)
+
+
+def floored_regression_count(document: Document) -> int:
+    """Regressions whose category carries the Major floor - never present in a 
valid APPROVE."""
+    return sum(
+        1
+        for finding, value in document.regressions.items()
+        if value and document.categories.get(finding) in FLOORED_CATEGORIES
+    )
+
+
 def finding_anchor_keys(document: Document, finding: str) -> list[tuple[str, 
int, int]]:
     """Return stable semantic anchors, excluding document source-line 
metadata."""
     return sorted(
@@ -286,6 +401,10 @@ def main() -> int:
             errors.append("EN and ZH finding IDs or order differ")
         if left.severities != right.severities:
             errors.append("EN and ZH finding severities differ")
+        if left.regressions != right.regressions:
+            errors.append("EN and ZH regression flags differ")
+        if left.categories != right.categories:
+            errors.append("EN and ZH finding categories differ")
         for finding in sorted(set(left.finding_ids) & set(right.finding_ids)):
             if finding_anchor_keys(left, finding) != 
finding_anchor_keys(right, finding):
                 errors.append(f"EN and ZH anchors differ for {finding}")
@@ -314,6 +433,8 @@ def main() -> int:
         "commit": head_sha,
         "verdict": documents[0].verdict,
         "findings": severity_counts(documents[0]),
+        "regressions": regression_count(documents[0]),
+        "floored_regressions": floored_regression_count(documents[0]),
         "rounds": documents[0].rounds,
         "converged": documents[0].converged,
     }
diff --git a/verify/README.md b/verify/README.md
index 150bbfb..ee02418 100644
--- a/verify/README.md
+++ b/verify/README.md
@@ -8,7 +8,7 @@ real Apache Doris cluster and against `doriscli`. Three layers:
 | **L1 — knowledge** | Every DDL template (T1–T5) and DDL gotcha in 
`doris-best-practices/SKILL.md` is accepted / rejected exactly as claimed | 
`mysql` client | ✅ `run.sh` |
 | **L2 — CLI contract** | Every command + JSON field in `CLI-CONTRACT.md` 
really exists in `doriscli` | `doriscli --format json` + `jq` | ✅ `cli/run.sh` |
 | **L3 — behavior** | Triggering, evidence-first / safety guardrails, 
end-to-end DDL that loops back through L1 | nested `claude -p` | ✅ `behavior/` 
(`run.sh` + `e2e-advisor-ddl.sh` + `triggering.sh`) |
-| **Repo review** | Runtime policy, verified review result, commit binding, 
and automatic comment behavior | shell + Python + mocked `gh` | ✅ 
`repo-review/run.sh` |
+| **Repo review** | Runtime policy, verified review result (incl. the 
per-finding category, the regression flag and its category-aware Major floor), 
commit binding, and automatic comment behavior | shell + Python + mocked `gh` | 
✅ `repo-review/run.sh` |
 
 Run the repository-review contract tests without a cluster or GitHub access:
 
diff --git a/verify/repo-review/post-comment.sh 
b/verify/repo-review/post-comment.sh
index d03723d..56bcac9 100755
--- a/verify/repo-review/post-comment.sh
+++ b/verify/repo-review/post-comment.sh
@@ -42,10 +42,23 @@ DOCS_ROOT=$REPO
 EOF
 }
 
+# write_docs <head> <verdict> <severity> <rounds> <convergence> 
[regression=no] [category=functional-loss]
+# A non-floored category (observability, ...) gets a severity rationale so the 
verifier accepts
+# the regression below Major; the poster is then the only remaining gate.
 write_docs() {
     local head="$1" verdict="$2" severity="$3" rounds="$4" convergence="$5"
-    local zh_convergence=已收敛
+    local regression="${6:-no}" category="${7:-functional-loss}"
+    local zh_convergence=已收敛 zh_regression=否 zh_category=功能缺失
+    local en_rationale="" zh_rationale=""
     [ "$convergence" = converged ] || zh_convergence=未收敛
+    [ "$regression" = no ] || zh_regression=是
+    case "$category" in
+        functional-loss) ;;
+        observability) zh_category=可观测性
+            en_rationale='**Severity rationale.** only a debug line lost 
detail.'
+            zh_rationale='**定级理由。** 只是一条 debug 日志少了细节。' ;;
+        *) fail "fixture has no ZH form for category $category" ;;
+    esac
     cat > "$REPO/review-docs/pr-123-review.en.md" <<EOF
 # Code Review — PR #123: fixture
 | | |
@@ -55,7 +68,11 @@ write_docs() {
 | Rounds | $rounds of max 3, $convergence |
 ### F-01 · fixture
 - **Severity**: $severity
+- **Category**: $category
+- **Regression**: $regression
 - **Where**: \`src/Foo.java:1\`
+
+$en_rationale
 EOF
     cat > "$REPO/review-docs/pr-123-review.zh.md" <<EOF
 # 代码评审 — PR #123:fixture
@@ -66,7 +83,11 @@ EOF
 | 轮次 | 共 $rounds 轮(上限 3),$zh_convergence |
 ### F-01 · fixture
 - **等级**:$severity
+- **类别**:$zh_category
+- **回归**:$zh_regression
 - **位置**:\`src/Foo.java:1\`
+
+$zh_rationale
 EOF
 }
 
@@ -134,6 +155,27 @@ write_docs "$HEAD_SHA" REQUEST_CHANGES Major 2 converged
 expect_failure "REQUEST_CHANGES never posts" "verdict is REQUEST_CHANGES" \
     "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
 
+write_docs "$HEAD_SHA" APPROVE Minor 2 converged yes
+expect_failure "a floored regression rated Minor never posts" "failed 
verification" \
+    "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+write_docs "$HEAD_SHA" REQUEST_CHANGES Major 2 converged yes
+expect_failure "a floored regression finding never posts" "verdict is 
REQUEST_CHANGES" \
+    "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+write_docs "$HEAD_SHA" APPROVE Minor 2 converged yes observability
+expect_failure "an undisclosed non-floored regression never posts" "the 
receipt must disclose" \
+    "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+DISCLOSURE_NOTES="$TMP_ROOT/disclosure-notes.md"
+printf -- '- F-01 (observability, undeclared behaviour change) 
`src/Foo.java:1` — a debug line lost detail; acceptable.\n' > 
"$DISCLOSURE_NOTES"
+"$S/post-pass-comment.sh" --ctx "$CTX" --notes-file "$DISCLOSURE_NOTES" 
--dry-run > "$TMP_ROOT/disclosed"
+grep -Fq "F-01 (observability, undeclared behaviour change)" 
"$CTX/pr-comment.md" \
+    || fail "disclosed regression note is missing from the receipt"
+grep -Fq "findings: {blocker: 0, major: 0, minor: 1, nit: 0}" 
"$CTX/pr-comment.md" \
+    || fail "receipt findings are wrong for a disclosed regression"
+pass "a disclosed non-floored regression rated Minor still posts"
+
 write_docs "$HEAD_SHA" APPROVE Minor 3 'did not converge'
 expect_failure "non-converged review never posts" "did not converge" \
     "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
diff --git a/verify/repo-review/review-documents.sh 
b/verify/repo-review/review-documents.sh
index 1544774..cb87629 100755
--- a/verify/repo-review/review-documents.sh
+++ b/verify/repo-review/review-documents.sh
@@ -36,6 +36,27 @@ DOCS_ROOT=$REPO
 EOF
 }
 
+# EN_REGRESSION / ZH_REGRESSION: the value of the mandatory regression line 
(default no / 否);
+# EN_CATEGORY / ZH_CATEGORY: the value of the mandatory category line (default 
a floored one);
+# EN_RATIONALE / ZH_RATIONALE: an optional "Severity rationale" paragraph.
+# The literal word `omit` leaves the line out entirely.
+EN_REGRESSION="${EN_REGRESSION:-no}"
+ZH_REGRESSION="${ZH_REGRESSION:-否}"
+EN_CATEGORY="${EN_CATEGORY:-functional-loss}"
+ZH_CATEGORY="${ZH_CATEGORY:-功能缺失}"
+EN_RATIONALE="${EN_RATIONALE:-omit}"
+ZH_RATIONALE="${ZH_RATIONALE:-omit}"
+
+finding_line() {
+    local label="$1" value="$2"
+    [ "$value" = omit ] || printf -- '- **%s**%s\n' "$label" "$value"
+}
+
+rationale_paragraph() {
+    local label="$1" value="$2"
+    [ "$value" = omit ] || printf -- '\n**%s** %s\n' "$label" "$value"
+}
+
 write_docs() {
     local head="$1" verdict="$2" en_severity="$3" zh_severity="$4"
     local rounds="$5" en_convergence="$6" zh_convergence="$7"
@@ -57,7 +78,10 @@ write_docs() {
 ### F-01 · fixture finding
 
 - **Severity**: $en_severity
+$(finding_line Category ": $EN_CATEGORY")
+$(finding_line Regression ": $EN_REGRESSION")
 - **Where**: \`src/Foo.java:$en_anchor\`
+$(rationale_paragraph "Severity rationale." "$EN_RATIONALE")
 EOF
     cat > "$REPO/review-docs/pr-123-review.zh.md" <<EOF
 # 代码评审 — PR #123:fixture
@@ -73,7 +97,10 @@ EOF
 ### F-01 · fixture finding
 
 - **等级**:$zh_severity
+$(finding_line 类别 ":$ZH_CATEGORY")
+$(finding_line 回归 ":$ZH_REGRESSION")
 - **位置**:\`src/Foo.java:$zh_anchor\`
+$(rationale_paragraph "定级理由。" "$ZH_RATIONALE")
 EOF
 }
 
@@ -87,10 +114,79 @@ write_meta "$HEAD_SHA"
 write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
 RESULT="$(verify_json)"
 jq -e --arg head "$HEAD_SHA" \
-    '.commit == $head and .verdict == "APPROVE" and .findings.minor == 1 and 
.rounds == 2 and .converged' \
+    '.commit == $head and .verdict == "APPROVE" and .findings.minor == 1 and 
.regressions == 0 and .floored_regressions == 0 and .rounds == 2 and 
.converged' \
     <<<"$RESULT" >/dev/null || fail "verified result JSON is wrong"
 pass "matching documents produce one verified result"
 
+# ---- category-aware regression floor
+for category in "functional-bug 功能缺陷" "functional-loss 功能缺失" "data-error 数据错误" 
\
+                "resource-leak 资源泄露" "performance 性能降低"; do
+    set -- $category
+    EN_CATEGORY="$1" ZH_CATEGORY="$2" EN_REGRESSION=yes ZH_REGRESSION=是 \
+        write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+    expect_failure "a $1 regression cannot be rated Minor" "a $1 regression 
against the merge base is at least Major" verify_json
+done
+
+EN_REGRESSION=yes ZH_REGRESSION=是 write_docs "$HEAD_SHA" APPROVE Nit Nit 2 
converged 已收敛
+expect_failure "a floored regression cannot be rated Nit" "at least Major" 
verify_json
+
+EN_REGRESSION=yes ZH_REGRESSION=是 write_docs "$HEAD_SHA" REQUEST_CHANGES Major 
Major 2 converged 已收敛
+RESULT="$(verify_json)"
+jq -e '.verdict == "REQUEST_CHANGES" and .findings.major == 1 and .regressions 
== 1 and .floored_regressions == 1' <<<"$RESULT" >/dev/null \
+    || fail "regression counts are not reported"
+pass "a Major floored regression is counted in the verified result"
+
+EN_CATEGORY=observability ZH_CATEGORY=可观测性 EN_REGRESSION=yes ZH_REGRESSION=是 \
+    write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "a non-floored regression below Major needs a rationale" \
+    "add a **Severity rationale** paragraph" verify_json
+
+for category in "observability 可观测性" "test-coverage 测试覆盖" "wording 措辞" 
"maintainability 可维护性"; do
+    set -- $category
+    EN_CATEGORY="$1" ZH_CATEGORY="$2" EN_REGRESSION=yes ZH_REGRESSION=是 \
+        EN_RATIONALE="only troubleshooting convenience is affected." 
ZH_RATIONALE="只影响排障便利性。" \
+        write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+    RESULT="$(verify_json)"
+    jq -e '.verdict == "APPROVE" and .findings.minor == 1 and .regressions == 
1 and .floored_regressions == 0' <<<"$RESULT" >/dev/null \
+        || fail "a $1 regression with a rationale was not accepted at Minor"
+    pass "a $1 regression with a rationale may stay Minor"
+done
+
+EN_CATEGORY='functional-bug (concurrency: lock order)' 
ZH_CATEGORY='功能缺陷(并发:锁序)' \
+    EN_REGRESSION=yes ZH_REGRESSION=是 write_docs "$HEAD_SHA" REQUEST_CHANGES 
Major Major 2 converged 已收敛
+RESULT="$(verify_json)"
+jq -e '.floored_regressions == 1' <<<"$RESULT" >/dev/null || fail "a domain 
note after the category was not accepted"
+pass "a parenthetical domain note after the category is accepted"
+
+EN_CATEGORY='Functional Bug' ZH_CATEGORY='功能性 bug' EN_REGRESSION=yes 
ZH_REGRESSION=是 \
+    write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "category spelling variants still resolve to the floored class" 
\
+    "a functional-bug regression against the merge base is at least Major" 
verify_json
+
+EN_CATEGORY=omit write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "the category line is mandatory" "has no Category/类别 line" 
verify_json
+
+EN_CATEGORY=security ZH_CATEGORY=安全 write_docs "$HEAD_SHA" APPROVE Minor Minor 
2 converged 已收敛
+expect_failure "an unknown category is rejected" "unknown category 'security'" 
verify_json
+
+EN_CATEGORY=observability ZH_CATEGORY=功能缺失 write_docs "$HEAD_SHA" APPROVE 
Minor Minor 2 converged 已收敛
+expect_failure "EN and ZH categories must agree" "categories differ" 
verify_json
+
+EN_REGRESSION='no (base 699-701 already lacked the reset)' 
ZH_REGRESSION='否(base 本来就没有)' \
+    write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+RESULT="$(verify_json)"
+jq -e '.regressions == 0' <<<"$RESULT" >/dev/null || fail "annotated 
regression flag was not accepted"
+pass "a parenthetical note after the regression flag is accepted"
+
+EN_REGRESSION=omit write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "the regression line is mandatory" "has no Regression/回归 line" 
verify_json
+
+EN_REGRESSION=yes ZH_REGRESSION=否 write_docs "$HEAD_SHA" REQUEST_CHANGES Major 
Major 2 converged 已收敛
+expect_failure "EN and ZH regression flags must agree" "regression flags 
differ" verify_json
+
+EN_REGRESSION=maybe write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "an unparseable regression value is rejected" "has no 
Regression/回归 line" verify_json
+
 write_docs "$OTHER_SHA" APPROVE Minor Minor 2 converged 已收敛
 expect_failure "document commit must match context" "context head" verify_json
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to